##// END OF EJS Templates
files: use a common function to handle url-by-refs, and fix landing refs for SVN....
marcink -
r4373:702c378c stable
parent child Browse files
Show More
@@ -1,807 +1,808 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2020 RhodeCode GmbH
3 # Copyright (C) 2016-2020 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 time
21 import time
22 import logging
22 import logging
23 import operator
23 import operator
24
24
25 from pyramid import compat
25 from pyramid import compat
26 from pyramid.httpexceptions import HTTPFound, HTTPForbidden, HTTPBadRequest
26 from pyramid.httpexceptions import HTTPFound, HTTPForbidden, HTTPBadRequest
27
27
28 from rhodecode.lib import helpers as h, diffs, rc_cache
28 from rhodecode.lib import helpers as h, diffs, rc_cache
29 from rhodecode.lib.utils2 import (
29 from rhodecode.lib.utils2 import (
30 StrictAttributeDict, str2bool, safe_int, datetime_to_time, safe_unicode)
30 StrictAttributeDict, str2bool, safe_int, datetime_to_time, safe_unicode)
31 from rhodecode.lib.markup_renderer import MarkupRenderer, relative_links
31 from rhodecode.lib.markup_renderer import MarkupRenderer, relative_links
32 from rhodecode.lib.vcs.backends.base import EmptyCommit
32 from rhodecode.lib.vcs.backends.base import EmptyCommit
33 from rhodecode.lib.vcs.exceptions import RepositoryRequirementError
33 from rhodecode.lib.vcs.exceptions import RepositoryRequirementError
34 from rhodecode.model import repo
34 from rhodecode.model import repo
35 from rhodecode.model import repo_group
35 from rhodecode.model import repo_group
36 from rhodecode.model import user_group
36 from rhodecode.model import user_group
37 from rhodecode.model import user
37 from rhodecode.model import user
38 from rhodecode.model.db import User
38 from rhodecode.model.db import User
39 from rhodecode.model.scm import ScmModel
39 from rhodecode.model.scm import ScmModel
40 from rhodecode.model.settings import VcsSettingsModel, IssueTrackerSettingsModel
40 from rhodecode.model.settings import VcsSettingsModel, IssueTrackerSettingsModel
41 from rhodecode.model.repo import ReadmeFinder
41 from rhodecode.model.repo import ReadmeFinder
42
42
43 log = logging.getLogger(__name__)
43 log = logging.getLogger(__name__)
44
44
45
45
46 ADMIN_PREFIX = '/_admin'
46 ADMIN_PREFIX = '/_admin'
47 STATIC_FILE_PREFIX = '/_static'
47 STATIC_FILE_PREFIX = '/_static'
48
48
49 URL_NAME_REQUIREMENTS = {
49 URL_NAME_REQUIREMENTS = {
50 # group name can have a slash in them, but they must not end with a slash
50 # group name can have a slash in them, but they must not end with a slash
51 'group_name': r'.*?[^/]',
51 'group_name': r'.*?[^/]',
52 'repo_group_name': r'.*?[^/]',
52 'repo_group_name': r'.*?[^/]',
53 # repo names can have a slash in them, but they must not end with a slash
53 # repo names can have a slash in them, but they must not end with a slash
54 'repo_name': r'.*?[^/]',
54 'repo_name': r'.*?[^/]',
55 # file path eats up everything at the end
55 # file path eats up everything at the end
56 'f_path': r'.*',
56 'f_path': r'.*',
57 # reference types
57 # reference types
58 'source_ref_type': '(branch|book|tag|rev|\%\(source_ref_type\)s)',
58 'source_ref_type': '(branch|book|tag|rev|\%\(source_ref_type\)s)',
59 'target_ref_type': '(branch|book|tag|rev|\%\(target_ref_type\)s)',
59 'target_ref_type': '(branch|book|tag|rev|\%\(target_ref_type\)s)',
60 }
60 }
61
61
62
62
63 def add_route_with_slash(config,name, pattern, **kw):
63 def add_route_with_slash(config,name, pattern, **kw):
64 config.add_route(name, pattern, **kw)
64 config.add_route(name, pattern, **kw)
65 if not pattern.endswith('/'):
65 if not pattern.endswith('/'):
66 config.add_route(name + '_slash', pattern + '/', **kw)
66 config.add_route(name + '_slash', pattern + '/', **kw)
67
67
68
68
69 def add_route_requirements(route_path, requirements=None):
69 def add_route_requirements(route_path, requirements=None):
70 """
70 """
71 Adds regex requirements to pyramid routes using a mapping dict
71 Adds regex requirements to pyramid routes using a mapping dict
72 e.g::
72 e.g::
73 add_route_requirements('{repo_name}/settings')
73 add_route_requirements('{repo_name}/settings')
74 """
74 """
75 requirements = requirements or URL_NAME_REQUIREMENTS
75 requirements = requirements or URL_NAME_REQUIREMENTS
76 for key, regex in requirements.items():
76 for key, regex in requirements.items():
77 route_path = route_path.replace('{%s}' % key, '{%s:%s}' % (key, regex))
77 route_path = route_path.replace('{%s}' % key, '{%s:%s}' % (key, regex))
78 return route_path
78 return route_path
79
79
80
80
81 def get_format_ref_id(repo):
81 def get_format_ref_id(repo):
82 """Returns a `repo` specific reference formatter function"""
82 """Returns a `repo` specific reference formatter function"""
83 if h.is_svn(repo):
83 if h.is_svn(repo):
84 return _format_ref_id_svn
84 return _format_ref_id_svn
85 else:
85 else:
86 return _format_ref_id
86 return _format_ref_id
87
87
88
88
89 def _format_ref_id(name, raw_id):
89 def _format_ref_id(name, raw_id):
90 """Default formatting of a given reference `name`"""
90 """Default formatting of a given reference `name`"""
91 return name
91 return name
92
92
93
93
94 def _format_ref_id_svn(name, raw_id):
94 def _format_ref_id_svn(name, raw_id):
95 """Special way of formatting a reference for Subversion including path"""
95 """Special way of formatting a reference for Subversion including path"""
96 return '%s@%s' % (name, raw_id)
96 return '%s@%s' % (name, raw_id)
97
97
98
98
99 class TemplateArgs(StrictAttributeDict):
99 class TemplateArgs(StrictAttributeDict):
100 pass
100 pass
101
101
102
102
103 class BaseAppView(object):
103 class BaseAppView(object):
104
104
105 def __init__(self, context, request):
105 def __init__(self, context, request):
106 self.request = request
106 self.request = request
107 self.context = context
107 self.context = context
108 self.session = request.session
108 self.session = request.session
109 if not hasattr(request, 'user'):
109 if not hasattr(request, 'user'):
110 # NOTE(marcink): edge case, we ended up in matched route
110 # NOTE(marcink): edge case, we ended up in matched route
111 # but probably of web-app context, e.g API CALL/VCS CALL
111 # but probably of web-app context, e.g API CALL/VCS CALL
112 if hasattr(request, 'vcs_call') or hasattr(request, 'rpc_method'):
112 if hasattr(request, 'vcs_call') or hasattr(request, 'rpc_method'):
113 log.warning('Unable to process request `%s` in this scope', request)
113 log.warning('Unable to process request `%s` in this scope', request)
114 raise HTTPBadRequest()
114 raise HTTPBadRequest()
115
115
116 self._rhodecode_user = request.user # auth user
116 self._rhodecode_user = request.user # auth user
117 self._rhodecode_db_user = self._rhodecode_user.get_instance()
117 self._rhodecode_db_user = self._rhodecode_user.get_instance()
118 self._maybe_needs_password_change(
118 self._maybe_needs_password_change(
119 request.matched_route.name, self._rhodecode_db_user)
119 request.matched_route.name, self._rhodecode_db_user)
120
120
121 def _maybe_needs_password_change(self, view_name, user_obj):
121 def _maybe_needs_password_change(self, view_name, user_obj):
122 log.debug('Checking if user %s needs password change on view %s',
122 log.debug('Checking if user %s needs password change on view %s',
123 user_obj, view_name)
123 user_obj, view_name)
124 skip_user_views = [
124 skip_user_views = [
125 'logout', 'login',
125 'logout', 'login',
126 'my_account_password', 'my_account_password_update'
126 'my_account_password', 'my_account_password_update'
127 ]
127 ]
128
128
129 if not user_obj:
129 if not user_obj:
130 return
130 return
131
131
132 if user_obj.username == User.DEFAULT_USER:
132 if user_obj.username == User.DEFAULT_USER:
133 return
133 return
134
134
135 now = time.time()
135 now = time.time()
136 should_change = user_obj.user_data.get('force_password_change')
136 should_change = user_obj.user_data.get('force_password_change')
137 change_after = safe_int(should_change) or 0
137 change_after = safe_int(should_change) or 0
138 if should_change and now > change_after:
138 if should_change and now > change_after:
139 log.debug('User %s requires password change', user_obj)
139 log.debug('User %s requires password change', user_obj)
140 h.flash('You are required to change your password', 'warning',
140 h.flash('You are required to change your password', 'warning',
141 ignore_duplicate=True)
141 ignore_duplicate=True)
142
142
143 if view_name not in skip_user_views:
143 if view_name not in skip_user_views:
144 raise HTTPFound(
144 raise HTTPFound(
145 self.request.route_path('my_account_password'))
145 self.request.route_path('my_account_password'))
146
146
147 def _log_creation_exception(self, e, repo_name):
147 def _log_creation_exception(self, e, repo_name):
148 _ = self.request.translate
148 _ = self.request.translate
149 reason = None
149 reason = None
150 if len(e.args) == 2:
150 if len(e.args) == 2:
151 reason = e.args[1]
151 reason = e.args[1]
152
152
153 if reason == 'INVALID_CERTIFICATE':
153 if reason == 'INVALID_CERTIFICATE':
154 log.exception(
154 log.exception(
155 'Exception creating a repository: invalid certificate')
155 'Exception creating a repository: invalid certificate')
156 msg = (_('Error creating repository %s: invalid certificate')
156 msg = (_('Error creating repository %s: invalid certificate')
157 % repo_name)
157 % repo_name)
158 else:
158 else:
159 log.exception("Exception creating a repository")
159 log.exception("Exception creating a repository")
160 msg = (_('Error creating repository %s')
160 msg = (_('Error creating repository %s')
161 % repo_name)
161 % repo_name)
162 return msg
162 return msg
163
163
164 def _get_local_tmpl_context(self, include_app_defaults=True):
164 def _get_local_tmpl_context(self, include_app_defaults=True):
165 c = TemplateArgs()
165 c = TemplateArgs()
166 c.auth_user = self.request.user
166 c.auth_user = self.request.user
167 # TODO(marcink): migrate the usage of c.rhodecode_user to c.auth_user
167 # TODO(marcink): migrate the usage of c.rhodecode_user to c.auth_user
168 c.rhodecode_user = self.request.user
168 c.rhodecode_user = self.request.user
169
169
170 if include_app_defaults:
170 if include_app_defaults:
171 from rhodecode.lib.base import attach_context_attributes
171 from rhodecode.lib.base import attach_context_attributes
172 attach_context_attributes(c, self.request, self.request.user.user_id)
172 attach_context_attributes(c, self.request, self.request.user.user_id)
173
173
174 c.is_super_admin = c.auth_user.is_admin
174 c.is_super_admin = c.auth_user.is_admin
175
175
176 c.can_create_repo = c.is_super_admin
176 c.can_create_repo = c.is_super_admin
177 c.can_create_repo_group = c.is_super_admin
177 c.can_create_repo_group = c.is_super_admin
178 c.can_create_user_group = c.is_super_admin
178 c.can_create_user_group = c.is_super_admin
179
179
180 c.is_delegated_admin = False
180 c.is_delegated_admin = False
181
181
182 if not c.auth_user.is_default and not c.is_super_admin:
182 if not c.auth_user.is_default and not c.is_super_admin:
183 c.can_create_repo = h.HasPermissionAny('hg.create.repository')(
183 c.can_create_repo = h.HasPermissionAny('hg.create.repository')(
184 user=self.request.user)
184 user=self.request.user)
185 repositories = c.auth_user.repositories_admin or c.can_create_repo
185 repositories = c.auth_user.repositories_admin or c.can_create_repo
186
186
187 c.can_create_repo_group = h.HasPermissionAny('hg.repogroup.create.true')(
187 c.can_create_repo_group = h.HasPermissionAny('hg.repogroup.create.true')(
188 user=self.request.user)
188 user=self.request.user)
189 repository_groups = c.auth_user.repository_groups_admin or c.can_create_repo_group
189 repository_groups = c.auth_user.repository_groups_admin or c.can_create_repo_group
190
190
191 c.can_create_user_group = h.HasPermissionAny('hg.usergroup.create.true')(
191 c.can_create_user_group = h.HasPermissionAny('hg.usergroup.create.true')(
192 user=self.request.user)
192 user=self.request.user)
193 user_groups = c.auth_user.user_groups_admin or c.can_create_user_group
193 user_groups = c.auth_user.user_groups_admin or c.can_create_user_group
194 # delegated admin can create, or manage some objects
194 # delegated admin can create, or manage some objects
195 c.is_delegated_admin = repositories or repository_groups or user_groups
195 c.is_delegated_admin = repositories or repository_groups or user_groups
196 return c
196 return c
197
197
198 def _get_template_context(self, tmpl_args, **kwargs):
198 def _get_template_context(self, tmpl_args, **kwargs):
199
199
200 local_tmpl_args = {
200 local_tmpl_args = {
201 'defaults': {},
201 'defaults': {},
202 'errors': {},
202 'errors': {},
203 'c': tmpl_args
203 'c': tmpl_args
204 }
204 }
205 local_tmpl_args.update(kwargs)
205 local_tmpl_args.update(kwargs)
206 return local_tmpl_args
206 return local_tmpl_args
207
207
208 def load_default_context(self):
208 def load_default_context(self):
209 """
209 """
210 example:
210 example:
211
211
212 def load_default_context(self):
212 def load_default_context(self):
213 c = self._get_local_tmpl_context()
213 c = self._get_local_tmpl_context()
214 c.custom_var = 'foobar'
214 c.custom_var = 'foobar'
215
215
216 return c
216 return c
217 """
217 """
218 raise NotImplementedError('Needs implementation in view class')
218 raise NotImplementedError('Needs implementation in view class')
219
219
220
220
221 class RepoAppView(BaseAppView):
221 class RepoAppView(BaseAppView):
222
222
223 def __init__(self, context, request):
223 def __init__(self, context, request):
224 super(RepoAppView, self).__init__(context, request)
224 super(RepoAppView, self).__init__(context, request)
225 self.db_repo = request.db_repo
225 self.db_repo = request.db_repo
226 self.db_repo_name = self.db_repo.repo_name
226 self.db_repo_name = self.db_repo.repo_name
227 self.db_repo_pull_requests = ScmModel().get_pull_requests(self.db_repo)
227 self.db_repo_pull_requests = ScmModel().get_pull_requests(self.db_repo)
228 self.db_repo_artifacts = ScmModel().get_artifacts(self.db_repo)
228 self.db_repo_artifacts = ScmModel().get_artifacts(self.db_repo)
229 self.db_repo_patterns = IssueTrackerSettingsModel(repo=self.db_repo)
229 self.db_repo_patterns = IssueTrackerSettingsModel(repo=self.db_repo)
230
230
231 def _handle_missing_requirements(self, error):
231 def _handle_missing_requirements(self, error):
232 log.error(
232 log.error(
233 'Requirements are missing for repository %s: %s',
233 'Requirements are missing for repository %s: %s',
234 self.db_repo_name, safe_unicode(error))
234 self.db_repo_name, safe_unicode(error))
235
235
236 def _get_local_tmpl_context(self, include_app_defaults=True):
236 def _get_local_tmpl_context(self, include_app_defaults=True):
237 _ = self.request.translate
237 _ = self.request.translate
238 c = super(RepoAppView, self)._get_local_tmpl_context(
238 c = super(RepoAppView, self)._get_local_tmpl_context(
239 include_app_defaults=include_app_defaults)
239 include_app_defaults=include_app_defaults)
240
240
241 # register common vars for this type of view
241 # register common vars for this type of view
242 c.rhodecode_db_repo = self.db_repo
242 c.rhodecode_db_repo = self.db_repo
243 c.repo_name = self.db_repo_name
243 c.repo_name = self.db_repo_name
244 c.repository_pull_requests = self.db_repo_pull_requests
244 c.repository_pull_requests = self.db_repo_pull_requests
245 c.repository_artifacts = self.db_repo_artifacts
245 c.repository_artifacts = self.db_repo_artifacts
246 c.repository_is_user_following = ScmModel().is_following_repo(
246 c.repository_is_user_following = ScmModel().is_following_repo(
247 self.db_repo_name, self._rhodecode_user.user_id)
247 self.db_repo_name, self._rhodecode_user.user_id)
248 self.path_filter = PathFilter(None)
248 self.path_filter = PathFilter(None)
249
249
250 c.repository_requirements_missing = {}
250 c.repository_requirements_missing = {}
251 try:
251 try:
252 self.rhodecode_vcs_repo = self.db_repo.scm_instance()
252 self.rhodecode_vcs_repo = self.db_repo.scm_instance()
253 # NOTE(marcink):
253 # NOTE(marcink):
254 # comparison to None since if it's an object __bool__ is expensive to
254 # comparison to None since if it's an object __bool__ is expensive to
255 # calculate
255 # calculate
256 if self.rhodecode_vcs_repo is not None:
256 if self.rhodecode_vcs_repo is not None:
257 path_perms = self.rhodecode_vcs_repo.get_path_permissions(
257 path_perms = self.rhodecode_vcs_repo.get_path_permissions(
258 c.auth_user.username)
258 c.auth_user.username)
259 self.path_filter = PathFilter(path_perms)
259 self.path_filter = PathFilter(path_perms)
260 except RepositoryRequirementError as e:
260 except RepositoryRequirementError as e:
261 c.repository_requirements_missing = {'error': str(e)}
261 c.repository_requirements_missing = {'error': str(e)}
262 self._handle_missing_requirements(e)
262 self._handle_missing_requirements(e)
263 self.rhodecode_vcs_repo = None
263 self.rhodecode_vcs_repo = None
264
264
265 c.path_filter = self.path_filter # used by atom_feed_entry.mako
265 c.path_filter = self.path_filter # used by atom_feed_entry.mako
266
266
267 if self.rhodecode_vcs_repo is None:
267 if self.rhodecode_vcs_repo is None:
268 # unable to fetch this repo as vcs instance, report back to user
268 # unable to fetch this repo as vcs instance, report back to user
269 h.flash(_(
269 h.flash(_(
270 "The repository `%(repo_name)s` cannot be loaded in filesystem. "
270 "The repository `%(repo_name)s` cannot be loaded in filesystem. "
271 "Please check if it exist, or is not damaged.") %
271 "Please check if it exist, or is not damaged.") %
272 {'repo_name': c.repo_name},
272 {'repo_name': c.repo_name},
273 category='error', ignore_duplicate=True)
273 category='error', ignore_duplicate=True)
274 if c.repository_requirements_missing:
274 if c.repository_requirements_missing:
275 route = self.request.matched_route.name
275 route = self.request.matched_route.name
276 if route.startswith(('edit_repo', 'repo_summary')):
276 if route.startswith(('edit_repo', 'repo_summary')):
277 # allow summary and edit repo on missing requirements
277 # allow summary and edit repo on missing requirements
278 return c
278 return c
279
279
280 raise HTTPFound(
280 raise HTTPFound(
281 h.route_path('repo_summary', repo_name=self.db_repo_name))
281 h.route_path('repo_summary', repo_name=self.db_repo_name))
282
282
283 else: # redirect if we don't show missing requirements
283 else: # redirect if we don't show missing requirements
284 raise HTTPFound(h.route_path('home'))
284 raise HTTPFound(h.route_path('home'))
285
285
286 c.has_origin_repo_read_perm = False
286 c.has_origin_repo_read_perm = False
287 if self.db_repo.fork:
287 if self.db_repo.fork:
288 c.has_origin_repo_read_perm = h.HasRepoPermissionAny(
288 c.has_origin_repo_read_perm = h.HasRepoPermissionAny(
289 'repository.write', 'repository.read', 'repository.admin')(
289 'repository.write', 'repository.read', 'repository.admin')(
290 self.db_repo.fork.repo_name, 'summary fork link')
290 self.db_repo.fork.repo_name, 'summary fork link')
291
291
292 return c
292 return c
293
293
294 def _get_f_path_unchecked(self, matchdict, default=None):
294 def _get_f_path_unchecked(self, matchdict, default=None):
295 """
295 """
296 Should only be used by redirects, everything else should call _get_f_path
296 Should only be used by redirects, everything else should call _get_f_path
297 """
297 """
298 f_path = matchdict.get('f_path')
298 f_path = matchdict.get('f_path')
299 if f_path:
299 if f_path:
300 # fix for multiple initial slashes that causes errors for GIT
300 # fix for multiple initial slashes that causes errors for GIT
301 return f_path.lstrip('/')
301 return f_path.lstrip('/')
302
302
303 return default
303 return default
304
304
305 def _get_f_path(self, matchdict, default=None):
305 def _get_f_path(self, matchdict, default=None):
306 f_path_match = self._get_f_path_unchecked(matchdict, default)
306 f_path_match = self._get_f_path_unchecked(matchdict, default)
307 return self.path_filter.assert_path_permissions(f_path_match)
307 return self.path_filter.assert_path_permissions(f_path_match)
308
308
309 def _get_general_setting(self, target_repo, settings_key, default=False):
309 def _get_general_setting(self, target_repo, settings_key, default=False):
310 settings_model = VcsSettingsModel(repo=target_repo)
310 settings_model = VcsSettingsModel(repo=target_repo)
311 settings = settings_model.get_general_settings()
311 settings = settings_model.get_general_settings()
312 return settings.get(settings_key, default)
312 return settings.get(settings_key, default)
313
313
314 def _get_repo_setting(self, target_repo, settings_key, default=False):
314 def _get_repo_setting(self, target_repo, settings_key, default=False):
315 settings_model = VcsSettingsModel(repo=target_repo)
315 settings_model = VcsSettingsModel(repo=target_repo)
316 settings = settings_model.get_repo_settings_inherited()
316 settings = settings_model.get_repo_settings_inherited()
317 return settings.get(settings_key, default)
317 return settings.get(settings_key, default)
318
318
319 def _get_readme_data(self, db_repo, renderer_type, commit_id=None, path='/'):
319 def _get_readme_data(self, db_repo, renderer_type, commit_id=None, path='/'):
320 log.debug('Looking for README file at path %s', path)
320 log.debug('Looking for README file at path %s', path)
321 if commit_id:
321 if commit_id:
322 landing_commit_id = commit_id
322 landing_commit_id = commit_id
323 else:
323 else:
324 landing_commit = db_repo.get_landing_commit()
324 landing_commit = db_repo.get_landing_commit()
325 if isinstance(landing_commit, EmptyCommit):
325 if isinstance(landing_commit, EmptyCommit):
326 return None, None
326 return None, None
327 landing_commit_id = landing_commit.raw_id
327 landing_commit_id = landing_commit.raw_id
328
328
329 cache_namespace_uid = 'cache_repo.{}'.format(db_repo.repo_id)
329 cache_namespace_uid = 'cache_repo.{}'.format(db_repo.repo_id)
330 region = rc_cache.get_or_create_region('cache_repo', cache_namespace_uid)
330 region = rc_cache.get_or_create_region('cache_repo', cache_namespace_uid)
331 start = time.time()
331 start = time.time()
332
332
333 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid)
333 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid)
334 def generate_repo_readme(repo_id, _commit_id, _repo_name, _readme_search_path, _renderer_type):
334 def generate_repo_readme(repo_id, _commit_id, _repo_name, _readme_search_path, _renderer_type):
335 readme_data = None
335 readme_data = None
336 readme_filename = None
336 readme_filename = None
337
337
338 commit = db_repo.get_commit(_commit_id)
338 commit = db_repo.get_commit(_commit_id)
339 log.debug("Searching for a README file at commit %s.", _commit_id)
339 log.debug("Searching for a README file at commit %s.", _commit_id)
340 readme_node = ReadmeFinder(_renderer_type).search(commit, path=_readme_search_path)
340 readme_node = ReadmeFinder(_renderer_type).search(commit, path=_readme_search_path)
341
341
342 if readme_node:
342 if readme_node:
343 log.debug('Found README node: %s', readme_node)
343 log.debug('Found README node: %s', readme_node)
344 relative_urls = {
344 relative_urls = {
345 'raw': h.route_path(
345 'raw': h.route_path(
346 'repo_file_raw', repo_name=_repo_name,
346 'repo_file_raw', repo_name=_repo_name,
347 commit_id=commit.raw_id, f_path=readme_node.path),
347 commit_id=commit.raw_id, f_path=readme_node.path),
348 'standard': h.route_path(
348 'standard': h.route_path(
349 'repo_files', repo_name=_repo_name,
349 'repo_files', repo_name=_repo_name,
350 commit_id=commit.raw_id, f_path=readme_node.path),
350 commit_id=commit.raw_id, f_path=readme_node.path),
351 }
351 }
352 readme_data = self._render_readme_or_none(commit, readme_node, relative_urls)
352 readme_data = self._render_readme_or_none(commit, readme_node, relative_urls)
353 readme_filename = readme_node.unicode_path
353 readme_filename = readme_node.unicode_path
354
354
355 return readme_data, readme_filename
355 return readme_data, readme_filename
356
356
357 readme_data, readme_filename = generate_repo_readme(
357 readme_data, readme_filename = generate_repo_readme(
358 db_repo.repo_id, landing_commit_id, db_repo.repo_name, path, renderer_type,)
358 db_repo.repo_id, landing_commit_id, db_repo.repo_name, path, renderer_type,)
359 compute_time = time.time() - start
359 compute_time = time.time() - start
360 log.debug('Repo README for path %s generated and computed in %.4fs',
360 log.debug('Repo README for path %s generated and computed in %.4fs',
361 path, compute_time)
361 path, compute_time)
362 return readme_data, readme_filename
362 return readme_data, readme_filename
363
363
364 def _render_readme_or_none(self, commit, readme_node, relative_urls):
364 def _render_readme_or_none(self, commit, readme_node, relative_urls):
365 log.debug('Found README file `%s` rendering...', readme_node.path)
365 log.debug('Found README file `%s` rendering...', readme_node.path)
366 renderer = MarkupRenderer()
366 renderer = MarkupRenderer()
367 try:
367 try:
368 html_source = renderer.render(
368 html_source = renderer.render(
369 readme_node.content, filename=readme_node.path)
369 readme_node.content, filename=readme_node.path)
370 if relative_urls:
370 if relative_urls:
371 return relative_links(html_source, relative_urls)
371 return relative_links(html_source, relative_urls)
372 return html_source
372 return html_source
373 except Exception:
373 except Exception:
374 log.exception(
374 log.exception(
375 "Exception while trying to render the README")
375 "Exception while trying to render the README")
376
376
377 def get_recache_flag(self):
377 def get_recache_flag(self):
378 for flag_name in ['force_recache', 'force-recache', 'no-cache']:
378 for flag_name in ['force_recache', 'force-recache', 'no-cache']:
379 flag_val = self.request.GET.get(flag_name)
379 flag_val = self.request.GET.get(flag_name)
380 if str2bool(flag_val):
380 if str2bool(flag_val):
381 return True
381 return True
382 return False
382 return False
383
383
384
384
385 class PathFilter(object):
385 class PathFilter(object):
386
386
387 # Expects and instance of BasePathPermissionChecker or None
387 # Expects and instance of BasePathPermissionChecker or None
388 def __init__(self, permission_checker):
388 def __init__(self, permission_checker):
389 self.permission_checker = permission_checker
389 self.permission_checker = permission_checker
390
390
391 def assert_path_permissions(self, path):
391 def assert_path_permissions(self, path):
392 if self.path_access_allowed(path):
392 if self.path_access_allowed(path):
393 return path
393 return path
394 raise HTTPForbidden()
394 raise HTTPForbidden()
395
395
396 def path_access_allowed(self, path):
396 def path_access_allowed(self, path):
397 log.debug('Checking ACL permissions for PathFilter for `%s`', path)
397 log.debug('Checking ACL permissions for PathFilter for `%s`', path)
398 if self.permission_checker:
398 if self.permission_checker:
399 has_access = path and self.permission_checker.has_access(path)
399 has_access = path and self.permission_checker.has_access(path)
400 log.debug('ACL Permissions checker enabled, ACL Check has_access: %s', has_access)
400 log.debug('ACL Permissions checker enabled, ACL Check has_access: %s', has_access)
401 return has_access
401 return has_access
402
402
403 log.debug('ACL permissions checker not enabled, skipping...')
403 log.debug('ACL permissions checker not enabled, skipping...')
404 return True
404 return True
405
405
406 def filter_patchset(self, patchset):
406 def filter_patchset(self, patchset):
407 if not self.permission_checker or not patchset:
407 if not self.permission_checker or not patchset:
408 return patchset, False
408 return patchset, False
409 had_filtered = False
409 had_filtered = False
410 filtered_patchset = []
410 filtered_patchset = []
411 for patch in patchset:
411 for patch in patchset:
412 filename = patch.get('filename', None)
412 filename = patch.get('filename', None)
413 if not filename or self.permission_checker.has_access(filename):
413 if not filename or self.permission_checker.has_access(filename):
414 filtered_patchset.append(patch)
414 filtered_patchset.append(patch)
415 else:
415 else:
416 had_filtered = True
416 had_filtered = True
417 if had_filtered:
417 if had_filtered:
418 if isinstance(patchset, diffs.LimitedDiffContainer):
418 if isinstance(patchset, diffs.LimitedDiffContainer):
419 filtered_patchset = diffs.LimitedDiffContainer(patchset.diff_limit, patchset.cur_diff_size, filtered_patchset)
419 filtered_patchset = diffs.LimitedDiffContainer(patchset.diff_limit, patchset.cur_diff_size, filtered_patchset)
420 return filtered_patchset, True
420 return filtered_patchset, True
421 else:
421 else:
422 return patchset, False
422 return patchset, False
423
423
424 def render_patchset_filtered(self, diffset, patchset, source_ref=None, target_ref=None):
424 def render_patchset_filtered(self, diffset, patchset, source_ref=None, target_ref=None):
425 filtered_patchset, has_hidden_changes = self.filter_patchset(patchset)
425 filtered_patchset, has_hidden_changes = self.filter_patchset(patchset)
426 result = diffset.render_patchset(
426 result = diffset.render_patchset(
427 filtered_patchset, source_ref=source_ref, target_ref=target_ref)
427 filtered_patchset, source_ref=source_ref, target_ref=target_ref)
428 result.has_hidden_changes = has_hidden_changes
428 result.has_hidden_changes = has_hidden_changes
429 return result
429 return result
430
430
431 def get_raw_patch(self, diff_processor):
431 def get_raw_patch(self, diff_processor):
432 if self.permission_checker is None:
432 if self.permission_checker is None:
433 return diff_processor.as_raw()
433 return diff_processor.as_raw()
434 elif self.permission_checker.has_full_access:
434 elif self.permission_checker.has_full_access:
435 return diff_processor.as_raw()
435 return diff_processor.as_raw()
436 else:
436 else:
437 return '# Repository has user-specific filters, raw patch generation is disabled.'
437 return '# Repository has user-specific filters, raw patch generation is disabled.'
438
438
439 @property
439 @property
440 def is_enabled(self):
440 def is_enabled(self):
441 return self.permission_checker is not None
441 return self.permission_checker is not None
442
442
443
443
444 class RepoGroupAppView(BaseAppView):
444 class RepoGroupAppView(BaseAppView):
445 def __init__(self, context, request):
445 def __init__(self, context, request):
446 super(RepoGroupAppView, self).__init__(context, request)
446 super(RepoGroupAppView, self).__init__(context, request)
447 self.db_repo_group = request.db_repo_group
447 self.db_repo_group = request.db_repo_group
448 self.db_repo_group_name = self.db_repo_group.group_name
448 self.db_repo_group_name = self.db_repo_group.group_name
449
449
450 def _get_local_tmpl_context(self, include_app_defaults=True):
450 def _get_local_tmpl_context(self, include_app_defaults=True):
451 _ = self.request.translate
451 _ = self.request.translate
452 c = super(RepoGroupAppView, self)._get_local_tmpl_context(
452 c = super(RepoGroupAppView, self)._get_local_tmpl_context(
453 include_app_defaults=include_app_defaults)
453 include_app_defaults=include_app_defaults)
454 c.repo_group = self.db_repo_group
454 c.repo_group = self.db_repo_group
455 return c
455 return c
456
456
457 def _revoke_perms_on_yourself(self, form_result):
457 def _revoke_perms_on_yourself(self, form_result):
458 _updates = filter(lambda u: self._rhodecode_user.user_id == int(u[0]),
458 _updates = filter(lambda u: self._rhodecode_user.user_id == int(u[0]),
459 form_result['perm_updates'])
459 form_result['perm_updates'])
460 _additions = filter(lambda u: self._rhodecode_user.user_id == int(u[0]),
460 _additions = filter(lambda u: self._rhodecode_user.user_id == int(u[0]),
461 form_result['perm_additions'])
461 form_result['perm_additions'])
462 _deletions = filter(lambda u: self._rhodecode_user.user_id == int(u[0]),
462 _deletions = filter(lambda u: self._rhodecode_user.user_id == int(u[0]),
463 form_result['perm_deletions'])
463 form_result['perm_deletions'])
464 admin_perm = 'group.admin'
464 admin_perm = 'group.admin'
465 if _updates and _updates[0][1] != admin_perm or \
465 if _updates and _updates[0][1] != admin_perm or \
466 _additions and _additions[0][1] != admin_perm or \
466 _additions and _additions[0][1] != admin_perm or \
467 _deletions and _deletions[0][1] != admin_perm:
467 _deletions and _deletions[0][1] != admin_perm:
468 return True
468 return True
469 return False
469 return False
470
470
471
471
472 class UserGroupAppView(BaseAppView):
472 class UserGroupAppView(BaseAppView):
473 def __init__(self, context, request):
473 def __init__(self, context, request):
474 super(UserGroupAppView, self).__init__(context, request)
474 super(UserGroupAppView, self).__init__(context, request)
475 self.db_user_group = request.db_user_group
475 self.db_user_group = request.db_user_group
476 self.db_user_group_name = self.db_user_group.users_group_name
476 self.db_user_group_name = self.db_user_group.users_group_name
477
477
478
478
479 class UserAppView(BaseAppView):
479 class UserAppView(BaseAppView):
480 def __init__(self, context, request):
480 def __init__(self, context, request):
481 super(UserAppView, self).__init__(context, request)
481 super(UserAppView, self).__init__(context, request)
482 self.db_user = request.db_user
482 self.db_user = request.db_user
483 self.db_user_id = self.db_user.user_id
483 self.db_user_id = self.db_user.user_id
484
484
485 _ = self.request.translate
485 _ = self.request.translate
486 if not request.db_user_supports_default:
486 if not request.db_user_supports_default:
487 if self.db_user.username == User.DEFAULT_USER:
487 if self.db_user.username == User.DEFAULT_USER:
488 h.flash(_("Editing user `{}` is disabled.".format(
488 h.flash(_("Editing user `{}` is disabled.".format(
489 User.DEFAULT_USER)), category='warning')
489 User.DEFAULT_USER)), category='warning')
490 raise HTTPFound(h.route_path('users'))
490 raise HTTPFound(h.route_path('users'))
491
491
492
492
493 class DataGridAppView(object):
493 class DataGridAppView(object):
494 """
494 """
495 Common class to have re-usable grid rendering components
495 Common class to have re-usable grid rendering components
496 """
496 """
497
497
498 def _extract_ordering(self, request, column_map=None):
498 def _extract_ordering(self, request, column_map=None):
499 column_map = column_map or {}
499 column_map = column_map or {}
500 column_index = safe_int(request.GET.get('order[0][column]'))
500 column_index = safe_int(request.GET.get('order[0][column]'))
501 order_dir = request.GET.get(
501 order_dir = request.GET.get(
502 'order[0][dir]', 'desc')
502 'order[0][dir]', 'desc')
503 order_by = request.GET.get(
503 order_by = request.GET.get(
504 'columns[%s][data][sort]' % column_index, 'name_raw')
504 'columns[%s][data][sort]' % column_index, 'name_raw')
505
505
506 # translate datatable to DB columns
506 # translate datatable to DB columns
507 order_by = column_map.get(order_by) or order_by
507 order_by = column_map.get(order_by) or order_by
508
508
509 search_q = request.GET.get('search[value]')
509 search_q = request.GET.get('search[value]')
510 return search_q, order_by, order_dir
510 return search_q, order_by, order_dir
511
511
512 def _extract_chunk(self, request):
512 def _extract_chunk(self, request):
513 start = safe_int(request.GET.get('start'), 0)
513 start = safe_int(request.GET.get('start'), 0)
514 length = safe_int(request.GET.get('length'), 25)
514 length = safe_int(request.GET.get('length'), 25)
515 draw = safe_int(request.GET.get('draw'))
515 draw = safe_int(request.GET.get('draw'))
516 return draw, start, length
516 return draw, start, length
517
517
518 def _get_order_col(self, order_by, model):
518 def _get_order_col(self, order_by, model):
519 if isinstance(order_by, compat.string_types):
519 if isinstance(order_by, compat.string_types):
520 try:
520 try:
521 return operator.attrgetter(order_by)(model)
521 return operator.attrgetter(order_by)(model)
522 except AttributeError:
522 except AttributeError:
523 return None
523 return None
524 else:
524 else:
525 return order_by
525 return order_by
526
526
527
527
528 class BaseReferencesView(RepoAppView):
528 class BaseReferencesView(RepoAppView):
529 """
529 """
530 Base for reference view for branches, tags and bookmarks.
530 Base for reference view for branches, tags and bookmarks.
531 """
531 """
532 def load_default_context(self):
532 def load_default_context(self):
533 c = self._get_local_tmpl_context()
533 c = self._get_local_tmpl_context()
534
535
536 return c
534 return c
537
535
538 def load_refs_context(self, ref_items, partials_template):
536 def load_refs_context(self, ref_items, partials_template):
539 _render = self.request.get_partial_renderer(partials_template)
537 _render = self.request.get_partial_renderer(partials_template)
540 pre_load = ["author", "date", "message", "parents"]
538 pre_load = ["author", "date", "message", "parents"]
541
539
542 is_svn = h.is_svn(self.rhodecode_vcs_repo)
540 is_svn = h.is_svn(self.rhodecode_vcs_repo)
543 is_hg = h.is_hg(self.rhodecode_vcs_repo)
541 is_hg = h.is_hg(self.rhodecode_vcs_repo)
544
542
545 format_ref_id = get_format_ref_id(self.rhodecode_vcs_repo)
543 format_ref_id = get_format_ref_id(self.rhodecode_vcs_repo)
546
544
547 closed_refs = {}
545 closed_refs = {}
548 if is_hg:
546 if is_hg:
549 closed_refs = self.rhodecode_vcs_repo.branches_closed
547 closed_refs = self.rhodecode_vcs_repo.branches_closed
550
548
551 data = []
549 data = []
552 for ref_name, commit_id in ref_items:
550 for ref_name, commit_id in ref_items:
553 commit = self.rhodecode_vcs_repo.get_commit(
551 commit = self.rhodecode_vcs_repo.get_commit(
554 commit_id=commit_id, pre_load=pre_load)
552 commit_id=commit_id, pre_load=pre_load)
555 closed = ref_name in closed_refs
553 closed = ref_name in closed_refs
556
554
557 # TODO: johbo: Unify generation of reference links
555 # TODO: johbo: Unify generation of reference links
558 use_commit_id = '/' in ref_name or is_svn
556 use_commit_id = '/' in ref_name or is_svn
559
557
560 if use_commit_id:
558 if use_commit_id:
561 files_url = h.route_path(
559 files_url = h.route_path(
562 'repo_files',
560 'repo_files',
563 repo_name=self.db_repo_name,
561 repo_name=self.db_repo_name,
564 f_path=ref_name if is_svn else '',
562 f_path=ref_name if is_svn else '',
565 commit_id=commit_id)
563 commit_id=commit_id,
564 _query=dict(at=ref_name)
565 )
566
566
567 else:
567 else:
568 files_url = h.route_path(
568 files_url = h.route_path(
569 'repo_files',
569 'repo_files',
570 repo_name=self.db_repo_name,
570 repo_name=self.db_repo_name,
571 f_path=ref_name if is_svn else '',
571 f_path=ref_name if is_svn else '',
572 commit_id=ref_name,
572 commit_id=ref_name,
573 _query=dict(at=ref_name))
573 _query=dict(at=ref_name)
574 )
574
575
575 data.append({
576 data.append({
576 "name": _render('name', ref_name, files_url, closed),
577 "name": _render('name', ref_name, files_url, closed),
577 "name_raw": ref_name,
578 "name_raw": ref_name,
578 "date": _render('date', commit.date),
579 "date": _render('date', commit.date),
579 "date_raw": datetime_to_time(commit.date),
580 "date_raw": datetime_to_time(commit.date),
580 "author": _render('author', commit.author),
581 "author": _render('author', commit.author),
581 "commit": _render(
582 "commit": _render(
582 'commit', commit.message, commit.raw_id, commit.idx),
583 'commit', commit.message, commit.raw_id, commit.idx),
583 "commit_raw": commit.idx,
584 "commit_raw": commit.idx,
584 "compare": _render(
585 "compare": _render(
585 'compare', format_ref_id(ref_name, commit.raw_id)),
586 'compare', format_ref_id(ref_name, commit.raw_id)),
586 })
587 })
587
588
588 return data
589 return data
589
590
590
591
591 class RepoRoutePredicate(object):
592 class RepoRoutePredicate(object):
592 def __init__(self, val, config):
593 def __init__(self, val, config):
593 self.val = val
594 self.val = val
594
595
595 def text(self):
596 def text(self):
596 return 'repo_route = %s' % self.val
597 return 'repo_route = %s' % self.val
597
598
598 phash = text
599 phash = text
599
600
600 def __call__(self, info, request):
601 def __call__(self, info, request):
601 if hasattr(request, 'vcs_call'):
602 if hasattr(request, 'vcs_call'):
602 # skip vcs calls
603 # skip vcs calls
603 return
604 return
604
605
605 repo_name = info['match']['repo_name']
606 repo_name = info['match']['repo_name']
606 repo_model = repo.RepoModel()
607 repo_model = repo.RepoModel()
607
608
608 by_name_match = repo_model.get_by_repo_name(repo_name, cache=False)
609 by_name_match = repo_model.get_by_repo_name(repo_name, cache=False)
609
610
610 def redirect_if_creating(route_info, db_repo):
611 def redirect_if_creating(route_info, db_repo):
611 skip_views = ['edit_repo_advanced_delete']
612 skip_views = ['edit_repo_advanced_delete']
612 route = route_info['route']
613 route = route_info['route']
613 # we should skip delete view so we can actually "remove" repositories
614 # we should skip delete view so we can actually "remove" repositories
614 # if they get stuck in creating state.
615 # if they get stuck in creating state.
615 if route.name in skip_views:
616 if route.name in skip_views:
616 return
617 return
617
618
618 if db_repo.repo_state in [repo.Repository.STATE_PENDING]:
619 if db_repo.repo_state in [repo.Repository.STATE_PENDING]:
619 repo_creating_url = request.route_path(
620 repo_creating_url = request.route_path(
620 'repo_creating', repo_name=db_repo.repo_name)
621 'repo_creating', repo_name=db_repo.repo_name)
621 raise HTTPFound(repo_creating_url)
622 raise HTTPFound(repo_creating_url)
622
623
623 if by_name_match:
624 if by_name_match:
624 # register this as request object we can re-use later
625 # register this as request object we can re-use later
625 request.db_repo = by_name_match
626 request.db_repo = by_name_match
626 redirect_if_creating(info, by_name_match)
627 redirect_if_creating(info, by_name_match)
627 return True
628 return True
628
629
629 by_id_match = repo_model.get_repo_by_id(repo_name)
630 by_id_match = repo_model.get_repo_by_id(repo_name)
630 if by_id_match:
631 if by_id_match:
631 request.db_repo = by_id_match
632 request.db_repo = by_id_match
632 redirect_if_creating(info, by_id_match)
633 redirect_if_creating(info, by_id_match)
633 return True
634 return True
634
635
635 return False
636 return False
636
637
637
638
638 class RepoForbidArchivedRoutePredicate(object):
639 class RepoForbidArchivedRoutePredicate(object):
639 def __init__(self, val, config):
640 def __init__(self, val, config):
640 self.val = val
641 self.val = val
641
642
642 def text(self):
643 def text(self):
643 return 'repo_forbid_archived = %s' % self.val
644 return 'repo_forbid_archived = %s' % self.val
644
645
645 phash = text
646 phash = text
646
647
647 def __call__(self, info, request):
648 def __call__(self, info, request):
648 _ = request.translate
649 _ = request.translate
649 rhodecode_db_repo = request.db_repo
650 rhodecode_db_repo = request.db_repo
650
651
651 log.debug(
652 log.debug(
652 '%s checking if archived flag for repo for %s',
653 '%s checking if archived flag for repo for %s',
653 self.__class__.__name__, rhodecode_db_repo.repo_name)
654 self.__class__.__name__, rhodecode_db_repo.repo_name)
654
655
655 if rhodecode_db_repo.archived:
656 if rhodecode_db_repo.archived:
656 log.warning('Current view is not supported for archived repo:%s',
657 log.warning('Current view is not supported for archived repo:%s',
657 rhodecode_db_repo.repo_name)
658 rhodecode_db_repo.repo_name)
658
659
659 h.flash(
660 h.flash(
660 h.literal(_('Action not supported for archived repository.')),
661 h.literal(_('Action not supported for archived repository.')),
661 category='warning')
662 category='warning')
662 summary_url = request.route_path(
663 summary_url = request.route_path(
663 'repo_summary', repo_name=rhodecode_db_repo.repo_name)
664 'repo_summary', repo_name=rhodecode_db_repo.repo_name)
664 raise HTTPFound(summary_url)
665 raise HTTPFound(summary_url)
665 return True
666 return True
666
667
667
668
668 class RepoTypeRoutePredicate(object):
669 class RepoTypeRoutePredicate(object):
669 def __init__(self, val, config):
670 def __init__(self, val, config):
670 self.val = val or ['hg', 'git', 'svn']
671 self.val = val or ['hg', 'git', 'svn']
671
672
672 def text(self):
673 def text(self):
673 return 'repo_accepted_type = %s' % self.val
674 return 'repo_accepted_type = %s' % self.val
674
675
675 phash = text
676 phash = text
676
677
677 def __call__(self, info, request):
678 def __call__(self, info, request):
678 if hasattr(request, 'vcs_call'):
679 if hasattr(request, 'vcs_call'):
679 # skip vcs calls
680 # skip vcs calls
680 return
681 return
681
682
682 rhodecode_db_repo = request.db_repo
683 rhodecode_db_repo = request.db_repo
683
684
684 log.debug(
685 log.debug(
685 '%s checking repo type for %s in %s',
686 '%s checking repo type for %s in %s',
686 self.__class__.__name__, rhodecode_db_repo.repo_type, self.val)
687 self.__class__.__name__, rhodecode_db_repo.repo_type, self.val)
687
688
688 if rhodecode_db_repo.repo_type in self.val:
689 if rhodecode_db_repo.repo_type in self.val:
689 return True
690 return True
690 else:
691 else:
691 log.warning('Current view is not supported for repo type:%s',
692 log.warning('Current view is not supported for repo type:%s',
692 rhodecode_db_repo.repo_type)
693 rhodecode_db_repo.repo_type)
693 return False
694 return False
694
695
695
696
696 class RepoGroupRoutePredicate(object):
697 class RepoGroupRoutePredicate(object):
697 def __init__(self, val, config):
698 def __init__(self, val, config):
698 self.val = val
699 self.val = val
699
700
700 def text(self):
701 def text(self):
701 return 'repo_group_route = %s' % self.val
702 return 'repo_group_route = %s' % self.val
702
703
703 phash = text
704 phash = text
704
705
705 def __call__(self, info, request):
706 def __call__(self, info, request):
706 if hasattr(request, 'vcs_call'):
707 if hasattr(request, 'vcs_call'):
707 # skip vcs calls
708 # skip vcs calls
708 return
709 return
709
710
710 repo_group_name = info['match']['repo_group_name']
711 repo_group_name = info['match']['repo_group_name']
711 repo_group_model = repo_group.RepoGroupModel()
712 repo_group_model = repo_group.RepoGroupModel()
712 by_name_match = repo_group_model.get_by_group_name(repo_group_name, cache=False)
713 by_name_match = repo_group_model.get_by_group_name(repo_group_name, cache=False)
713
714
714 if by_name_match:
715 if by_name_match:
715 # register this as request object we can re-use later
716 # register this as request object we can re-use later
716 request.db_repo_group = by_name_match
717 request.db_repo_group = by_name_match
717 return True
718 return True
718
719
719 return False
720 return False
720
721
721
722
722 class UserGroupRoutePredicate(object):
723 class UserGroupRoutePredicate(object):
723 def __init__(self, val, config):
724 def __init__(self, val, config):
724 self.val = val
725 self.val = val
725
726
726 def text(self):
727 def text(self):
727 return 'user_group_route = %s' % self.val
728 return 'user_group_route = %s' % self.val
728
729
729 phash = text
730 phash = text
730
731
731 def __call__(self, info, request):
732 def __call__(self, info, request):
732 if hasattr(request, 'vcs_call'):
733 if hasattr(request, 'vcs_call'):
733 # skip vcs calls
734 # skip vcs calls
734 return
735 return
735
736
736 user_group_id = info['match']['user_group_id']
737 user_group_id = info['match']['user_group_id']
737 user_group_model = user_group.UserGroup()
738 user_group_model = user_group.UserGroup()
738 by_id_match = user_group_model.get(user_group_id, cache=False)
739 by_id_match = user_group_model.get(user_group_id, cache=False)
739
740
740 if by_id_match:
741 if by_id_match:
741 # register this as request object we can re-use later
742 # register this as request object we can re-use later
742 request.db_user_group = by_id_match
743 request.db_user_group = by_id_match
743 return True
744 return True
744
745
745 return False
746 return False
746
747
747
748
748 class UserRoutePredicateBase(object):
749 class UserRoutePredicateBase(object):
749 supports_default = None
750 supports_default = None
750
751
751 def __init__(self, val, config):
752 def __init__(self, val, config):
752 self.val = val
753 self.val = val
753
754
754 def text(self):
755 def text(self):
755 raise NotImplementedError()
756 raise NotImplementedError()
756
757
757 def __call__(self, info, request):
758 def __call__(self, info, request):
758 if hasattr(request, 'vcs_call'):
759 if hasattr(request, 'vcs_call'):
759 # skip vcs calls
760 # skip vcs calls
760 return
761 return
761
762
762 user_id = info['match']['user_id']
763 user_id = info['match']['user_id']
763 user_model = user.User()
764 user_model = user.User()
764 by_id_match = user_model.get(user_id, cache=False)
765 by_id_match = user_model.get(user_id, cache=False)
765
766
766 if by_id_match:
767 if by_id_match:
767 # register this as request object we can re-use later
768 # register this as request object we can re-use later
768 request.db_user = by_id_match
769 request.db_user = by_id_match
769 request.db_user_supports_default = self.supports_default
770 request.db_user_supports_default = self.supports_default
770 return True
771 return True
771
772
772 return False
773 return False
773
774
774
775
775 class UserRoutePredicate(UserRoutePredicateBase):
776 class UserRoutePredicate(UserRoutePredicateBase):
776 supports_default = False
777 supports_default = False
777
778
778 def text(self):
779 def text(self):
779 return 'user_route = %s' % self.val
780 return 'user_route = %s' % self.val
780
781
781 phash = text
782 phash = text
782
783
783
784
784 class UserRouteWithDefaultPredicate(UserRoutePredicateBase):
785 class UserRouteWithDefaultPredicate(UserRoutePredicateBase):
785 supports_default = True
786 supports_default = True
786
787
787 def text(self):
788 def text(self):
788 return 'user_with_default_route = %s' % self.val
789 return 'user_with_default_route = %s' % self.val
789
790
790 phash = text
791 phash = text
791
792
792
793
793 def includeme(config):
794 def includeme(config):
794 config.add_route_predicate(
795 config.add_route_predicate(
795 'repo_route', RepoRoutePredicate)
796 'repo_route', RepoRoutePredicate)
796 config.add_route_predicate(
797 config.add_route_predicate(
797 'repo_accepted_types', RepoTypeRoutePredicate)
798 'repo_accepted_types', RepoTypeRoutePredicate)
798 config.add_route_predicate(
799 config.add_route_predicate(
799 'repo_forbid_when_archived', RepoForbidArchivedRoutePredicate)
800 'repo_forbid_when_archived', RepoForbidArchivedRoutePredicate)
800 config.add_route_predicate(
801 config.add_route_predicate(
801 'repo_group_route', RepoGroupRoutePredicate)
802 'repo_group_route', RepoGroupRoutePredicate)
802 config.add_route_predicate(
803 config.add_route_predicate(
803 'user_group_route', UserGroupRoutePredicate)
804 'user_group_route', UserGroupRoutePredicate)
804 config.add_route_predicate(
805 config.add_route_predicate(
805 'user_route_with_default', UserRouteWithDefaultPredicate)
806 'user_route_with_default', UserRouteWithDefaultPredicate)
806 config.add_route_predicate(
807 config.add_route_predicate(
807 'user_route', UserRoutePredicate)
808 'user_route', UserRoutePredicate)
@@ -1,1614 +1,1618 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2020 RhodeCode GmbH
3 # Copyright (C) 2011-2020 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 import pathlib2
28 import pathlib2
29
29
30 from pyramid.httpexceptions import HTTPNotFound, HTTPBadRequest, HTTPFound
30 from pyramid.httpexceptions import HTTPNotFound, HTTPBadRequest, HTTPFound
31 from pyramid.view import view_config
31 from pyramid.view import view_config
32 from pyramid.renderers import render
32 from pyramid.renderers import render
33 from pyramid.response import Response
33 from pyramid.response import Response
34
34
35 import rhodecode
35 import rhodecode
36 from rhodecode.apps._base import RepoAppView
36 from rhodecode.apps._base import RepoAppView
37
37
38
38
39 from rhodecode.lib import diffs, helpers as h, rc_cache
39 from rhodecode.lib import diffs, helpers as h, rc_cache
40 from rhodecode.lib import audit_logger
40 from rhodecode.lib import audit_logger
41 from rhodecode.lib.view_utils import parse_path_ref
41 from rhodecode.lib.view_utils import parse_path_ref
42 from rhodecode.lib.exceptions import NonRelativePathError
42 from rhodecode.lib.exceptions import NonRelativePathError
43 from rhodecode.lib.codeblocks import (
43 from rhodecode.lib.codeblocks import (
44 filenode_as_lines_tokens, filenode_as_annotated_lines_tokens)
44 filenode_as_lines_tokens, filenode_as_annotated_lines_tokens)
45 from rhodecode.lib.utils2 import (
45 from rhodecode.lib.utils2 import (
46 convert_line_endings, detect_mode, safe_str, str2bool, safe_int, sha1, safe_unicode)
46 convert_line_endings, detect_mode, safe_str, str2bool, safe_int, sha1, safe_unicode)
47 from rhodecode.lib.auth import (
47 from rhodecode.lib.auth import (
48 LoginRequired, HasRepoPermissionAnyDecorator, CSRFRequired)
48 LoginRequired, HasRepoPermissionAnyDecorator, CSRFRequired)
49 from rhodecode.lib.vcs import path as vcspath
49 from rhodecode.lib.vcs import path as vcspath
50 from rhodecode.lib.vcs.backends.base import EmptyCommit
50 from rhodecode.lib.vcs.backends.base import EmptyCommit
51 from rhodecode.lib.vcs.conf import settings
51 from rhodecode.lib.vcs.conf import settings
52 from rhodecode.lib.vcs.nodes import FileNode
52 from rhodecode.lib.vcs.nodes import FileNode
53 from rhodecode.lib.vcs.exceptions import (
53 from rhodecode.lib.vcs.exceptions import (
54 RepositoryError, CommitDoesNotExistError, EmptyRepositoryError,
54 RepositoryError, CommitDoesNotExistError, EmptyRepositoryError,
55 ImproperArchiveTypeError, VCSError, NodeAlreadyExistsError,
55 ImproperArchiveTypeError, VCSError, NodeAlreadyExistsError,
56 NodeDoesNotExistError, CommitError, NodeError)
56 NodeDoesNotExistError, CommitError, NodeError)
57
57
58 from rhodecode.model.scm import ScmModel
58 from rhodecode.model.scm import ScmModel
59 from rhodecode.model.db import Repository
59 from rhodecode.model.db import Repository
60
60
61 log = logging.getLogger(__name__)
61 log = logging.getLogger(__name__)
62
62
63
63
64 class RepoFilesView(RepoAppView):
64 class RepoFilesView(RepoAppView):
65
65
66 @staticmethod
66 @staticmethod
67 def adjust_file_path_for_svn(f_path, repo):
67 def adjust_file_path_for_svn(f_path, repo):
68 """
68 """
69 Computes the relative path of `f_path`.
69 Computes the relative path of `f_path`.
70
70
71 This is mainly based on prefix matching of the recognized tags and
71 This is mainly based on prefix matching of the recognized tags and
72 branches in the underlying repository.
72 branches in the underlying repository.
73 """
73 """
74 tags_and_branches = itertools.chain(
74 tags_and_branches = itertools.chain(
75 repo.branches.iterkeys(),
75 repo.branches.iterkeys(),
76 repo.tags.iterkeys())
76 repo.tags.iterkeys())
77 tags_and_branches = sorted(tags_and_branches, key=len, reverse=True)
77 tags_and_branches = sorted(tags_and_branches, key=len, reverse=True)
78
78
79 for name in tags_and_branches:
79 for name in tags_and_branches:
80 if f_path.startswith('{}/'.format(name)):
80 if f_path.startswith('{}/'.format(name)):
81 f_path = vcspath.relpath(f_path, name)
81 f_path = vcspath.relpath(f_path, name)
82 break
82 break
83 return f_path
83 return f_path
84
84
85 def load_default_context(self):
85 def load_default_context(self):
86 c = self._get_local_tmpl_context(include_app_defaults=True)
86 c = self._get_local_tmpl_context(include_app_defaults=True)
87 c.rhodecode_repo = self.rhodecode_vcs_repo
87 c.rhodecode_repo = self.rhodecode_vcs_repo
88 c.enable_downloads = self.db_repo.enable_downloads
88 c.enable_downloads = self.db_repo.enable_downloads
89 return c
89 return c
90
90
91 def _ensure_not_locked(self, commit_id='tip'):
91 def _ensure_not_locked(self, commit_id='tip'):
92 _ = self.request.translate
92 _ = self.request.translate
93
93
94 repo = self.db_repo
94 repo = self.db_repo
95 if repo.enable_locking and repo.locked[0]:
95 if repo.enable_locking and repo.locked[0]:
96 h.flash(_('This repository has been locked by %s on %s')
96 h.flash(_('This repository has been locked by %s on %s')
97 % (h.person_by_id(repo.locked[0]),
97 % (h.person_by_id(repo.locked[0]),
98 h.format_date(h.time_to_datetime(repo.locked[1]))),
98 h.format_date(h.time_to_datetime(repo.locked[1]))),
99 'warning')
99 'warning')
100 files_url = h.route_path(
100 files_url = h.route_path(
101 'repo_files:default_path',
101 'repo_files:default_path',
102 repo_name=self.db_repo_name, commit_id=commit_id)
102 repo_name=self.db_repo_name, commit_id=commit_id)
103 raise HTTPFound(files_url)
103 raise HTTPFound(files_url)
104
104
105 def forbid_non_head(self, is_head, f_path, commit_id='tip', json_mode=False):
105 def forbid_non_head(self, is_head, f_path, commit_id='tip', json_mode=False):
106 _ = self.request.translate
106 _ = self.request.translate
107
107
108 if not is_head:
108 if not is_head:
109 message = _('Cannot modify file. '
109 message = _('Cannot modify file. '
110 'Given commit `{}` is not head of a branch.').format(commit_id)
110 'Given commit `{}` is not head of a branch.').format(commit_id)
111 h.flash(message, category='warning')
111 h.flash(message, category='warning')
112
112
113 if json_mode:
113 if json_mode:
114 return message
114 return message
115
115
116 files_url = h.route_path(
116 files_url = h.route_path(
117 'repo_files', repo_name=self.db_repo_name, commit_id=commit_id,
117 'repo_files', repo_name=self.db_repo_name, commit_id=commit_id,
118 f_path=f_path)
118 f_path=f_path)
119 raise HTTPFound(files_url)
119 raise HTTPFound(files_url)
120
120
121 def check_branch_permission(self, branch_name, commit_id='tip', json_mode=False):
121 def check_branch_permission(self, branch_name, commit_id='tip', json_mode=False):
122 _ = self.request.translate
122 _ = self.request.translate
123
123
124 rule, branch_perm = self._rhodecode_user.get_rule_and_branch_permission(
124 rule, branch_perm = self._rhodecode_user.get_rule_and_branch_permission(
125 self.db_repo_name, branch_name)
125 self.db_repo_name, branch_name)
126 if branch_perm and branch_perm not in ['branch.push', 'branch.push_force']:
126 if branch_perm and branch_perm not in ['branch.push', 'branch.push_force']:
127 message = _('Branch `{}` changes forbidden by rule {}.').format(
127 message = _('Branch `{}` changes forbidden by rule {}.').format(
128 branch_name, rule)
128 branch_name, rule)
129 h.flash(message, 'warning')
129 h.flash(message, 'warning')
130
130
131 if json_mode:
131 if json_mode:
132 return message
132 return message
133
133
134 files_url = h.route_path(
134 files_url = h.route_path(
135 'repo_files:default_path', repo_name=self.db_repo_name, commit_id=commit_id)
135 'repo_files:default_path', repo_name=self.db_repo_name, commit_id=commit_id)
136
136
137 raise HTTPFound(files_url)
137 raise HTTPFound(files_url)
138
138
139 def _get_commit_and_path(self):
139 def _get_commit_and_path(self):
140 default_commit_id = self.db_repo.landing_ref_name
140 default_commit_id = self.db_repo.landing_ref_name
141 default_f_path = '/'
141 default_f_path = '/'
142
142
143 commit_id = self.request.matchdict.get(
143 commit_id = self.request.matchdict.get(
144 'commit_id', default_commit_id)
144 'commit_id', default_commit_id)
145 f_path = self._get_f_path(self.request.matchdict, default_f_path)
145 f_path = self._get_f_path(self.request.matchdict, default_f_path)
146 return commit_id, f_path
146 return commit_id, f_path
147
147
148 def _get_default_encoding(self, c):
148 def _get_default_encoding(self, c):
149 enc_list = getattr(c, 'default_encodings', [])
149 enc_list = getattr(c, 'default_encodings', [])
150 return enc_list[0] if enc_list else 'UTF-8'
150 return enc_list[0] if enc_list else 'UTF-8'
151
151
152 def _get_commit_or_redirect(self, commit_id, redirect_after=True):
152 def _get_commit_or_redirect(self, commit_id, redirect_after=True):
153 """
153 """
154 This is a safe way to get commit. If an error occurs it redirects to
154 This is a safe way to get commit. If an error occurs it redirects to
155 tip with proper message
155 tip with proper message
156
156
157 :param commit_id: id of commit to fetch
157 :param commit_id: id of commit to fetch
158 :param redirect_after: toggle redirection
158 :param redirect_after: toggle redirection
159 """
159 """
160 _ = self.request.translate
160 _ = self.request.translate
161
161
162 try:
162 try:
163 return self.rhodecode_vcs_repo.get_commit(commit_id)
163 return self.rhodecode_vcs_repo.get_commit(commit_id)
164 except EmptyRepositoryError:
164 except EmptyRepositoryError:
165 if not redirect_after:
165 if not redirect_after:
166 return None
166 return None
167
167
168 _url = h.route_path(
168 _url = h.route_path(
169 'repo_files_add_file',
169 'repo_files_add_file',
170 repo_name=self.db_repo_name, commit_id=0, f_path='')
170 repo_name=self.db_repo_name, commit_id=0, f_path='')
171
171
172 if h.HasRepoPermissionAny(
172 if h.HasRepoPermissionAny(
173 'repository.write', 'repository.admin')(self.db_repo_name):
173 'repository.write', 'repository.admin')(self.db_repo_name):
174 add_new = h.link_to(
174 add_new = h.link_to(
175 _('Click here to add a new file.'), _url, class_="alert-link")
175 _('Click here to add a new file.'), _url, class_="alert-link")
176 else:
176 else:
177 add_new = ""
177 add_new = ""
178
178
179 h.flash(h.literal(
179 h.flash(h.literal(
180 _('There are no files yet. %s') % add_new), category='warning')
180 _('There are no files yet. %s') % add_new), category='warning')
181 raise HTTPFound(
181 raise HTTPFound(
182 h.route_path('repo_summary', repo_name=self.db_repo_name))
182 h.route_path('repo_summary', repo_name=self.db_repo_name))
183
183
184 except (CommitDoesNotExistError, LookupError) as e:
184 except (CommitDoesNotExistError, LookupError) as e:
185 msg = _('No such commit exists for this repository. Commit: {}').format(commit_id)
185 msg = _('No such commit exists for this repository. Commit: {}').format(commit_id)
186 h.flash(msg, category='error')
186 h.flash(msg, category='error')
187 raise HTTPNotFound()
187 raise HTTPNotFound()
188 except RepositoryError as e:
188 except RepositoryError as e:
189 h.flash(safe_str(h.escape(e)), category='error')
189 h.flash(safe_str(h.escape(e)), category='error')
190 raise HTTPNotFound()
190 raise HTTPNotFound()
191
191
192 def _get_filenode_or_redirect(self, commit_obj, path):
192 def _get_filenode_or_redirect(self, commit_obj, path):
193 """
193 """
194 Returns file_node, if error occurs or given path is directory,
194 Returns file_node, if error occurs or given path is directory,
195 it'll redirect to top level path
195 it'll redirect to top level path
196 """
196 """
197 _ = self.request.translate
197 _ = self.request.translate
198
198
199 try:
199 try:
200 file_node = commit_obj.get_node(path)
200 file_node = commit_obj.get_node(path)
201 if file_node.is_dir():
201 if file_node.is_dir():
202 raise RepositoryError('The given path is a directory')
202 raise RepositoryError('The given path is a directory')
203 except CommitDoesNotExistError:
203 except CommitDoesNotExistError:
204 log.exception('No such commit exists for this repository')
204 log.exception('No such commit exists for this repository')
205 h.flash(_('No such commit exists for this repository'), category='error')
205 h.flash(_('No such commit exists for this repository'), category='error')
206 raise HTTPNotFound()
206 raise HTTPNotFound()
207 except RepositoryError as e:
207 except RepositoryError as e:
208 log.warning('Repository error while fetching filenode `%s`. Err:%s', path, e)
208 log.warning('Repository error while fetching filenode `%s`. Err:%s', path, e)
209 h.flash(safe_str(h.escape(e)), category='error')
209 h.flash(safe_str(h.escape(e)), category='error')
210 raise HTTPNotFound()
210 raise HTTPNotFound()
211
211
212 return file_node
212 return file_node
213
213
214 def _is_valid_head(self, commit_id, repo):
214 def _is_valid_head(self, commit_id, repo):
215 branch_name = sha_commit_id = ''
215 branch_name = sha_commit_id = ''
216 is_head = False
216 is_head = False
217 log.debug('Checking if commit_id `%s` is a head for %s.', commit_id, repo)
217 log.debug('Checking if commit_id `%s` is a head for %s.', commit_id, repo)
218
218
219 for _branch_name, branch_commit_id in repo.branches.items():
219 for _branch_name, branch_commit_id in repo.branches.items():
220 # simple case we pass in branch name, it's a HEAD
220 # simple case we pass in branch name, it's a HEAD
221 if commit_id == _branch_name:
221 if commit_id == _branch_name:
222 is_head = True
222 is_head = True
223 branch_name = _branch_name
223 branch_name = _branch_name
224 sha_commit_id = branch_commit_id
224 sha_commit_id = branch_commit_id
225 break
225 break
226 # case when we pass in full sha commit_id, which is a head
226 # case when we pass in full sha commit_id, which is a head
227 elif commit_id == branch_commit_id:
227 elif commit_id == branch_commit_id:
228 is_head = True
228 is_head = True
229 branch_name = _branch_name
229 branch_name = _branch_name
230 sha_commit_id = branch_commit_id
230 sha_commit_id = branch_commit_id
231 break
231 break
232
232
233 if h.is_svn(repo) and not repo.is_empty():
233 if h.is_svn(repo) and not repo.is_empty():
234 # Note: Subversion only has one head.
234 # Note: Subversion only has one head.
235 if commit_id == repo.get_commit(commit_idx=-1).raw_id:
235 if commit_id == repo.get_commit(commit_idx=-1).raw_id:
236 is_head = True
236 is_head = True
237 return branch_name, sha_commit_id, is_head
237 return branch_name, sha_commit_id, is_head
238
238
239 # checked branches, means we only need to try to get the branch/commit_sha
239 # checked branches, means we only need to try to get the branch/commit_sha
240 if not repo.is_empty():
240 if not repo.is_empty():
241 commit = repo.get_commit(commit_id=commit_id)
241 commit = repo.get_commit(commit_id=commit_id)
242 if commit:
242 if commit:
243 branch_name = commit.branch
243 branch_name = commit.branch
244 sha_commit_id = commit.raw_id
244 sha_commit_id = commit.raw_id
245
245
246 return branch_name, sha_commit_id, is_head
246 return branch_name, sha_commit_id, is_head
247
247
248 def _get_tree_at_commit(self, c, commit_id, f_path, full_load=False, at_rev=None):
248 def _get_tree_at_commit(self, c, commit_id, f_path, full_load=False, at_rev=None):
249
249
250 repo_id = self.db_repo.repo_id
250 repo_id = self.db_repo.repo_id
251 force_recache = self.get_recache_flag()
251 force_recache = self.get_recache_flag()
252
252
253 cache_seconds = safe_int(
253 cache_seconds = safe_int(
254 rhodecode.CONFIG.get('rc_cache.cache_repo.expiration_time'))
254 rhodecode.CONFIG.get('rc_cache.cache_repo.expiration_time'))
255 cache_on = not force_recache and cache_seconds > 0
255 cache_on = not force_recache and cache_seconds > 0
256 log.debug(
256 log.debug(
257 'Computing FILE TREE for repo_id %s commit_id `%s` and path `%s`'
257 'Computing FILE TREE for repo_id %s commit_id `%s` and path `%s`'
258 'with caching: %s[TTL: %ss]' % (
258 'with caching: %s[TTL: %ss]' % (
259 repo_id, commit_id, f_path, cache_on, cache_seconds or 0))
259 repo_id, commit_id, f_path, cache_on, cache_seconds or 0))
260
260
261 cache_namespace_uid = 'cache_repo.{}'.format(repo_id)
261 cache_namespace_uid = 'cache_repo.{}'.format(repo_id)
262 region = rc_cache.get_or_create_region('cache_repo', cache_namespace_uid)
262 region = rc_cache.get_or_create_region('cache_repo', cache_namespace_uid)
263
263
264 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid, condition=cache_on)
264 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid, condition=cache_on)
265 def compute_file_tree(ver, _name_hash, _repo_id, _commit_id, _f_path, _full_load, _at_rev):
265 def compute_file_tree(ver, _name_hash, _repo_id, _commit_id, _f_path, _full_load, _at_rev):
266 log.debug('Generating cached file tree at ver:%s for repo_id: %s, %s, %s',
266 log.debug('Generating cached file tree at ver:%s for repo_id: %s, %s, %s',
267 ver, _repo_id, _commit_id, _f_path)
267 ver, _repo_id, _commit_id, _f_path)
268
268
269 c.full_load = _full_load
269 c.full_load = _full_load
270 return render(
270 return render(
271 'rhodecode:templates/files/files_browser_tree.mako',
271 'rhodecode:templates/files/files_browser_tree.mako',
272 self._get_template_context(c), self.request, _at_rev)
272 self._get_template_context(c), self.request, _at_rev)
273
273
274 return compute_file_tree(
274 return compute_file_tree(
275 rc_cache.FILE_TREE_CACHE_VER, self.db_repo.repo_name_hash,
275 rc_cache.FILE_TREE_CACHE_VER, self.db_repo.repo_name_hash,
276 self.db_repo.repo_id, commit_id, f_path, full_load, at_rev)
276 self.db_repo.repo_id, commit_id, f_path, full_load, at_rev)
277
277
278 def _get_archive_spec(self, fname):
278 def _get_archive_spec(self, fname):
279 log.debug('Detecting archive spec for: `%s`', fname)
279 log.debug('Detecting archive spec for: `%s`', fname)
280
280
281 fileformat = None
281 fileformat = None
282 ext = None
282 ext = None
283 content_type = None
283 content_type = None
284 for a_type, content_type, extension in settings.ARCHIVE_SPECS:
284 for a_type, content_type, extension in settings.ARCHIVE_SPECS:
285
285
286 if fname.endswith(extension):
286 if fname.endswith(extension):
287 fileformat = a_type
287 fileformat = a_type
288 log.debug('archive is of type: %s', fileformat)
288 log.debug('archive is of type: %s', fileformat)
289 ext = extension
289 ext = extension
290 break
290 break
291
291
292 if not fileformat:
292 if not fileformat:
293 raise ValueError()
293 raise ValueError()
294
294
295 # left over part of whole fname is the commit
295 # left over part of whole fname is the commit
296 commit_id = fname[:-len(ext)]
296 commit_id = fname[:-len(ext)]
297
297
298 return commit_id, ext, fileformat, content_type
298 return commit_id, ext, fileformat, content_type
299
299
300 def create_pure_path(self, *parts):
300 def create_pure_path(self, *parts):
301 # Split paths and sanitize them, removing any ../ etc
301 # Split paths and sanitize them, removing any ../ etc
302 sanitized_path = [
302 sanitized_path = [
303 x for x in pathlib2.PurePath(*parts).parts
303 x for x in pathlib2.PurePath(*parts).parts
304 if x not in ['.', '..']]
304 if x not in ['.', '..']]
305
305
306 pure_path = pathlib2.PurePath(*sanitized_path)
306 pure_path = pathlib2.PurePath(*sanitized_path)
307 return pure_path
307 return pure_path
308
308
309 def _is_lf_enabled(self, target_repo):
309 def _is_lf_enabled(self, target_repo):
310 lf_enabled = False
310 lf_enabled = False
311
311
312 lf_key_for_vcs_map = {
312 lf_key_for_vcs_map = {
313 'hg': 'extensions_largefiles',
313 'hg': 'extensions_largefiles',
314 'git': 'vcs_git_lfs_enabled'
314 'git': 'vcs_git_lfs_enabled'
315 }
315 }
316
316
317 lf_key_for_vcs = lf_key_for_vcs_map.get(target_repo.repo_type)
317 lf_key_for_vcs = lf_key_for_vcs_map.get(target_repo.repo_type)
318
318
319 if lf_key_for_vcs:
319 if lf_key_for_vcs:
320 lf_enabled = self._get_repo_setting(target_repo, lf_key_for_vcs)
320 lf_enabled = self._get_repo_setting(target_repo, lf_key_for_vcs)
321
321
322 return lf_enabled
322 return lf_enabled
323
323
324 @LoginRequired()
324 @LoginRequired()
325 @HasRepoPermissionAnyDecorator(
325 @HasRepoPermissionAnyDecorator(
326 'repository.read', 'repository.write', 'repository.admin')
326 'repository.read', 'repository.write', 'repository.admin')
327 @view_config(
327 @view_config(
328 route_name='repo_archivefile', request_method='GET',
328 route_name='repo_archivefile', request_method='GET',
329 renderer=None)
329 renderer=None)
330 def repo_archivefile(self):
330 def repo_archivefile(self):
331 # archive cache config
331 # archive cache config
332 from rhodecode import CONFIG
332 from rhodecode import CONFIG
333 _ = self.request.translate
333 _ = self.request.translate
334 self.load_default_context()
334 self.load_default_context()
335 default_at_path = '/'
335 default_at_path = '/'
336 fname = self.request.matchdict['fname']
336 fname = self.request.matchdict['fname']
337 subrepos = self.request.GET.get('subrepos') == 'true'
337 subrepos = self.request.GET.get('subrepos') == 'true'
338 at_path = self.request.GET.get('at_path') or default_at_path
338 at_path = self.request.GET.get('at_path') or default_at_path
339
339
340 if not self.db_repo.enable_downloads:
340 if not self.db_repo.enable_downloads:
341 return Response(_('Downloads disabled'))
341 return Response(_('Downloads disabled'))
342
342
343 try:
343 try:
344 commit_id, ext, fileformat, content_type = \
344 commit_id, ext, fileformat, content_type = \
345 self._get_archive_spec(fname)
345 self._get_archive_spec(fname)
346 except ValueError:
346 except ValueError:
347 return Response(_('Unknown archive type for: `{}`').format(
347 return Response(_('Unknown archive type for: `{}`').format(
348 h.escape(fname)))
348 h.escape(fname)))
349
349
350 try:
350 try:
351 commit = self.rhodecode_vcs_repo.get_commit(commit_id)
351 commit = self.rhodecode_vcs_repo.get_commit(commit_id)
352 except CommitDoesNotExistError:
352 except CommitDoesNotExistError:
353 return Response(_('Unknown commit_id {}').format(
353 return Response(_('Unknown commit_id {}').format(
354 h.escape(commit_id)))
354 h.escape(commit_id)))
355 except EmptyRepositoryError:
355 except EmptyRepositoryError:
356 return Response(_('Empty repository'))
356 return Response(_('Empty repository'))
357
357
358 try:
358 try:
359 at_path = commit.get_node(at_path).path or default_at_path
359 at_path = commit.get_node(at_path).path or default_at_path
360 except Exception:
360 except Exception:
361 return Response(_('No node at path {} for this repository').format(at_path))
361 return Response(_('No node at path {} for this repository').format(at_path))
362
362
363 path_sha = sha1(at_path)[:8]
363 path_sha = sha1(at_path)[:8]
364
364
365 # original backward compat name of archive
365 # original backward compat name of archive
366 clean_name = safe_str(self.db_repo_name.replace('/', '_'))
366 clean_name = safe_str(self.db_repo_name.replace('/', '_'))
367 short_sha = safe_str(commit.short_id)
367 short_sha = safe_str(commit.short_id)
368
368
369 if at_path == default_at_path:
369 if at_path == default_at_path:
370 archive_name = '{}-{}{}{}'.format(
370 archive_name = '{}-{}{}{}'.format(
371 clean_name,
371 clean_name,
372 '-sub' if subrepos else '',
372 '-sub' if subrepos else '',
373 short_sha,
373 short_sha,
374 ext)
374 ext)
375 # custom path and new name
375 # custom path and new name
376 else:
376 else:
377 archive_name = '{}-{}{}-{}{}'.format(
377 archive_name = '{}-{}{}-{}{}'.format(
378 clean_name,
378 clean_name,
379 '-sub' if subrepos else '',
379 '-sub' if subrepos else '',
380 short_sha,
380 short_sha,
381 path_sha,
381 path_sha,
382 ext)
382 ext)
383
383
384 use_cached_archive = False
384 use_cached_archive = False
385 archive_cache_enabled = CONFIG.get(
385 archive_cache_enabled = CONFIG.get(
386 'archive_cache_dir') and not self.request.GET.get('no_cache')
386 'archive_cache_dir') and not self.request.GET.get('no_cache')
387 cached_archive_path = None
387 cached_archive_path = None
388
388
389 if archive_cache_enabled:
389 if archive_cache_enabled:
390 # check if we it's ok to write
390 # check if we it's ok to write
391 if not os.path.isdir(CONFIG['archive_cache_dir']):
391 if not os.path.isdir(CONFIG['archive_cache_dir']):
392 os.makedirs(CONFIG['archive_cache_dir'])
392 os.makedirs(CONFIG['archive_cache_dir'])
393 cached_archive_path = os.path.join(
393 cached_archive_path = os.path.join(
394 CONFIG['archive_cache_dir'], archive_name)
394 CONFIG['archive_cache_dir'], archive_name)
395 if os.path.isfile(cached_archive_path):
395 if os.path.isfile(cached_archive_path):
396 log.debug('Found cached archive in %s', cached_archive_path)
396 log.debug('Found cached archive in %s', cached_archive_path)
397 fd, archive = None, cached_archive_path
397 fd, archive = None, cached_archive_path
398 use_cached_archive = True
398 use_cached_archive = True
399 else:
399 else:
400 log.debug('Archive %s is not yet cached', archive_name)
400 log.debug('Archive %s is not yet cached', archive_name)
401
401
402 if not use_cached_archive:
402 if not use_cached_archive:
403 # generate new archive
403 # generate new archive
404 fd, archive = tempfile.mkstemp()
404 fd, archive = tempfile.mkstemp()
405 log.debug('Creating new temp archive in %s', archive)
405 log.debug('Creating new temp archive in %s', archive)
406 try:
406 try:
407 commit.archive_repo(archive, kind=fileformat, subrepos=subrepos,
407 commit.archive_repo(archive, kind=fileformat, subrepos=subrepos,
408 archive_at_path=at_path)
408 archive_at_path=at_path)
409 except ImproperArchiveTypeError:
409 except ImproperArchiveTypeError:
410 return _('Unknown archive type')
410 return _('Unknown archive type')
411 if archive_cache_enabled:
411 if archive_cache_enabled:
412 # if we generated the archive and we have cache enabled
412 # if we generated the archive and we have cache enabled
413 # let's use this for future
413 # let's use this for future
414 log.debug('Storing new archive in %s', cached_archive_path)
414 log.debug('Storing new archive in %s', cached_archive_path)
415 shutil.move(archive, cached_archive_path)
415 shutil.move(archive, cached_archive_path)
416 archive = cached_archive_path
416 archive = cached_archive_path
417
417
418 # store download action
418 # store download action
419 audit_logger.store_web(
419 audit_logger.store_web(
420 'repo.archive.download', action_data={
420 'repo.archive.download', action_data={
421 'user_agent': self.request.user_agent,
421 'user_agent': self.request.user_agent,
422 'archive_name': archive_name,
422 'archive_name': archive_name,
423 'archive_spec': fname,
423 'archive_spec': fname,
424 'archive_cached': use_cached_archive},
424 'archive_cached': use_cached_archive},
425 user=self._rhodecode_user,
425 user=self._rhodecode_user,
426 repo=self.db_repo,
426 repo=self.db_repo,
427 commit=True
427 commit=True
428 )
428 )
429
429
430 def get_chunked_archive(archive_path):
430 def get_chunked_archive(archive_path):
431 with open(archive_path, 'rb') as stream:
431 with open(archive_path, 'rb') as stream:
432 while True:
432 while True:
433 data = stream.read(16 * 1024)
433 data = stream.read(16 * 1024)
434 if not data:
434 if not data:
435 if fd: # fd means we used temporary file
435 if fd: # fd means we used temporary file
436 os.close(fd)
436 os.close(fd)
437 if not archive_cache_enabled:
437 if not archive_cache_enabled:
438 log.debug('Destroying temp archive %s', archive_path)
438 log.debug('Destroying temp archive %s', archive_path)
439 os.remove(archive_path)
439 os.remove(archive_path)
440 break
440 break
441 yield data
441 yield data
442
442
443 response = Response(app_iter=get_chunked_archive(archive))
443 response = Response(app_iter=get_chunked_archive(archive))
444 response.content_disposition = str(
444 response.content_disposition = str(
445 'attachment; filename=%s' % archive_name)
445 'attachment; filename=%s' % archive_name)
446 response.content_type = str(content_type)
446 response.content_type = str(content_type)
447
447
448 return response
448 return response
449
449
450 def _get_file_node(self, commit_id, f_path):
450 def _get_file_node(self, commit_id, f_path):
451 if commit_id not in ['', None, 'None', '0' * 12, '0' * 40]:
451 if commit_id not in ['', None, 'None', '0' * 12, '0' * 40]:
452 commit = self.rhodecode_vcs_repo.get_commit(commit_id=commit_id)
452 commit = self.rhodecode_vcs_repo.get_commit(commit_id=commit_id)
453 try:
453 try:
454 node = commit.get_node(f_path)
454 node = commit.get_node(f_path)
455 if node.is_dir():
455 if node.is_dir():
456 raise NodeError('%s path is a %s not a file'
456 raise NodeError('%s path is a %s not a file'
457 % (node, type(node)))
457 % (node, type(node)))
458 except NodeDoesNotExistError:
458 except NodeDoesNotExistError:
459 commit = EmptyCommit(
459 commit = EmptyCommit(
460 commit_id=commit_id,
460 commit_id=commit_id,
461 idx=commit.idx,
461 idx=commit.idx,
462 repo=commit.repository,
462 repo=commit.repository,
463 alias=commit.repository.alias,
463 alias=commit.repository.alias,
464 message=commit.message,
464 message=commit.message,
465 author=commit.author,
465 author=commit.author,
466 date=commit.date)
466 date=commit.date)
467 node = FileNode(f_path, '', commit=commit)
467 node = FileNode(f_path, '', commit=commit)
468 else:
468 else:
469 commit = EmptyCommit(
469 commit = EmptyCommit(
470 repo=self.rhodecode_vcs_repo,
470 repo=self.rhodecode_vcs_repo,
471 alias=self.rhodecode_vcs_repo.alias)
471 alias=self.rhodecode_vcs_repo.alias)
472 node = FileNode(f_path, '', commit=commit)
472 node = FileNode(f_path, '', commit=commit)
473 return node
473 return node
474
474
475 @LoginRequired()
475 @LoginRequired()
476 @HasRepoPermissionAnyDecorator(
476 @HasRepoPermissionAnyDecorator(
477 'repository.read', 'repository.write', 'repository.admin')
477 'repository.read', 'repository.write', 'repository.admin')
478 @view_config(
478 @view_config(
479 route_name='repo_files_diff', request_method='GET',
479 route_name='repo_files_diff', request_method='GET',
480 renderer=None)
480 renderer=None)
481 def repo_files_diff(self):
481 def repo_files_diff(self):
482 c = self.load_default_context()
482 c = self.load_default_context()
483 f_path = self._get_f_path(self.request.matchdict)
483 f_path = self._get_f_path(self.request.matchdict)
484 diff1 = self.request.GET.get('diff1', '')
484 diff1 = self.request.GET.get('diff1', '')
485 diff2 = self.request.GET.get('diff2', '')
485 diff2 = self.request.GET.get('diff2', '')
486
486
487 path1, diff1 = parse_path_ref(diff1, default_path=f_path)
487 path1, diff1 = parse_path_ref(diff1, default_path=f_path)
488
488
489 ignore_whitespace = str2bool(self.request.GET.get('ignorews'))
489 ignore_whitespace = str2bool(self.request.GET.get('ignorews'))
490 line_context = self.request.GET.get('context', 3)
490 line_context = self.request.GET.get('context', 3)
491
491
492 if not any((diff1, diff2)):
492 if not any((diff1, diff2)):
493 h.flash(
493 h.flash(
494 'Need query parameter "diff1" or "diff2" to generate a diff.',
494 'Need query parameter "diff1" or "diff2" to generate a diff.',
495 category='error')
495 category='error')
496 raise HTTPBadRequest()
496 raise HTTPBadRequest()
497
497
498 c.action = self.request.GET.get('diff')
498 c.action = self.request.GET.get('diff')
499 if c.action not in ['download', 'raw']:
499 if c.action not in ['download', 'raw']:
500 compare_url = h.route_path(
500 compare_url = h.route_path(
501 'repo_compare',
501 'repo_compare',
502 repo_name=self.db_repo_name,
502 repo_name=self.db_repo_name,
503 source_ref_type='rev',
503 source_ref_type='rev',
504 source_ref=diff1,
504 source_ref=diff1,
505 target_repo=self.db_repo_name,
505 target_repo=self.db_repo_name,
506 target_ref_type='rev',
506 target_ref_type='rev',
507 target_ref=diff2,
507 target_ref=diff2,
508 _query=dict(f_path=f_path))
508 _query=dict(f_path=f_path))
509 # redirect to new view if we render diff
509 # redirect to new view if we render diff
510 raise HTTPFound(compare_url)
510 raise HTTPFound(compare_url)
511
511
512 try:
512 try:
513 node1 = self._get_file_node(diff1, path1)
513 node1 = self._get_file_node(diff1, path1)
514 node2 = self._get_file_node(diff2, f_path)
514 node2 = self._get_file_node(diff2, f_path)
515 except (RepositoryError, NodeError):
515 except (RepositoryError, NodeError):
516 log.exception("Exception while trying to get node from repository")
516 log.exception("Exception while trying to get node from repository")
517 raise HTTPFound(
517 raise HTTPFound(
518 h.route_path('repo_files', repo_name=self.db_repo_name,
518 h.route_path('repo_files', repo_name=self.db_repo_name,
519 commit_id='tip', f_path=f_path))
519 commit_id='tip', f_path=f_path))
520
520
521 if all(isinstance(node.commit, EmptyCommit)
521 if all(isinstance(node.commit, EmptyCommit)
522 for node in (node1, node2)):
522 for node in (node1, node2)):
523 raise HTTPNotFound()
523 raise HTTPNotFound()
524
524
525 c.commit_1 = node1.commit
525 c.commit_1 = node1.commit
526 c.commit_2 = node2.commit
526 c.commit_2 = node2.commit
527
527
528 if c.action == 'download':
528 if c.action == 'download':
529 _diff = diffs.get_gitdiff(node1, node2,
529 _diff = diffs.get_gitdiff(node1, node2,
530 ignore_whitespace=ignore_whitespace,
530 ignore_whitespace=ignore_whitespace,
531 context=line_context)
531 context=line_context)
532 diff = diffs.DiffProcessor(_diff, format='gitdiff')
532 diff = diffs.DiffProcessor(_diff, format='gitdiff')
533
533
534 response = Response(self.path_filter.get_raw_patch(diff))
534 response = Response(self.path_filter.get_raw_patch(diff))
535 response.content_type = 'text/plain'
535 response.content_type = 'text/plain'
536 response.content_disposition = (
536 response.content_disposition = (
537 'attachment; filename=%s_%s_vs_%s.diff' % (f_path, diff1, diff2)
537 'attachment; filename=%s_%s_vs_%s.diff' % (f_path, diff1, diff2)
538 )
538 )
539 charset = self._get_default_encoding(c)
539 charset = self._get_default_encoding(c)
540 if charset:
540 if charset:
541 response.charset = charset
541 response.charset = charset
542 return response
542 return response
543
543
544 elif c.action == 'raw':
544 elif c.action == 'raw':
545 _diff = diffs.get_gitdiff(node1, node2,
545 _diff = diffs.get_gitdiff(node1, node2,
546 ignore_whitespace=ignore_whitespace,
546 ignore_whitespace=ignore_whitespace,
547 context=line_context)
547 context=line_context)
548 diff = diffs.DiffProcessor(_diff, format='gitdiff')
548 diff = diffs.DiffProcessor(_diff, format='gitdiff')
549
549
550 response = Response(self.path_filter.get_raw_patch(diff))
550 response = Response(self.path_filter.get_raw_patch(diff))
551 response.content_type = 'text/plain'
551 response.content_type = 'text/plain'
552 charset = self._get_default_encoding(c)
552 charset = self._get_default_encoding(c)
553 if charset:
553 if charset:
554 response.charset = charset
554 response.charset = charset
555 return response
555 return response
556
556
557 # in case we ever end up here
557 # in case we ever end up here
558 raise HTTPNotFound()
558 raise HTTPNotFound()
559
559
560 @LoginRequired()
560 @LoginRequired()
561 @HasRepoPermissionAnyDecorator(
561 @HasRepoPermissionAnyDecorator(
562 'repository.read', 'repository.write', 'repository.admin')
562 'repository.read', 'repository.write', 'repository.admin')
563 @view_config(
563 @view_config(
564 route_name='repo_files_diff_2way_redirect', request_method='GET',
564 route_name='repo_files_diff_2way_redirect', request_method='GET',
565 renderer=None)
565 renderer=None)
566 def repo_files_diff_2way_redirect(self):
566 def repo_files_diff_2way_redirect(self):
567 """
567 """
568 Kept only to make OLD links work
568 Kept only to make OLD links work
569 """
569 """
570 f_path = self._get_f_path_unchecked(self.request.matchdict)
570 f_path = self._get_f_path_unchecked(self.request.matchdict)
571 diff1 = self.request.GET.get('diff1', '')
571 diff1 = self.request.GET.get('diff1', '')
572 diff2 = self.request.GET.get('diff2', '')
572 diff2 = self.request.GET.get('diff2', '')
573
573
574 if not any((diff1, diff2)):
574 if not any((diff1, diff2)):
575 h.flash(
575 h.flash(
576 'Need query parameter "diff1" or "diff2" to generate a diff.',
576 'Need query parameter "diff1" or "diff2" to generate a diff.',
577 category='error')
577 category='error')
578 raise HTTPBadRequest()
578 raise HTTPBadRequest()
579
579
580 compare_url = h.route_path(
580 compare_url = h.route_path(
581 'repo_compare',
581 'repo_compare',
582 repo_name=self.db_repo_name,
582 repo_name=self.db_repo_name,
583 source_ref_type='rev',
583 source_ref_type='rev',
584 source_ref=diff1,
584 source_ref=diff1,
585 target_ref_type='rev',
585 target_ref_type='rev',
586 target_ref=diff2,
586 target_ref=diff2,
587 _query=dict(f_path=f_path, diffmode='sideside',
587 _query=dict(f_path=f_path, diffmode='sideside',
588 target_repo=self.db_repo_name,))
588 target_repo=self.db_repo_name,))
589 raise HTTPFound(compare_url)
589 raise HTTPFound(compare_url)
590
590
591 @LoginRequired()
591 @LoginRequired()
592 @view_config(
592 @view_config(
593 route_name='repo_files:default_commit', request_method='GET',
593 route_name='repo_files:default_commit', request_method='GET',
594 renderer=None)
594 renderer=None)
595 def repo_files_default(self):
595 def repo_files_default(self):
596 c = self.load_default_context()
596 c = self.load_default_context()
597
597 ref_name = c.rhodecode_db_repo.landing_ref_name
598 landing_url = h.route_path(
598 landing_url = h.repo_files_by_ref_url(
599 'repo_files', repo_name=c.repo_name,
599 c.rhodecode_db_repo.repo_name,
600 commit_id=c.rhodecode_db_repo.landing_ref_name, f_path='',
600 c.rhodecode_db_repo.repo_type,
601 _query={'at': c.rhodecode_db_repo.landing_ref_name})
601 f_path='',
602 ref_name=ref_name,
603 commit_id='tip',
604 query=dict(at=ref_name)
605 )
602
606
603 raise HTTPFound(landing_url)
607 raise HTTPFound(landing_url)
604
608
605 @LoginRequired()
609 @LoginRequired()
606 @HasRepoPermissionAnyDecorator(
610 @HasRepoPermissionAnyDecorator(
607 'repository.read', 'repository.write', 'repository.admin')
611 'repository.read', 'repository.write', 'repository.admin')
608 @view_config(
612 @view_config(
609 route_name='repo_files', request_method='GET',
613 route_name='repo_files', request_method='GET',
610 renderer=None)
614 renderer=None)
611 @view_config(
615 @view_config(
612 route_name='repo_files:default_path', request_method='GET',
616 route_name='repo_files:default_path', request_method='GET',
613 renderer=None)
617 renderer=None)
614 @view_config(
618 @view_config(
615 route_name='repo_files:rendered', request_method='GET',
619 route_name='repo_files:rendered', request_method='GET',
616 renderer=None)
620 renderer=None)
617 @view_config(
621 @view_config(
618 route_name='repo_files:annotated', request_method='GET',
622 route_name='repo_files:annotated', request_method='GET',
619 renderer=None)
623 renderer=None)
620 def repo_files(self):
624 def repo_files(self):
621 c = self.load_default_context()
625 c = self.load_default_context()
622
626
623 view_name = getattr(self.request.matched_route, 'name', None)
627 view_name = getattr(self.request.matched_route, 'name', None)
624
628
625 c.annotate = view_name == 'repo_files:annotated'
629 c.annotate = view_name == 'repo_files:annotated'
626 # default is false, but .rst/.md files later are auto rendered, we can
630 # default is false, but .rst/.md files later are auto rendered, we can
627 # overwrite auto rendering by setting this GET flag
631 # overwrite auto rendering by setting this GET flag
628 c.renderer = view_name == 'repo_files:rendered' or \
632 c.renderer = view_name == 'repo_files:rendered' or \
629 not self.request.GET.get('no-render', False)
633 not self.request.GET.get('no-render', False)
630
634
631 commit_id, f_path = self._get_commit_and_path()
635 commit_id, f_path = self._get_commit_and_path()
632
636
633 c.commit = self._get_commit_or_redirect(commit_id)
637 c.commit = self._get_commit_or_redirect(commit_id)
634 c.branch = self.request.GET.get('branch', None)
638 c.branch = self.request.GET.get('branch', None)
635 c.f_path = f_path
639 c.f_path = f_path
636 at_rev = self.request.GET.get('at')
640 at_rev = self.request.GET.get('at')
637
641
638 # prev link
642 # prev link
639 try:
643 try:
640 prev_commit = c.commit.prev(c.branch)
644 prev_commit = c.commit.prev(c.branch)
641 c.prev_commit = prev_commit
645 c.prev_commit = prev_commit
642 c.url_prev = h.route_path(
646 c.url_prev = h.route_path(
643 'repo_files', repo_name=self.db_repo_name,
647 'repo_files', repo_name=self.db_repo_name,
644 commit_id=prev_commit.raw_id, f_path=f_path)
648 commit_id=prev_commit.raw_id, f_path=f_path)
645 if c.branch:
649 if c.branch:
646 c.url_prev += '?branch=%s' % c.branch
650 c.url_prev += '?branch=%s' % c.branch
647 except (CommitDoesNotExistError, VCSError):
651 except (CommitDoesNotExistError, VCSError):
648 c.url_prev = '#'
652 c.url_prev = '#'
649 c.prev_commit = EmptyCommit()
653 c.prev_commit = EmptyCommit()
650
654
651 # next link
655 # next link
652 try:
656 try:
653 next_commit = c.commit.next(c.branch)
657 next_commit = c.commit.next(c.branch)
654 c.next_commit = next_commit
658 c.next_commit = next_commit
655 c.url_next = h.route_path(
659 c.url_next = h.route_path(
656 'repo_files', repo_name=self.db_repo_name,
660 'repo_files', repo_name=self.db_repo_name,
657 commit_id=next_commit.raw_id, f_path=f_path)
661 commit_id=next_commit.raw_id, f_path=f_path)
658 if c.branch:
662 if c.branch:
659 c.url_next += '?branch=%s' % c.branch
663 c.url_next += '?branch=%s' % c.branch
660 except (CommitDoesNotExistError, VCSError):
664 except (CommitDoesNotExistError, VCSError):
661 c.url_next = '#'
665 c.url_next = '#'
662 c.next_commit = EmptyCommit()
666 c.next_commit = EmptyCommit()
663
667
664 # files or dirs
668 # files or dirs
665 try:
669 try:
666 c.file = c.commit.get_node(f_path)
670 c.file = c.commit.get_node(f_path)
667 c.file_author = True
671 c.file_author = True
668 c.file_tree = ''
672 c.file_tree = ''
669
673
670 # load file content
674 # load file content
671 if c.file.is_file():
675 if c.file.is_file():
672 c.lf_node = {}
676 c.lf_node = {}
673
677
674 has_lf_enabled = self._is_lf_enabled(self.db_repo)
678 has_lf_enabled = self._is_lf_enabled(self.db_repo)
675 if has_lf_enabled:
679 if has_lf_enabled:
676 c.lf_node = c.file.get_largefile_node()
680 c.lf_node = c.file.get_largefile_node()
677
681
678 c.file_source_page = 'true'
682 c.file_source_page = 'true'
679 c.file_last_commit = c.file.last_commit
683 c.file_last_commit = c.file.last_commit
680
684
681 c.file_size_too_big = c.file.size > c.visual.cut_off_limit_file
685 c.file_size_too_big = c.file.size > c.visual.cut_off_limit_file
682
686
683 if not (c.file_size_too_big or c.file.is_binary):
687 if not (c.file_size_too_big or c.file.is_binary):
684 if c.annotate: # annotation has precedence over renderer
688 if c.annotate: # annotation has precedence over renderer
685 c.annotated_lines = filenode_as_annotated_lines_tokens(
689 c.annotated_lines = filenode_as_annotated_lines_tokens(
686 c.file
690 c.file
687 )
691 )
688 else:
692 else:
689 c.renderer = (
693 c.renderer = (
690 c.renderer and h.renderer_from_filename(c.file.path)
694 c.renderer and h.renderer_from_filename(c.file.path)
691 )
695 )
692 if not c.renderer:
696 if not c.renderer:
693 c.lines = filenode_as_lines_tokens(c.file)
697 c.lines = filenode_as_lines_tokens(c.file)
694
698
695 _branch_name, _sha_commit_id, is_head = self._is_valid_head(
699 _branch_name, _sha_commit_id, is_head = self._is_valid_head(
696 commit_id, self.rhodecode_vcs_repo)
700 commit_id, self.rhodecode_vcs_repo)
697 c.on_branch_head = is_head
701 c.on_branch_head = is_head
698
702
699 branch = c.commit.branch if (
703 branch = c.commit.branch if (
700 c.commit.branch and '/' not in c.commit.branch) else None
704 c.commit.branch and '/' not in c.commit.branch) else None
701 c.branch_or_raw_id = branch or c.commit.raw_id
705 c.branch_or_raw_id = branch or c.commit.raw_id
702 c.branch_name = c.commit.branch or h.short_id(c.commit.raw_id)
706 c.branch_name = c.commit.branch or h.short_id(c.commit.raw_id)
703
707
704 author = c.file_last_commit.author
708 author = c.file_last_commit.author
705 c.authors = [[
709 c.authors = [[
706 h.email(author),
710 h.email(author),
707 h.person(author, 'username_or_name_or_email'),
711 h.person(author, 'username_or_name_or_email'),
708 1
712 1
709 ]]
713 ]]
710
714
711 else: # load tree content at path
715 else: # load tree content at path
712 c.file_source_page = 'false'
716 c.file_source_page = 'false'
713 c.authors = []
717 c.authors = []
714 # this loads a simple tree without metadata to speed things up
718 # this loads a simple tree without metadata to speed things up
715 # later via ajax we call repo_nodetree_full and fetch whole
719 # later via ajax we call repo_nodetree_full and fetch whole
716 c.file_tree = self._get_tree_at_commit(c, c.commit.raw_id, f_path, at_rev=at_rev)
720 c.file_tree = self._get_tree_at_commit(c, c.commit.raw_id, f_path, at_rev=at_rev)
717
721
718 c.readme_data, c.readme_file = \
722 c.readme_data, c.readme_file = \
719 self._get_readme_data(self.db_repo, c.visual.default_renderer,
723 self._get_readme_data(self.db_repo, c.visual.default_renderer,
720 c.commit.raw_id, f_path)
724 c.commit.raw_id, f_path)
721
725
722 except RepositoryError as e:
726 except RepositoryError as e:
723 h.flash(safe_str(h.escape(e)), category='error')
727 h.flash(safe_str(h.escape(e)), category='error')
724 raise HTTPNotFound()
728 raise HTTPNotFound()
725
729
726 if self.request.environ.get('HTTP_X_PJAX'):
730 if self.request.environ.get('HTTP_X_PJAX'):
727 html = render('rhodecode:templates/files/files_pjax.mako',
731 html = render('rhodecode:templates/files/files_pjax.mako',
728 self._get_template_context(c), self.request)
732 self._get_template_context(c), self.request)
729 else:
733 else:
730 html = render('rhodecode:templates/files/files.mako',
734 html = render('rhodecode:templates/files/files.mako',
731 self._get_template_context(c), self.request)
735 self._get_template_context(c), self.request)
732 return Response(html)
736 return Response(html)
733
737
734 @HasRepoPermissionAnyDecorator(
738 @HasRepoPermissionAnyDecorator(
735 'repository.read', 'repository.write', 'repository.admin')
739 'repository.read', 'repository.write', 'repository.admin')
736 @view_config(
740 @view_config(
737 route_name='repo_files:annotated_previous', request_method='GET',
741 route_name='repo_files:annotated_previous', request_method='GET',
738 renderer=None)
742 renderer=None)
739 def repo_files_annotated_previous(self):
743 def repo_files_annotated_previous(self):
740 self.load_default_context()
744 self.load_default_context()
741
745
742 commit_id, f_path = self._get_commit_and_path()
746 commit_id, f_path = self._get_commit_and_path()
743 commit = self._get_commit_or_redirect(commit_id)
747 commit = self._get_commit_or_redirect(commit_id)
744 prev_commit_id = commit.raw_id
748 prev_commit_id = commit.raw_id
745 line_anchor = self.request.GET.get('line_anchor')
749 line_anchor = self.request.GET.get('line_anchor')
746 is_file = False
750 is_file = False
747 try:
751 try:
748 _file = commit.get_node(f_path)
752 _file = commit.get_node(f_path)
749 is_file = _file.is_file()
753 is_file = _file.is_file()
750 except (NodeDoesNotExistError, CommitDoesNotExistError, VCSError):
754 except (NodeDoesNotExistError, CommitDoesNotExistError, VCSError):
751 pass
755 pass
752
756
753 if is_file:
757 if is_file:
754 history = commit.get_path_history(f_path)
758 history = commit.get_path_history(f_path)
755 prev_commit_id = history[1].raw_id \
759 prev_commit_id = history[1].raw_id \
756 if len(history) > 1 else prev_commit_id
760 if len(history) > 1 else prev_commit_id
757 prev_url = h.route_path(
761 prev_url = h.route_path(
758 'repo_files:annotated', repo_name=self.db_repo_name,
762 'repo_files:annotated', repo_name=self.db_repo_name,
759 commit_id=prev_commit_id, f_path=f_path,
763 commit_id=prev_commit_id, f_path=f_path,
760 _anchor='L{}'.format(line_anchor))
764 _anchor='L{}'.format(line_anchor))
761
765
762 raise HTTPFound(prev_url)
766 raise HTTPFound(prev_url)
763
767
764 @LoginRequired()
768 @LoginRequired()
765 @HasRepoPermissionAnyDecorator(
769 @HasRepoPermissionAnyDecorator(
766 'repository.read', 'repository.write', 'repository.admin')
770 'repository.read', 'repository.write', 'repository.admin')
767 @view_config(
771 @view_config(
768 route_name='repo_nodetree_full', request_method='GET',
772 route_name='repo_nodetree_full', request_method='GET',
769 renderer=None, xhr=True)
773 renderer=None, xhr=True)
770 @view_config(
774 @view_config(
771 route_name='repo_nodetree_full:default_path', request_method='GET',
775 route_name='repo_nodetree_full:default_path', request_method='GET',
772 renderer=None, xhr=True)
776 renderer=None, xhr=True)
773 def repo_nodetree_full(self):
777 def repo_nodetree_full(self):
774 """
778 """
775 Returns rendered html of file tree that contains commit date,
779 Returns rendered html of file tree that contains commit date,
776 author, commit_id for the specified combination of
780 author, commit_id for the specified combination of
777 repo, commit_id and file path
781 repo, commit_id and file path
778 """
782 """
779 c = self.load_default_context()
783 c = self.load_default_context()
780
784
781 commit_id, f_path = self._get_commit_and_path()
785 commit_id, f_path = self._get_commit_and_path()
782 commit = self._get_commit_or_redirect(commit_id)
786 commit = self._get_commit_or_redirect(commit_id)
783 try:
787 try:
784 dir_node = commit.get_node(f_path)
788 dir_node = commit.get_node(f_path)
785 except RepositoryError as e:
789 except RepositoryError as e:
786 return Response('error: {}'.format(h.escape(safe_str(e))))
790 return Response('error: {}'.format(h.escape(safe_str(e))))
787
791
788 if dir_node.is_file():
792 if dir_node.is_file():
789 return Response('')
793 return Response('')
790
794
791 c.file = dir_node
795 c.file = dir_node
792 c.commit = commit
796 c.commit = commit
793 at_rev = self.request.GET.get('at')
797 at_rev = self.request.GET.get('at')
794
798
795 html = self._get_tree_at_commit(
799 html = self._get_tree_at_commit(
796 c, commit.raw_id, dir_node.path, full_load=True, at_rev=at_rev)
800 c, commit.raw_id, dir_node.path, full_load=True, at_rev=at_rev)
797
801
798 return Response(html)
802 return Response(html)
799
803
800 def _get_attachement_headers(self, f_path):
804 def _get_attachement_headers(self, f_path):
801 f_name = safe_str(f_path.split(Repository.NAME_SEP)[-1])
805 f_name = safe_str(f_path.split(Repository.NAME_SEP)[-1])
802 safe_path = f_name.replace('"', '\\"')
806 safe_path = f_name.replace('"', '\\"')
803 encoded_path = urllib.quote(f_name)
807 encoded_path = urllib.quote(f_name)
804
808
805 return "attachment; " \
809 return "attachment; " \
806 "filename=\"{}\"; " \
810 "filename=\"{}\"; " \
807 "filename*=UTF-8\'\'{}".format(safe_path, encoded_path)
811 "filename*=UTF-8\'\'{}".format(safe_path, encoded_path)
808
812
809 @LoginRequired()
813 @LoginRequired()
810 @HasRepoPermissionAnyDecorator(
814 @HasRepoPermissionAnyDecorator(
811 'repository.read', 'repository.write', 'repository.admin')
815 'repository.read', 'repository.write', 'repository.admin')
812 @view_config(
816 @view_config(
813 route_name='repo_file_raw', request_method='GET',
817 route_name='repo_file_raw', request_method='GET',
814 renderer=None)
818 renderer=None)
815 def repo_file_raw(self):
819 def repo_file_raw(self):
816 """
820 """
817 Action for show as raw, some mimetypes are "rendered",
821 Action for show as raw, some mimetypes are "rendered",
818 those include images, icons.
822 those include images, icons.
819 """
823 """
820 c = self.load_default_context()
824 c = self.load_default_context()
821
825
822 commit_id, f_path = self._get_commit_and_path()
826 commit_id, f_path = self._get_commit_and_path()
823 commit = self._get_commit_or_redirect(commit_id)
827 commit = self._get_commit_or_redirect(commit_id)
824 file_node = self._get_filenode_or_redirect(commit, f_path)
828 file_node = self._get_filenode_or_redirect(commit, f_path)
825
829
826 raw_mimetype_mapping = {
830 raw_mimetype_mapping = {
827 # map original mimetype to a mimetype used for "show as raw"
831 # map original mimetype to a mimetype used for "show as raw"
828 # you can also provide a content-disposition to override the
832 # you can also provide a content-disposition to override the
829 # default "attachment" disposition.
833 # default "attachment" disposition.
830 # orig_type: (new_type, new_dispo)
834 # orig_type: (new_type, new_dispo)
831
835
832 # show images inline:
836 # show images inline:
833 # Do not re-add SVG: it is unsafe and permits XSS attacks. One can
837 # Do not re-add SVG: it is unsafe and permits XSS attacks. One can
834 # for example render an SVG with javascript inside or even render
838 # for example render an SVG with javascript inside or even render
835 # HTML.
839 # HTML.
836 'image/x-icon': ('image/x-icon', 'inline'),
840 'image/x-icon': ('image/x-icon', 'inline'),
837 'image/png': ('image/png', 'inline'),
841 'image/png': ('image/png', 'inline'),
838 'image/gif': ('image/gif', 'inline'),
842 'image/gif': ('image/gif', 'inline'),
839 'image/jpeg': ('image/jpeg', 'inline'),
843 'image/jpeg': ('image/jpeg', 'inline'),
840 'application/pdf': ('application/pdf', 'inline'),
844 'application/pdf': ('application/pdf', 'inline'),
841 }
845 }
842
846
843 mimetype = file_node.mimetype
847 mimetype = file_node.mimetype
844 try:
848 try:
845 mimetype, disposition = raw_mimetype_mapping[mimetype]
849 mimetype, disposition = raw_mimetype_mapping[mimetype]
846 except KeyError:
850 except KeyError:
847 # we don't know anything special about this, handle it safely
851 # we don't know anything special about this, handle it safely
848 if file_node.is_binary:
852 if file_node.is_binary:
849 # do same as download raw for binary files
853 # do same as download raw for binary files
850 mimetype, disposition = 'application/octet-stream', 'attachment'
854 mimetype, disposition = 'application/octet-stream', 'attachment'
851 else:
855 else:
852 # do not just use the original mimetype, but force text/plain,
856 # do not just use the original mimetype, but force text/plain,
853 # otherwise it would serve text/html and that might be unsafe.
857 # otherwise it would serve text/html and that might be unsafe.
854 # Note: underlying vcs library fakes text/plain mimetype if the
858 # Note: underlying vcs library fakes text/plain mimetype if the
855 # mimetype can not be determined and it thinks it is not
859 # mimetype can not be determined and it thinks it is not
856 # binary.This might lead to erroneous text display in some
860 # binary.This might lead to erroneous text display in some
857 # cases, but helps in other cases, like with text files
861 # cases, but helps in other cases, like with text files
858 # without extension.
862 # without extension.
859 mimetype, disposition = 'text/plain', 'inline'
863 mimetype, disposition = 'text/plain', 'inline'
860
864
861 if disposition == 'attachment':
865 if disposition == 'attachment':
862 disposition = self._get_attachement_headers(f_path)
866 disposition = self._get_attachement_headers(f_path)
863
867
864 stream_content = file_node.stream_bytes()
868 stream_content = file_node.stream_bytes()
865
869
866 response = Response(app_iter=stream_content)
870 response = Response(app_iter=stream_content)
867 response.content_disposition = disposition
871 response.content_disposition = disposition
868 response.content_type = mimetype
872 response.content_type = mimetype
869
873
870 charset = self._get_default_encoding(c)
874 charset = self._get_default_encoding(c)
871 if charset:
875 if charset:
872 response.charset = charset
876 response.charset = charset
873
877
874 return response
878 return response
875
879
876 @LoginRequired()
880 @LoginRequired()
877 @HasRepoPermissionAnyDecorator(
881 @HasRepoPermissionAnyDecorator(
878 'repository.read', 'repository.write', 'repository.admin')
882 'repository.read', 'repository.write', 'repository.admin')
879 @view_config(
883 @view_config(
880 route_name='repo_file_download', request_method='GET',
884 route_name='repo_file_download', request_method='GET',
881 renderer=None)
885 renderer=None)
882 @view_config(
886 @view_config(
883 route_name='repo_file_download:legacy', request_method='GET',
887 route_name='repo_file_download:legacy', request_method='GET',
884 renderer=None)
888 renderer=None)
885 def repo_file_download(self):
889 def repo_file_download(self):
886 c = self.load_default_context()
890 c = self.load_default_context()
887
891
888 commit_id, f_path = self._get_commit_and_path()
892 commit_id, f_path = self._get_commit_and_path()
889 commit = self._get_commit_or_redirect(commit_id)
893 commit = self._get_commit_or_redirect(commit_id)
890 file_node = self._get_filenode_or_redirect(commit, f_path)
894 file_node = self._get_filenode_or_redirect(commit, f_path)
891
895
892 if self.request.GET.get('lf'):
896 if self.request.GET.get('lf'):
893 # only if lf get flag is passed, we download this file
897 # only if lf get flag is passed, we download this file
894 # as LFS/Largefile
898 # as LFS/Largefile
895 lf_node = file_node.get_largefile_node()
899 lf_node = file_node.get_largefile_node()
896 if lf_node:
900 if lf_node:
897 # overwrite our pointer with the REAL large-file
901 # overwrite our pointer with the REAL large-file
898 file_node = lf_node
902 file_node = lf_node
899
903
900 disposition = self._get_attachement_headers(f_path)
904 disposition = self._get_attachement_headers(f_path)
901
905
902 stream_content = file_node.stream_bytes()
906 stream_content = file_node.stream_bytes()
903
907
904 response = Response(app_iter=stream_content)
908 response = Response(app_iter=stream_content)
905 response.content_disposition = disposition
909 response.content_disposition = disposition
906 response.content_type = file_node.mimetype
910 response.content_type = file_node.mimetype
907
911
908 charset = self._get_default_encoding(c)
912 charset = self._get_default_encoding(c)
909 if charset:
913 if charset:
910 response.charset = charset
914 response.charset = charset
911
915
912 return response
916 return response
913
917
914 def _get_nodelist_at_commit(self, repo_name, repo_id, commit_id, f_path):
918 def _get_nodelist_at_commit(self, repo_name, repo_id, commit_id, f_path):
915
919
916 cache_seconds = safe_int(
920 cache_seconds = safe_int(
917 rhodecode.CONFIG.get('rc_cache.cache_repo.expiration_time'))
921 rhodecode.CONFIG.get('rc_cache.cache_repo.expiration_time'))
918 cache_on = cache_seconds > 0
922 cache_on = cache_seconds > 0
919 log.debug(
923 log.debug(
920 'Computing FILE SEARCH for repo_id %s commit_id `%s` and path `%s`'
924 'Computing FILE SEARCH for repo_id %s commit_id `%s` and path `%s`'
921 'with caching: %s[TTL: %ss]' % (
925 'with caching: %s[TTL: %ss]' % (
922 repo_id, commit_id, f_path, cache_on, cache_seconds or 0))
926 repo_id, commit_id, f_path, cache_on, cache_seconds or 0))
923
927
924 cache_namespace_uid = 'cache_repo.{}'.format(repo_id)
928 cache_namespace_uid = 'cache_repo.{}'.format(repo_id)
925 region = rc_cache.get_or_create_region('cache_repo', cache_namespace_uid)
929 region = rc_cache.get_or_create_region('cache_repo', cache_namespace_uid)
926
930
927 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid, condition=cache_on)
931 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid, condition=cache_on)
928 def compute_file_search(_name_hash, _repo_id, _commit_id, _f_path):
932 def compute_file_search(_name_hash, _repo_id, _commit_id, _f_path):
929 log.debug('Generating cached nodelist for repo_id:%s, %s, %s',
933 log.debug('Generating cached nodelist for repo_id:%s, %s, %s',
930 _repo_id, commit_id, f_path)
934 _repo_id, commit_id, f_path)
931 try:
935 try:
932 _d, _f = ScmModel().get_quick_filter_nodes(repo_name, _commit_id, _f_path)
936 _d, _f = ScmModel().get_quick_filter_nodes(repo_name, _commit_id, _f_path)
933 except (RepositoryError, CommitDoesNotExistError, Exception) as e:
937 except (RepositoryError, CommitDoesNotExistError, Exception) as e:
934 log.exception(safe_str(e))
938 log.exception(safe_str(e))
935 h.flash(safe_str(h.escape(e)), category='error')
939 h.flash(safe_str(h.escape(e)), category='error')
936 raise HTTPFound(h.route_path(
940 raise HTTPFound(h.route_path(
937 'repo_files', repo_name=self.db_repo_name,
941 'repo_files', repo_name=self.db_repo_name,
938 commit_id='tip', f_path='/'))
942 commit_id='tip', f_path='/'))
939
943
940 return _d + _f
944 return _d + _f
941
945
942 result = compute_file_search(self.db_repo.repo_name_hash, self.db_repo.repo_id,
946 result = compute_file_search(self.db_repo.repo_name_hash, self.db_repo.repo_id,
943 commit_id, f_path)
947 commit_id, f_path)
944 return filter(lambda n: self.path_filter.path_access_allowed(n['name']), result)
948 return filter(lambda n: self.path_filter.path_access_allowed(n['name']), result)
945
949
946 @LoginRequired()
950 @LoginRequired()
947 @HasRepoPermissionAnyDecorator(
951 @HasRepoPermissionAnyDecorator(
948 'repository.read', 'repository.write', 'repository.admin')
952 'repository.read', 'repository.write', 'repository.admin')
949 @view_config(
953 @view_config(
950 route_name='repo_files_nodelist', request_method='GET',
954 route_name='repo_files_nodelist', request_method='GET',
951 renderer='json_ext', xhr=True)
955 renderer='json_ext', xhr=True)
952 def repo_nodelist(self):
956 def repo_nodelist(self):
953 self.load_default_context()
957 self.load_default_context()
954
958
955 commit_id, f_path = self._get_commit_and_path()
959 commit_id, f_path = self._get_commit_and_path()
956 commit = self._get_commit_or_redirect(commit_id)
960 commit = self._get_commit_or_redirect(commit_id)
957
961
958 metadata = self._get_nodelist_at_commit(
962 metadata = self._get_nodelist_at_commit(
959 self.db_repo_name, self.db_repo.repo_id, commit.raw_id, f_path)
963 self.db_repo_name, self.db_repo.repo_id, commit.raw_id, f_path)
960 return {'nodes': metadata}
964 return {'nodes': metadata}
961
965
962 def _create_references(self, branches_or_tags, symbolic_reference, f_path, ref_type):
966 def _create_references(self, branches_or_tags, symbolic_reference, f_path, ref_type):
963 items = []
967 items = []
964 for name, commit_id in branches_or_tags.items():
968 for name, commit_id in branches_or_tags.items():
965 sym_ref = symbolic_reference(commit_id, name, f_path, ref_type)
969 sym_ref = symbolic_reference(commit_id, name, f_path, ref_type)
966 items.append((sym_ref, name, ref_type))
970 items.append((sym_ref, name, ref_type))
967 return items
971 return items
968
972
969 def _symbolic_reference(self, commit_id, name, f_path, ref_type):
973 def _symbolic_reference(self, commit_id, name, f_path, ref_type):
970 return commit_id
974 return commit_id
971
975
972 def _symbolic_reference_svn(self, commit_id, name, f_path, ref_type):
976 def _symbolic_reference_svn(self, commit_id, name, f_path, ref_type):
973 return commit_id
977 return commit_id
974
978
975 # NOTE(dan): old code we used in "diff" mode compare
979 # NOTE(dan): old code we used in "diff" mode compare
976 new_f_path = vcspath.join(name, f_path)
980 new_f_path = vcspath.join(name, f_path)
977 return u'%s@%s' % (new_f_path, commit_id)
981 return u'%s@%s' % (new_f_path, commit_id)
978
982
979 def _get_node_history(self, commit_obj, f_path, commits=None):
983 def _get_node_history(self, commit_obj, f_path, commits=None):
980 """
984 """
981 get commit history for given node
985 get commit history for given node
982
986
983 :param commit_obj: commit to calculate history
987 :param commit_obj: commit to calculate history
984 :param f_path: path for node to calculate history for
988 :param f_path: path for node to calculate history for
985 :param commits: if passed don't calculate history and take
989 :param commits: if passed don't calculate history and take
986 commits defined in this list
990 commits defined in this list
987 """
991 """
988 _ = self.request.translate
992 _ = self.request.translate
989
993
990 # calculate history based on tip
994 # calculate history based on tip
991 tip = self.rhodecode_vcs_repo.get_commit()
995 tip = self.rhodecode_vcs_repo.get_commit()
992 if commits is None:
996 if commits is None:
993 pre_load = ["author", "branch"]
997 pre_load = ["author", "branch"]
994 try:
998 try:
995 commits = tip.get_path_history(f_path, pre_load=pre_load)
999 commits = tip.get_path_history(f_path, pre_load=pre_load)
996 except (NodeDoesNotExistError, CommitError):
1000 except (NodeDoesNotExistError, CommitError):
997 # this node is not present at tip!
1001 # this node is not present at tip!
998 commits = commit_obj.get_path_history(f_path, pre_load=pre_load)
1002 commits = commit_obj.get_path_history(f_path, pre_load=pre_load)
999
1003
1000 history = []
1004 history = []
1001 commits_group = ([], _("Changesets"))
1005 commits_group = ([], _("Changesets"))
1002 for commit in commits:
1006 for commit in commits:
1003 branch = ' (%s)' % commit.branch if commit.branch else ''
1007 branch = ' (%s)' % commit.branch if commit.branch else ''
1004 n_desc = 'r%s:%s%s' % (commit.idx, commit.short_id, branch)
1008 n_desc = 'r%s:%s%s' % (commit.idx, commit.short_id, branch)
1005 commits_group[0].append((commit.raw_id, n_desc, 'sha'))
1009 commits_group[0].append((commit.raw_id, n_desc, 'sha'))
1006 history.append(commits_group)
1010 history.append(commits_group)
1007
1011
1008 symbolic_reference = self._symbolic_reference
1012 symbolic_reference = self._symbolic_reference
1009
1013
1010 if self.rhodecode_vcs_repo.alias == 'svn':
1014 if self.rhodecode_vcs_repo.alias == 'svn':
1011 adjusted_f_path = RepoFilesView.adjust_file_path_for_svn(
1015 adjusted_f_path = RepoFilesView.adjust_file_path_for_svn(
1012 f_path, self.rhodecode_vcs_repo)
1016 f_path, self.rhodecode_vcs_repo)
1013 if adjusted_f_path != f_path:
1017 if adjusted_f_path != f_path:
1014 log.debug(
1018 log.debug(
1015 'Recognized svn tag or branch in file "%s", using svn '
1019 'Recognized svn tag or branch in file "%s", using svn '
1016 'specific symbolic references', f_path)
1020 'specific symbolic references', f_path)
1017 f_path = adjusted_f_path
1021 f_path = adjusted_f_path
1018 symbolic_reference = self._symbolic_reference_svn
1022 symbolic_reference = self._symbolic_reference_svn
1019
1023
1020 branches = self._create_references(
1024 branches = self._create_references(
1021 self.rhodecode_vcs_repo.branches, symbolic_reference, f_path, 'branch')
1025 self.rhodecode_vcs_repo.branches, symbolic_reference, f_path, 'branch')
1022 branches_group = (branches, _("Branches"))
1026 branches_group = (branches, _("Branches"))
1023
1027
1024 tags = self._create_references(
1028 tags = self._create_references(
1025 self.rhodecode_vcs_repo.tags, symbolic_reference, f_path, 'tag')
1029 self.rhodecode_vcs_repo.tags, symbolic_reference, f_path, 'tag')
1026 tags_group = (tags, _("Tags"))
1030 tags_group = (tags, _("Tags"))
1027
1031
1028 history.append(branches_group)
1032 history.append(branches_group)
1029 history.append(tags_group)
1033 history.append(tags_group)
1030
1034
1031 return history, commits
1035 return history, commits
1032
1036
1033 @LoginRequired()
1037 @LoginRequired()
1034 @HasRepoPermissionAnyDecorator(
1038 @HasRepoPermissionAnyDecorator(
1035 'repository.read', 'repository.write', 'repository.admin')
1039 'repository.read', 'repository.write', 'repository.admin')
1036 @view_config(
1040 @view_config(
1037 route_name='repo_file_history', request_method='GET',
1041 route_name='repo_file_history', request_method='GET',
1038 renderer='json_ext')
1042 renderer='json_ext')
1039 def repo_file_history(self):
1043 def repo_file_history(self):
1040 self.load_default_context()
1044 self.load_default_context()
1041
1045
1042 commit_id, f_path = self._get_commit_and_path()
1046 commit_id, f_path = self._get_commit_and_path()
1043 commit = self._get_commit_or_redirect(commit_id)
1047 commit = self._get_commit_or_redirect(commit_id)
1044 file_node = self._get_filenode_or_redirect(commit, f_path)
1048 file_node = self._get_filenode_or_redirect(commit, f_path)
1045
1049
1046 if file_node.is_file():
1050 if file_node.is_file():
1047 file_history, _hist = self._get_node_history(commit, f_path)
1051 file_history, _hist = self._get_node_history(commit, f_path)
1048
1052
1049 res = []
1053 res = []
1050 for section_items, section in file_history:
1054 for section_items, section in file_history:
1051 items = []
1055 items = []
1052 for obj_id, obj_text, obj_type in section_items:
1056 for obj_id, obj_text, obj_type in section_items:
1053 at_rev = ''
1057 at_rev = ''
1054 if obj_type in ['branch', 'bookmark', 'tag']:
1058 if obj_type in ['branch', 'bookmark', 'tag']:
1055 at_rev = obj_text
1059 at_rev = obj_text
1056 entry = {
1060 entry = {
1057 'id': obj_id,
1061 'id': obj_id,
1058 'text': obj_text,
1062 'text': obj_text,
1059 'type': obj_type,
1063 'type': obj_type,
1060 'at_rev': at_rev
1064 'at_rev': at_rev
1061 }
1065 }
1062
1066
1063 items.append(entry)
1067 items.append(entry)
1064
1068
1065 res.append({
1069 res.append({
1066 'text': section,
1070 'text': section,
1067 'children': items
1071 'children': items
1068 })
1072 })
1069
1073
1070 data = {
1074 data = {
1071 'more': False,
1075 'more': False,
1072 'results': res
1076 'results': res
1073 }
1077 }
1074 return data
1078 return data
1075
1079
1076 log.warning('Cannot fetch history for directory')
1080 log.warning('Cannot fetch history for directory')
1077 raise HTTPBadRequest()
1081 raise HTTPBadRequest()
1078
1082
1079 @LoginRequired()
1083 @LoginRequired()
1080 @HasRepoPermissionAnyDecorator(
1084 @HasRepoPermissionAnyDecorator(
1081 'repository.read', 'repository.write', 'repository.admin')
1085 'repository.read', 'repository.write', 'repository.admin')
1082 @view_config(
1086 @view_config(
1083 route_name='repo_file_authors', request_method='GET',
1087 route_name='repo_file_authors', request_method='GET',
1084 renderer='rhodecode:templates/files/file_authors_box.mako')
1088 renderer='rhodecode:templates/files/file_authors_box.mako')
1085 def repo_file_authors(self):
1089 def repo_file_authors(self):
1086 c = self.load_default_context()
1090 c = self.load_default_context()
1087
1091
1088 commit_id, f_path = self._get_commit_and_path()
1092 commit_id, f_path = self._get_commit_and_path()
1089 commit = self._get_commit_or_redirect(commit_id)
1093 commit = self._get_commit_or_redirect(commit_id)
1090 file_node = self._get_filenode_or_redirect(commit, f_path)
1094 file_node = self._get_filenode_or_redirect(commit, f_path)
1091
1095
1092 if not file_node.is_file():
1096 if not file_node.is_file():
1093 raise HTTPBadRequest()
1097 raise HTTPBadRequest()
1094
1098
1095 c.file_last_commit = file_node.last_commit
1099 c.file_last_commit = file_node.last_commit
1096 if self.request.GET.get('annotate') == '1':
1100 if self.request.GET.get('annotate') == '1':
1097 # use _hist from annotation if annotation mode is on
1101 # use _hist from annotation if annotation mode is on
1098 commit_ids = set(x[1] for x in file_node.annotate)
1102 commit_ids = set(x[1] for x in file_node.annotate)
1099 _hist = (
1103 _hist = (
1100 self.rhodecode_vcs_repo.get_commit(commit_id)
1104 self.rhodecode_vcs_repo.get_commit(commit_id)
1101 for commit_id in commit_ids)
1105 for commit_id in commit_ids)
1102 else:
1106 else:
1103 _f_history, _hist = self._get_node_history(commit, f_path)
1107 _f_history, _hist = self._get_node_history(commit, f_path)
1104 c.file_author = False
1108 c.file_author = False
1105
1109
1106 unique = collections.OrderedDict()
1110 unique = collections.OrderedDict()
1107 for commit in _hist:
1111 for commit in _hist:
1108 author = commit.author
1112 author = commit.author
1109 if author not in unique:
1113 if author not in unique:
1110 unique[commit.author] = [
1114 unique[commit.author] = [
1111 h.email(author),
1115 h.email(author),
1112 h.person(author, 'username_or_name_or_email'),
1116 h.person(author, 'username_or_name_or_email'),
1113 1 # counter
1117 1 # counter
1114 ]
1118 ]
1115
1119
1116 else:
1120 else:
1117 # increase counter
1121 # increase counter
1118 unique[commit.author][2] += 1
1122 unique[commit.author][2] += 1
1119
1123
1120 c.authors = [val for val in unique.values()]
1124 c.authors = [val for val in unique.values()]
1121
1125
1122 return self._get_template_context(c)
1126 return self._get_template_context(c)
1123
1127
1124 @LoginRequired()
1128 @LoginRequired()
1125 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1129 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1126 @view_config(
1130 @view_config(
1127 route_name='repo_files_check_head', request_method='POST',
1131 route_name='repo_files_check_head', request_method='POST',
1128 renderer='json_ext', xhr=True)
1132 renderer='json_ext', xhr=True)
1129 def repo_files_check_head(self):
1133 def repo_files_check_head(self):
1130 self.load_default_context()
1134 self.load_default_context()
1131
1135
1132 commit_id, f_path = self._get_commit_and_path()
1136 commit_id, f_path = self._get_commit_and_path()
1133 _branch_name, _sha_commit_id, is_head = \
1137 _branch_name, _sha_commit_id, is_head = \
1134 self._is_valid_head(commit_id, self.rhodecode_vcs_repo)
1138 self._is_valid_head(commit_id, self.rhodecode_vcs_repo)
1135
1139
1136 new_path = self.request.POST.get('path')
1140 new_path = self.request.POST.get('path')
1137 operation = self.request.POST.get('operation')
1141 operation = self.request.POST.get('operation')
1138 path_exist = ''
1142 path_exist = ''
1139
1143
1140 if new_path and operation in ['create', 'upload']:
1144 if new_path and operation in ['create', 'upload']:
1141 new_f_path = os.path.join(f_path.lstrip('/'), new_path)
1145 new_f_path = os.path.join(f_path.lstrip('/'), new_path)
1142 try:
1146 try:
1143 commit_obj = self.rhodecode_vcs_repo.get_commit(commit_id)
1147 commit_obj = self.rhodecode_vcs_repo.get_commit(commit_id)
1144 # NOTE(dan): construct whole path without leading /
1148 # NOTE(dan): construct whole path without leading /
1145 file_node = commit_obj.get_node(new_f_path)
1149 file_node = commit_obj.get_node(new_f_path)
1146 if file_node is not None:
1150 if file_node is not None:
1147 path_exist = new_f_path
1151 path_exist = new_f_path
1148 except EmptyRepositoryError:
1152 except EmptyRepositoryError:
1149 pass
1153 pass
1150 except Exception:
1154 except Exception:
1151 pass
1155 pass
1152
1156
1153 return {
1157 return {
1154 'branch': _branch_name,
1158 'branch': _branch_name,
1155 'sha': _sha_commit_id,
1159 'sha': _sha_commit_id,
1156 'is_head': is_head,
1160 'is_head': is_head,
1157 'path_exists': path_exist
1161 'path_exists': path_exist
1158 }
1162 }
1159
1163
1160 @LoginRequired()
1164 @LoginRequired()
1161 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1165 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1162 @view_config(
1166 @view_config(
1163 route_name='repo_files_remove_file', request_method='GET',
1167 route_name='repo_files_remove_file', request_method='GET',
1164 renderer='rhodecode:templates/files/files_delete.mako')
1168 renderer='rhodecode:templates/files/files_delete.mako')
1165 def repo_files_remove_file(self):
1169 def repo_files_remove_file(self):
1166 _ = self.request.translate
1170 _ = self.request.translate
1167 c = self.load_default_context()
1171 c = self.load_default_context()
1168 commit_id, f_path = self._get_commit_and_path()
1172 commit_id, f_path = self._get_commit_and_path()
1169
1173
1170 self._ensure_not_locked()
1174 self._ensure_not_locked()
1171 _branch_name, _sha_commit_id, is_head = \
1175 _branch_name, _sha_commit_id, is_head = \
1172 self._is_valid_head(commit_id, self.rhodecode_vcs_repo)
1176 self._is_valid_head(commit_id, self.rhodecode_vcs_repo)
1173
1177
1174 self.forbid_non_head(is_head, f_path)
1178 self.forbid_non_head(is_head, f_path)
1175 self.check_branch_permission(_branch_name)
1179 self.check_branch_permission(_branch_name)
1176
1180
1177 c.commit = self._get_commit_or_redirect(commit_id)
1181 c.commit = self._get_commit_or_redirect(commit_id)
1178 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1182 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1179
1183
1180 c.default_message = _(
1184 c.default_message = _(
1181 'Deleted file {} via RhodeCode Enterprise').format(f_path)
1185 'Deleted file {} via RhodeCode Enterprise').format(f_path)
1182 c.f_path = f_path
1186 c.f_path = f_path
1183
1187
1184 return self._get_template_context(c)
1188 return self._get_template_context(c)
1185
1189
1186 @LoginRequired()
1190 @LoginRequired()
1187 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1191 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1188 @CSRFRequired()
1192 @CSRFRequired()
1189 @view_config(
1193 @view_config(
1190 route_name='repo_files_delete_file', request_method='POST',
1194 route_name='repo_files_delete_file', request_method='POST',
1191 renderer=None)
1195 renderer=None)
1192 def repo_files_delete_file(self):
1196 def repo_files_delete_file(self):
1193 _ = self.request.translate
1197 _ = self.request.translate
1194
1198
1195 c = self.load_default_context()
1199 c = self.load_default_context()
1196 commit_id, f_path = self._get_commit_and_path()
1200 commit_id, f_path = self._get_commit_and_path()
1197
1201
1198 self._ensure_not_locked()
1202 self._ensure_not_locked()
1199 _branch_name, _sha_commit_id, is_head = \
1203 _branch_name, _sha_commit_id, is_head = \
1200 self._is_valid_head(commit_id, self.rhodecode_vcs_repo)
1204 self._is_valid_head(commit_id, self.rhodecode_vcs_repo)
1201
1205
1202 self.forbid_non_head(is_head, f_path)
1206 self.forbid_non_head(is_head, f_path)
1203 self.check_branch_permission(_branch_name)
1207 self.check_branch_permission(_branch_name)
1204
1208
1205 c.commit = self._get_commit_or_redirect(commit_id)
1209 c.commit = self._get_commit_or_redirect(commit_id)
1206 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1210 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1207
1211
1208 c.default_message = _(
1212 c.default_message = _(
1209 'Deleted file {} via RhodeCode Enterprise').format(f_path)
1213 'Deleted file {} via RhodeCode Enterprise').format(f_path)
1210 c.f_path = f_path
1214 c.f_path = f_path
1211 node_path = f_path
1215 node_path = f_path
1212 author = self._rhodecode_db_user.full_contact
1216 author = self._rhodecode_db_user.full_contact
1213 message = self.request.POST.get('message') or c.default_message
1217 message = self.request.POST.get('message') or c.default_message
1214 try:
1218 try:
1215 nodes = {
1219 nodes = {
1216 node_path: {
1220 node_path: {
1217 'content': ''
1221 'content': ''
1218 }
1222 }
1219 }
1223 }
1220 ScmModel().delete_nodes(
1224 ScmModel().delete_nodes(
1221 user=self._rhodecode_db_user.user_id, repo=self.db_repo,
1225 user=self._rhodecode_db_user.user_id, repo=self.db_repo,
1222 message=message,
1226 message=message,
1223 nodes=nodes,
1227 nodes=nodes,
1224 parent_commit=c.commit,
1228 parent_commit=c.commit,
1225 author=author,
1229 author=author,
1226 )
1230 )
1227
1231
1228 h.flash(
1232 h.flash(
1229 _('Successfully deleted file `{}`').format(
1233 _('Successfully deleted file `{}`').format(
1230 h.escape(f_path)), category='success')
1234 h.escape(f_path)), category='success')
1231 except Exception:
1235 except Exception:
1232 log.exception('Error during commit operation')
1236 log.exception('Error during commit operation')
1233 h.flash(_('Error occurred during commit'), category='error')
1237 h.flash(_('Error occurred during commit'), category='error')
1234 raise HTTPFound(
1238 raise HTTPFound(
1235 h.route_path('repo_commit', repo_name=self.db_repo_name,
1239 h.route_path('repo_commit', repo_name=self.db_repo_name,
1236 commit_id='tip'))
1240 commit_id='tip'))
1237
1241
1238 @LoginRequired()
1242 @LoginRequired()
1239 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1243 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1240 @view_config(
1244 @view_config(
1241 route_name='repo_files_edit_file', request_method='GET',
1245 route_name='repo_files_edit_file', request_method='GET',
1242 renderer='rhodecode:templates/files/files_edit.mako')
1246 renderer='rhodecode:templates/files/files_edit.mako')
1243 def repo_files_edit_file(self):
1247 def repo_files_edit_file(self):
1244 _ = self.request.translate
1248 _ = self.request.translate
1245 c = self.load_default_context()
1249 c = self.load_default_context()
1246 commit_id, f_path = self._get_commit_and_path()
1250 commit_id, f_path = self._get_commit_and_path()
1247
1251
1248 self._ensure_not_locked()
1252 self._ensure_not_locked()
1249 _branch_name, _sha_commit_id, is_head = \
1253 _branch_name, _sha_commit_id, is_head = \
1250 self._is_valid_head(commit_id, self.rhodecode_vcs_repo)
1254 self._is_valid_head(commit_id, self.rhodecode_vcs_repo)
1251
1255
1252 self.forbid_non_head(is_head, f_path, commit_id=commit_id)
1256 self.forbid_non_head(is_head, f_path, commit_id=commit_id)
1253 self.check_branch_permission(_branch_name, commit_id=commit_id)
1257 self.check_branch_permission(_branch_name, commit_id=commit_id)
1254
1258
1255 c.commit = self._get_commit_or_redirect(commit_id)
1259 c.commit = self._get_commit_or_redirect(commit_id)
1256 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1260 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1257
1261
1258 if c.file.is_binary:
1262 if c.file.is_binary:
1259 files_url = h.route_path(
1263 files_url = h.route_path(
1260 'repo_files',
1264 'repo_files',
1261 repo_name=self.db_repo_name,
1265 repo_name=self.db_repo_name,
1262 commit_id=c.commit.raw_id, f_path=f_path)
1266 commit_id=c.commit.raw_id, f_path=f_path)
1263 raise HTTPFound(files_url)
1267 raise HTTPFound(files_url)
1264
1268
1265 c.default_message = _('Edited file {} via RhodeCode Enterprise').format(f_path)
1269 c.default_message = _('Edited file {} via RhodeCode Enterprise').format(f_path)
1266 c.f_path = f_path
1270 c.f_path = f_path
1267
1271
1268 return self._get_template_context(c)
1272 return self._get_template_context(c)
1269
1273
1270 @LoginRequired()
1274 @LoginRequired()
1271 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1275 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1272 @CSRFRequired()
1276 @CSRFRequired()
1273 @view_config(
1277 @view_config(
1274 route_name='repo_files_update_file', request_method='POST',
1278 route_name='repo_files_update_file', request_method='POST',
1275 renderer=None)
1279 renderer=None)
1276 def repo_files_update_file(self):
1280 def repo_files_update_file(self):
1277 _ = self.request.translate
1281 _ = self.request.translate
1278 c = self.load_default_context()
1282 c = self.load_default_context()
1279 commit_id, f_path = self._get_commit_and_path()
1283 commit_id, f_path = self._get_commit_and_path()
1280
1284
1281 self._ensure_not_locked()
1285 self._ensure_not_locked()
1282
1286
1283 c.commit = self._get_commit_or_redirect(commit_id)
1287 c.commit = self._get_commit_or_redirect(commit_id)
1284 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1288 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1285
1289
1286 if c.file.is_binary:
1290 if c.file.is_binary:
1287 raise HTTPFound(h.route_path('repo_files', repo_name=self.db_repo_name,
1291 raise HTTPFound(h.route_path('repo_files', repo_name=self.db_repo_name,
1288 commit_id=c.commit.raw_id, f_path=f_path))
1292 commit_id=c.commit.raw_id, f_path=f_path))
1289
1293
1290 _branch_name, _sha_commit_id, is_head = \
1294 _branch_name, _sha_commit_id, is_head = \
1291 self._is_valid_head(commit_id, self.rhodecode_vcs_repo)
1295 self._is_valid_head(commit_id, self.rhodecode_vcs_repo)
1292
1296
1293 self.forbid_non_head(is_head, f_path, commit_id=commit_id)
1297 self.forbid_non_head(is_head, f_path, commit_id=commit_id)
1294 self.check_branch_permission(_branch_name, commit_id=commit_id)
1298 self.check_branch_permission(_branch_name, commit_id=commit_id)
1295
1299
1296 c.default_message = _('Edited file {} via RhodeCode Enterprise').format(f_path)
1300 c.default_message = _('Edited file {} via RhodeCode Enterprise').format(f_path)
1297 c.f_path = f_path
1301 c.f_path = f_path
1298
1302
1299 old_content = c.file.content
1303 old_content = c.file.content
1300 sl = old_content.splitlines(1)
1304 sl = old_content.splitlines(1)
1301 first_line = sl[0] if sl else ''
1305 first_line = sl[0] if sl else ''
1302
1306
1303 r_post = self.request.POST
1307 r_post = self.request.POST
1304 # line endings: 0 - Unix, 1 - Mac, 2 - DOS
1308 # line endings: 0 - Unix, 1 - Mac, 2 - DOS
1305 line_ending_mode = detect_mode(first_line, 0)
1309 line_ending_mode = detect_mode(first_line, 0)
1306 content = convert_line_endings(r_post.get('content', ''), line_ending_mode)
1310 content = convert_line_endings(r_post.get('content', ''), line_ending_mode)
1307
1311
1308 message = r_post.get('message') or c.default_message
1312 message = r_post.get('message') or c.default_message
1309 org_node_path = c.file.unicode_path
1313 org_node_path = c.file.unicode_path
1310 filename = r_post['filename']
1314 filename = r_post['filename']
1311
1315
1312 root_path = c.file.dir_path
1316 root_path = c.file.dir_path
1313 pure_path = self.create_pure_path(root_path, filename)
1317 pure_path = self.create_pure_path(root_path, filename)
1314 node_path = safe_unicode(bytes(pure_path))
1318 node_path = safe_unicode(bytes(pure_path))
1315
1319
1316 default_redirect_url = h.route_path('repo_commit', repo_name=self.db_repo_name,
1320 default_redirect_url = h.route_path('repo_commit', repo_name=self.db_repo_name,
1317 commit_id=commit_id)
1321 commit_id=commit_id)
1318 if content == old_content and node_path == org_node_path:
1322 if content == old_content and node_path == org_node_path:
1319 h.flash(_('No changes detected on {}').format(h.escape(org_node_path)),
1323 h.flash(_('No changes detected on {}').format(h.escape(org_node_path)),
1320 category='warning')
1324 category='warning')
1321 raise HTTPFound(default_redirect_url)
1325 raise HTTPFound(default_redirect_url)
1322
1326
1323 try:
1327 try:
1324 mapping = {
1328 mapping = {
1325 org_node_path: {
1329 org_node_path: {
1326 'org_filename': org_node_path,
1330 'org_filename': org_node_path,
1327 'filename': node_path,
1331 'filename': node_path,
1328 'content': content,
1332 'content': content,
1329 'lexer': '',
1333 'lexer': '',
1330 'op': 'mod',
1334 'op': 'mod',
1331 'mode': c.file.mode
1335 'mode': c.file.mode
1332 }
1336 }
1333 }
1337 }
1334
1338
1335 commit = ScmModel().update_nodes(
1339 commit = ScmModel().update_nodes(
1336 user=self._rhodecode_db_user.user_id,
1340 user=self._rhodecode_db_user.user_id,
1337 repo=self.db_repo,
1341 repo=self.db_repo,
1338 message=message,
1342 message=message,
1339 nodes=mapping,
1343 nodes=mapping,
1340 parent_commit=c.commit,
1344 parent_commit=c.commit,
1341 )
1345 )
1342
1346
1343 h.flash(_('Successfully committed changes to file `{}`').format(
1347 h.flash(_('Successfully committed changes to file `{}`').format(
1344 h.escape(f_path)), category='success')
1348 h.escape(f_path)), category='success')
1345 default_redirect_url = h.route_path(
1349 default_redirect_url = h.route_path(
1346 'repo_commit', repo_name=self.db_repo_name, commit_id=commit.raw_id)
1350 'repo_commit', repo_name=self.db_repo_name, commit_id=commit.raw_id)
1347
1351
1348 except Exception:
1352 except Exception:
1349 log.exception('Error occurred during commit')
1353 log.exception('Error occurred during commit')
1350 h.flash(_('Error occurred during commit'), category='error')
1354 h.flash(_('Error occurred during commit'), category='error')
1351
1355
1352 raise HTTPFound(default_redirect_url)
1356 raise HTTPFound(default_redirect_url)
1353
1357
1354 @LoginRequired()
1358 @LoginRequired()
1355 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1359 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1356 @view_config(
1360 @view_config(
1357 route_name='repo_files_add_file', request_method='GET',
1361 route_name='repo_files_add_file', request_method='GET',
1358 renderer='rhodecode:templates/files/files_add.mako')
1362 renderer='rhodecode:templates/files/files_add.mako')
1359 @view_config(
1363 @view_config(
1360 route_name='repo_files_upload_file', request_method='GET',
1364 route_name='repo_files_upload_file', request_method='GET',
1361 renderer='rhodecode:templates/files/files_upload.mako')
1365 renderer='rhodecode:templates/files/files_upload.mako')
1362 def repo_files_add_file(self):
1366 def repo_files_add_file(self):
1363 _ = self.request.translate
1367 _ = self.request.translate
1364 c = self.load_default_context()
1368 c = self.load_default_context()
1365 commit_id, f_path = self._get_commit_and_path()
1369 commit_id, f_path = self._get_commit_and_path()
1366
1370
1367 self._ensure_not_locked()
1371 self._ensure_not_locked()
1368
1372
1369 c.commit = self._get_commit_or_redirect(commit_id, redirect_after=False)
1373 c.commit = self._get_commit_or_redirect(commit_id, redirect_after=False)
1370 if c.commit is None:
1374 if c.commit is None:
1371 c.commit = EmptyCommit(alias=self.rhodecode_vcs_repo.alias)
1375 c.commit = EmptyCommit(alias=self.rhodecode_vcs_repo.alias)
1372
1376
1373 if self.rhodecode_vcs_repo.is_empty():
1377 if self.rhodecode_vcs_repo.is_empty():
1374 # for empty repository we cannot check for current branch, we rely on
1378 # for empty repository we cannot check for current branch, we rely on
1375 # c.commit.branch instead
1379 # c.commit.branch instead
1376 _branch_name, _sha_commit_id, is_head = c.commit.branch, '', True
1380 _branch_name, _sha_commit_id, is_head = c.commit.branch, '', True
1377 else:
1381 else:
1378 _branch_name, _sha_commit_id, is_head = \
1382 _branch_name, _sha_commit_id, is_head = \
1379 self._is_valid_head(commit_id, self.rhodecode_vcs_repo)
1383 self._is_valid_head(commit_id, self.rhodecode_vcs_repo)
1380
1384
1381 self.forbid_non_head(is_head, f_path, commit_id=commit_id)
1385 self.forbid_non_head(is_head, f_path, commit_id=commit_id)
1382 self.check_branch_permission(_branch_name, commit_id=commit_id)
1386 self.check_branch_permission(_branch_name, commit_id=commit_id)
1383
1387
1384 c.default_message = (_('Added file via RhodeCode Enterprise'))
1388 c.default_message = (_('Added file via RhodeCode Enterprise'))
1385 c.f_path = f_path.lstrip('/') # ensure not relative path
1389 c.f_path = f_path.lstrip('/') # ensure not relative path
1386
1390
1387 return self._get_template_context(c)
1391 return self._get_template_context(c)
1388
1392
1389 @LoginRequired()
1393 @LoginRequired()
1390 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1394 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1391 @CSRFRequired()
1395 @CSRFRequired()
1392 @view_config(
1396 @view_config(
1393 route_name='repo_files_create_file', request_method='POST',
1397 route_name='repo_files_create_file', request_method='POST',
1394 renderer=None)
1398 renderer=None)
1395 def repo_files_create_file(self):
1399 def repo_files_create_file(self):
1396 _ = self.request.translate
1400 _ = self.request.translate
1397 c = self.load_default_context()
1401 c = self.load_default_context()
1398 commit_id, f_path = self._get_commit_and_path()
1402 commit_id, f_path = self._get_commit_and_path()
1399
1403
1400 self._ensure_not_locked()
1404 self._ensure_not_locked()
1401
1405
1402 c.commit = self._get_commit_or_redirect(commit_id, redirect_after=False)
1406 c.commit = self._get_commit_or_redirect(commit_id, redirect_after=False)
1403 if c.commit is None:
1407 if c.commit is None:
1404 c.commit = EmptyCommit(alias=self.rhodecode_vcs_repo.alias)
1408 c.commit = EmptyCommit(alias=self.rhodecode_vcs_repo.alias)
1405
1409
1406 # calculate redirect URL
1410 # calculate redirect URL
1407 if self.rhodecode_vcs_repo.is_empty():
1411 if self.rhodecode_vcs_repo.is_empty():
1408 default_redirect_url = h.route_path(
1412 default_redirect_url = h.route_path(
1409 'repo_summary', repo_name=self.db_repo_name)
1413 'repo_summary', repo_name=self.db_repo_name)
1410 else:
1414 else:
1411 default_redirect_url = h.route_path(
1415 default_redirect_url = h.route_path(
1412 'repo_commit', repo_name=self.db_repo_name, commit_id='tip')
1416 'repo_commit', repo_name=self.db_repo_name, commit_id='tip')
1413
1417
1414 if self.rhodecode_vcs_repo.is_empty():
1418 if self.rhodecode_vcs_repo.is_empty():
1415 # for empty repository we cannot check for current branch, we rely on
1419 # for empty repository we cannot check for current branch, we rely on
1416 # c.commit.branch instead
1420 # c.commit.branch instead
1417 _branch_name, _sha_commit_id, is_head = c.commit.branch, '', True
1421 _branch_name, _sha_commit_id, is_head = c.commit.branch, '', True
1418 else:
1422 else:
1419 _branch_name, _sha_commit_id, is_head = \
1423 _branch_name, _sha_commit_id, is_head = \
1420 self._is_valid_head(commit_id, self.rhodecode_vcs_repo)
1424 self._is_valid_head(commit_id, self.rhodecode_vcs_repo)
1421
1425
1422 self.forbid_non_head(is_head, f_path, commit_id=commit_id)
1426 self.forbid_non_head(is_head, f_path, commit_id=commit_id)
1423 self.check_branch_permission(_branch_name, commit_id=commit_id)
1427 self.check_branch_permission(_branch_name, commit_id=commit_id)
1424
1428
1425 c.default_message = (_('Added file via RhodeCode Enterprise'))
1429 c.default_message = (_('Added file via RhodeCode Enterprise'))
1426 c.f_path = f_path
1430 c.f_path = f_path
1427
1431
1428 r_post = self.request.POST
1432 r_post = self.request.POST
1429 message = r_post.get('message') or c.default_message
1433 message = r_post.get('message') or c.default_message
1430 filename = r_post.get('filename')
1434 filename = r_post.get('filename')
1431 unix_mode = 0
1435 unix_mode = 0
1432 content = convert_line_endings(r_post.get('content', ''), unix_mode)
1436 content = convert_line_endings(r_post.get('content', ''), unix_mode)
1433
1437
1434 if not filename:
1438 if not filename:
1435 # If there's no commit, redirect to repo summary
1439 # If there's no commit, redirect to repo summary
1436 if type(c.commit) is EmptyCommit:
1440 if type(c.commit) is EmptyCommit:
1437 redirect_url = h.route_path(
1441 redirect_url = h.route_path(
1438 'repo_summary', repo_name=self.db_repo_name)
1442 'repo_summary', repo_name=self.db_repo_name)
1439 else:
1443 else:
1440 redirect_url = default_redirect_url
1444 redirect_url = default_redirect_url
1441 h.flash(_('No filename specified'), category='warning')
1445 h.flash(_('No filename specified'), category='warning')
1442 raise HTTPFound(redirect_url)
1446 raise HTTPFound(redirect_url)
1443
1447
1444 root_path = f_path
1448 root_path = f_path
1445 pure_path = self.create_pure_path(root_path, filename)
1449 pure_path = self.create_pure_path(root_path, filename)
1446 node_path = safe_unicode(bytes(pure_path).lstrip('/'))
1450 node_path = safe_unicode(bytes(pure_path).lstrip('/'))
1447
1451
1448 author = self._rhodecode_db_user.full_contact
1452 author = self._rhodecode_db_user.full_contact
1449 nodes = {
1453 nodes = {
1450 node_path: {
1454 node_path: {
1451 'content': content
1455 'content': content
1452 }
1456 }
1453 }
1457 }
1454
1458
1455 try:
1459 try:
1456
1460
1457 commit = ScmModel().create_nodes(
1461 commit = ScmModel().create_nodes(
1458 user=self._rhodecode_db_user.user_id,
1462 user=self._rhodecode_db_user.user_id,
1459 repo=self.db_repo,
1463 repo=self.db_repo,
1460 message=message,
1464 message=message,
1461 nodes=nodes,
1465 nodes=nodes,
1462 parent_commit=c.commit,
1466 parent_commit=c.commit,
1463 author=author,
1467 author=author,
1464 )
1468 )
1465
1469
1466 h.flash(_('Successfully committed new file `{}`').format(
1470 h.flash(_('Successfully committed new file `{}`').format(
1467 h.escape(node_path)), category='success')
1471 h.escape(node_path)), category='success')
1468
1472
1469 default_redirect_url = h.route_path(
1473 default_redirect_url = h.route_path(
1470 'repo_commit', repo_name=self.db_repo_name, commit_id=commit.raw_id)
1474 'repo_commit', repo_name=self.db_repo_name, commit_id=commit.raw_id)
1471
1475
1472 except NonRelativePathError:
1476 except NonRelativePathError:
1473 log.exception('Non Relative path found')
1477 log.exception('Non Relative path found')
1474 h.flash(_('The location specified must be a relative path and must not '
1478 h.flash(_('The location specified must be a relative path and must not '
1475 'contain .. in the path'), category='warning')
1479 'contain .. in the path'), category='warning')
1476 raise HTTPFound(default_redirect_url)
1480 raise HTTPFound(default_redirect_url)
1477 except (NodeError, NodeAlreadyExistsError) as e:
1481 except (NodeError, NodeAlreadyExistsError) as e:
1478 h.flash(_(h.escape(e)), category='error')
1482 h.flash(_(h.escape(e)), category='error')
1479 except Exception:
1483 except Exception:
1480 log.exception('Error occurred during commit')
1484 log.exception('Error occurred during commit')
1481 h.flash(_('Error occurred during commit'), category='error')
1485 h.flash(_('Error occurred during commit'), category='error')
1482
1486
1483 raise HTTPFound(default_redirect_url)
1487 raise HTTPFound(default_redirect_url)
1484
1488
1485 @LoginRequired()
1489 @LoginRequired()
1486 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1490 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1487 @CSRFRequired()
1491 @CSRFRequired()
1488 @view_config(
1492 @view_config(
1489 route_name='repo_files_upload_file', request_method='POST',
1493 route_name='repo_files_upload_file', request_method='POST',
1490 renderer='json_ext')
1494 renderer='json_ext')
1491 def repo_files_upload_file(self):
1495 def repo_files_upload_file(self):
1492 _ = self.request.translate
1496 _ = self.request.translate
1493 c = self.load_default_context()
1497 c = self.load_default_context()
1494 commit_id, f_path = self._get_commit_and_path()
1498 commit_id, f_path = self._get_commit_and_path()
1495
1499
1496 self._ensure_not_locked()
1500 self._ensure_not_locked()
1497
1501
1498 c.commit = self._get_commit_or_redirect(commit_id, redirect_after=False)
1502 c.commit = self._get_commit_or_redirect(commit_id, redirect_after=False)
1499 if c.commit is None:
1503 if c.commit is None:
1500 c.commit = EmptyCommit(alias=self.rhodecode_vcs_repo.alias)
1504 c.commit = EmptyCommit(alias=self.rhodecode_vcs_repo.alias)
1501
1505
1502 # calculate redirect URL
1506 # calculate redirect URL
1503 if self.rhodecode_vcs_repo.is_empty():
1507 if self.rhodecode_vcs_repo.is_empty():
1504 default_redirect_url = h.route_path(
1508 default_redirect_url = h.route_path(
1505 'repo_summary', repo_name=self.db_repo_name)
1509 'repo_summary', repo_name=self.db_repo_name)
1506 else:
1510 else:
1507 default_redirect_url = h.route_path(
1511 default_redirect_url = h.route_path(
1508 'repo_commit', repo_name=self.db_repo_name, commit_id='tip')
1512 'repo_commit', repo_name=self.db_repo_name, commit_id='tip')
1509
1513
1510 if self.rhodecode_vcs_repo.is_empty():
1514 if self.rhodecode_vcs_repo.is_empty():
1511 # for empty repository we cannot check for current branch, we rely on
1515 # for empty repository we cannot check for current branch, we rely on
1512 # c.commit.branch instead
1516 # c.commit.branch instead
1513 _branch_name, _sha_commit_id, is_head = c.commit.branch, '', True
1517 _branch_name, _sha_commit_id, is_head = c.commit.branch, '', True
1514 else:
1518 else:
1515 _branch_name, _sha_commit_id, is_head = \
1519 _branch_name, _sha_commit_id, is_head = \
1516 self._is_valid_head(commit_id, self.rhodecode_vcs_repo)
1520 self._is_valid_head(commit_id, self.rhodecode_vcs_repo)
1517
1521
1518 error = self.forbid_non_head(is_head, f_path, json_mode=True)
1522 error = self.forbid_non_head(is_head, f_path, json_mode=True)
1519 if error:
1523 if error:
1520 return {
1524 return {
1521 'error': error,
1525 'error': error,
1522 'redirect_url': default_redirect_url
1526 'redirect_url': default_redirect_url
1523 }
1527 }
1524 error = self.check_branch_permission(_branch_name, json_mode=True)
1528 error = self.check_branch_permission(_branch_name, json_mode=True)
1525 if error:
1529 if error:
1526 return {
1530 return {
1527 'error': error,
1531 'error': error,
1528 'redirect_url': default_redirect_url
1532 'redirect_url': default_redirect_url
1529 }
1533 }
1530
1534
1531 c.default_message = (_('Uploaded file via RhodeCode Enterprise'))
1535 c.default_message = (_('Uploaded file via RhodeCode Enterprise'))
1532 c.f_path = f_path
1536 c.f_path = f_path
1533
1537
1534 r_post = self.request.POST
1538 r_post = self.request.POST
1535
1539
1536 message = c.default_message
1540 message = c.default_message
1537 user_message = r_post.getall('message')
1541 user_message = r_post.getall('message')
1538 if isinstance(user_message, list) and user_message:
1542 if isinstance(user_message, list) and user_message:
1539 # we take the first from duplicated results if it's not empty
1543 # we take the first from duplicated results if it's not empty
1540 message = user_message[0] if user_message[0] else message
1544 message = user_message[0] if user_message[0] else message
1541
1545
1542 nodes = {}
1546 nodes = {}
1543
1547
1544 for file_obj in r_post.getall('files_upload') or []:
1548 for file_obj in r_post.getall('files_upload') or []:
1545 content = file_obj.file
1549 content = file_obj.file
1546 filename = file_obj.filename
1550 filename = file_obj.filename
1547
1551
1548 root_path = f_path
1552 root_path = f_path
1549 pure_path = self.create_pure_path(root_path, filename)
1553 pure_path = self.create_pure_path(root_path, filename)
1550 node_path = safe_unicode(bytes(pure_path).lstrip('/'))
1554 node_path = safe_unicode(bytes(pure_path).lstrip('/'))
1551
1555
1552 nodes[node_path] = {
1556 nodes[node_path] = {
1553 'content': content
1557 'content': content
1554 }
1558 }
1555
1559
1556 if not nodes:
1560 if not nodes:
1557 error = 'missing files'
1561 error = 'missing files'
1558 return {
1562 return {
1559 'error': error,
1563 'error': error,
1560 'redirect_url': default_redirect_url
1564 'redirect_url': default_redirect_url
1561 }
1565 }
1562
1566
1563 author = self._rhodecode_db_user.full_contact
1567 author = self._rhodecode_db_user.full_contact
1564
1568
1565 try:
1569 try:
1566 commit = ScmModel().create_nodes(
1570 commit = ScmModel().create_nodes(
1567 user=self._rhodecode_db_user.user_id,
1571 user=self._rhodecode_db_user.user_id,
1568 repo=self.db_repo,
1572 repo=self.db_repo,
1569 message=message,
1573 message=message,
1570 nodes=nodes,
1574 nodes=nodes,
1571 parent_commit=c.commit,
1575 parent_commit=c.commit,
1572 author=author,
1576 author=author,
1573 )
1577 )
1574 if len(nodes) == 1:
1578 if len(nodes) == 1:
1575 flash_message = _('Successfully committed {} new files').format(len(nodes))
1579 flash_message = _('Successfully committed {} new files').format(len(nodes))
1576 else:
1580 else:
1577 flash_message = _('Successfully committed 1 new file')
1581 flash_message = _('Successfully committed 1 new file')
1578
1582
1579 h.flash(flash_message, category='success')
1583 h.flash(flash_message, category='success')
1580
1584
1581 default_redirect_url = h.route_path(
1585 default_redirect_url = h.route_path(
1582 'repo_commit', repo_name=self.db_repo_name, commit_id=commit.raw_id)
1586 'repo_commit', repo_name=self.db_repo_name, commit_id=commit.raw_id)
1583
1587
1584 except NonRelativePathError:
1588 except NonRelativePathError:
1585 log.exception('Non Relative path found')
1589 log.exception('Non Relative path found')
1586 error = _('The location specified must be a relative path and must not '
1590 error = _('The location specified must be a relative path and must not '
1587 'contain .. in the path')
1591 'contain .. in the path')
1588 h.flash(error, category='warning')
1592 h.flash(error, category='warning')
1589
1593
1590 return {
1594 return {
1591 'error': error,
1595 'error': error,
1592 'redirect_url': default_redirect_url
1596 'redirect_url': default_redirect_url
1593 }
1597 }
1594 except (NodeError, NodeAlreadyExistsError) as e:
1598 except (NodeError, NodeAlreadyExistsError) as e:
1595 error = h.escape(e)
1599 error = h.escape(e)
1596 h.flash(error, category='error')
1600 h.flash(error, category='error')
1597
1601
1598 return {
1602 return {
1599 'error': error,
1603 'error': error,
1600 'redirect_url': default_redirect_url
1604 'redirect_url': default_redirect_url
1601 }
1605 }
1602 except Exception:
1606 except Exception:
1603 log.exception('Error occurred during commit')
1607 log.exception('Error occurred during commit')
1604 error = _('Error occurred during commit')
1608 error = _('Error occurred during commit')
1605 h.flash(error, category='error')
1609 h.flash(error, category='error')
1606 return {
1610 return {
1607 'error': error,
1611 'error': error,
1608 'redirect_url': default_redirect_url
1612 'redirect_url': default_redirect_url
1609 }
1613 }
1610
1614
1611 return {
1615 return {
1612 'error': None,
1616 'error': None,
1613 'redirect_url': default_redirect_url
1617 'redirect_url': default_redirect_url
1614 }
1618 }
@@ -1,1974 +1,2029 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2020 RhodeCode GmbH
3 # Copyright (C) 2010-2020 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 webhelpers2.html import literal, HTML, escape
56 from webhelpers2.html import literal, HTML, escape
57 from webhelpers2.html._autolink import _auto_link_urls
57 from webhelpers2.html._autolink import _auto_link_urls
58 from webhelpers2.html.tools import (
58 from webhelpers2.html.tools import (
59 button_to, highlight, js_obfuscate, strip_links, strip_tags)
59 button_to, highlight, js_obfuscate, strip_links, strip_tags)
60
60
61 from webhelpers2.text import (
61 from webhelpers2.text import (
62 chop_at, collapse, convert_accented_entities,
62 chop_at, collapse, convert_accented_entities,
63 convert_misc_entities, lchop, plural, rchop, remove_formatting,
63 convert_misc_entities, lchop, plural, rchop, remove_formatting,
64 replace_whitespace, urlify, truncate, wrap_paragraphs)
64 replace_whitespace, urlify, truncate, wrap_paragraphs)
65 from webhelpers2.date import time_ago_in_words
65 from webhelpers2.date import time_ago_in_words
66
66
67 from webhelpers2.html.tags import (
67 from webhelpers2.html.tags import (
68 _input, NotGiven, _make_safe_id_component as safeid,
68 _input, NotGiven, _make_safe_id_component as safeid,
69 form as insecure_form,
69 form as insecure_form,
70 auto_discovery_link, checkbox, end_form, file,
70 auto_discovery_link, checkbox, end_form, file,
71 hidden, image, javascript_link, link_to, link_to_if, link_to_unless, ol,
71 hidden, image, javascript_link, link_to, link_to_if, link_to_unless, ol,
72 select as raw_select, stylesheet_link, submit, text, password, textarea,
72 select as raw_select, stylesheet_link, submit, text, password, textarea,
73 ul, radio, Options)
73 ul, radio, Options)
74
74
75 from webhelpers2.number import format_byte_size
75 from webhelpers2.number import format_byte_size
76
76
77 from rhodecode.lib.action_parser import action_parser
77 from rhodecode.lib.action_parser import action_parser
78 from rhodecode.lib.pagination import Page, RepoPage, SqlPage
78 from rhodecode.lib.pagination import Page, RepoPage, SqlPage
79 from rhodecode.lib.ext_json import json
79 from rhodecode.lib.ext_json import json
80 from rhodecode.lib.utils import repo_name_slug, get_custom_lexer
80 from rhodecode.lib.utils import repo_name_slug, get_custom_lexer
81 from rhodecode.lib.utils2 import (
81 from rhodecode.lib.utils2 import (
82 str2bool, safe_unicode, safe_str,
82 str2bool, safe_unicode, safe_str,
83 get_commit_safe, datetime_to_time, time_to_datetime, time_to_utcdatetime,
83 get_commit_safe, datetime_to_time, time_to_datetime, time_to_utcdatetime,
84 AttributeDict, safe_int, md5, md5_safe, get_host_info)
84 AttributeDict, safe_int, md5, md5_safe, get_host_info)
85 from rhodecode.lib.markup_renderer import MarkupRenderer, relative_links
85 from rhodecode.lib.markup_renderer import MarkupRenderer, relative_links
86 from rhodecode.lib.vcs.exceptions import CommitDoesNotExistError
86 from rhodecode.lib.vcs.exceptions import CommitDoesNotExistError
87 from rhodecode.lib.vcs.backends.base import BaseChangeset, EmptyCommit
87 from rhodecode.lib.vcs.backends.base import BaseChangeset, EmptyCommit
88 from rhodecode.lib.index.search_utils import get_matching_line_offsets
88 from rhodecode.lib.index.search_utils import get_matching_line_offsets
89 from rhodecode.config.conf import DATE_FORMAT, DATETIME_FORMAT
89 from rhodecode.config.conf import DATE_FORMAT, DATETIME_FORMAT
90 from rhodecode.model.changeset_status import ChangesetStatusModel
90 from rhodecode.model.changeset_status import ChangesetStatusModel
91 from rhodecode.model.db import Permission, User, Repository
91 from rhodecode.model.db import Permission, User, Repository
92 from rhodecode.model.repo_group import RepoGroupModel
92 from rhodecode.model.repo_group import RepoGroupModel
93 from rhodecode.model.settings import IssueTrackerSettingsModel
93 from rhodecode.model.settings import IssueTrackerSettingsModel
94
94
95
95
96 log = logging.getLogger(__name__)
96 log = logging.getLogger(__name__)
97
97
98
98
99 DEFAULT_USER = User.DEFAULT_USER
99 DEFAULT_USER = User.DEFAULT_USER
100 DEFAULT_USER_EMAIL = User.DEFAULT_USER_EMAIL
100 DEFAULT_USER_EMAIL = User.DEFAULT_USER_EMAIL
101
101
102
102
103 def asset(path, ver=None, **kwargs):
103 def asset(path, ver=None, **kwargs):
104 """
104 """
105 Helper to generate a static asset file path for rhodecode assets
105 Helper to generate a static asset file path for rhodecode assets
106
106
107 eg. h.asset('images/image.png', ver='3923')
107 eg. h.asset('images/image.png', ver='3923')
108
108
109 :param path: path of asset
109 :param path: path of asset
110 :param ver: optional version query param to append as ?ver=
110 :param ver: optional version query param to append as ?ver=
111 """
111 """
112 request = get_current_request()
112 request = get_current_request()
113 query = {}
113 query = {}
114 query.update(kwargs)
114 query.update(kwargs)
115 if ver:
115 if ver:
116 query = {'ver': ver}
116 query = {'ver': ver}
117 return request.static_path(
117 return request.static_path(
118 'rhodecode:public/{}'.format(path), _query=query)
118 'rhodecode:public/{}'.format(path), _query=query)
119
119
120
120
121 default_html_escape_table = {
121 default_html_escape_table = {
122 ord('&'): u'&amp;',
122 ord('&'): u'&amp;',
123 ord('<'): u'&lt;',
123 ord('<'): u'&lt;',
124 ord('>'): u'&gt;',
124 ord('>'): u'&gt;',
125 ord('"'): u'&quot;',
125 ord('"'): u'&quot;',
126 ord("'"): u'&#39;',
126 ord("'"): u'&#39;',
127 }
127 }
128
128
129
129
130 def html_escape(text, html_escape_table=default_html_escape_table):
130 def html_escape(text, html_escape_table=default_html_escape_table):
131 """Produce entities within text."""
131 """Produce entities within text."""
132 return text.translate(html_escape_table)
132 return text.translate(html_escape_table)
133
133
134
134
135 def chop_at_smart(s, sub, inclusive=False, suffix_if_chopped=None):
135 def chop_at_smart(s, sub, inclusive=False, suffix_if_chopped=None):
136 """
136 """
137 Truncate string ``s`` at the first occurrence of ``sub``.
137 Truncate string ``s`` at the first occurrence of ``sub``.
138
138
139 If ``inclusive`` is true, truncate just after ``sub`` rather than at it.
139 If ``inclusive`` is true, truncate just after ``sub`` rather than at it.
140 """
140 """
141 suffix_if_chopped = suffix_if_chopped or ''
141 suffix_if_chopped = suffix_if_chopped or ''
142 pos = s.find(sub)
142 pos = s.find(sub)
143 if pos == -1:
143 if pos == -1:
144 return s
144 return s
145
145
146 if inclusive:
146 if inclusive:
147 pos += len(sub)
147 pos += len(sub)
148
148
149 chopped = s[:pos]
149 chopped = s[:pos]
150 left = s[pos:].strip()
150 left = s[pos:].strip()
151
151
152 if left and suffix_if_chopped:
152 if left and suffix_if_chopped:
153 chopped += suffix_if_chopped
153 chopped += suffix_if_chopped
154
154
155 return chopped
155 return chopped
156
156
157
157
158 def shorter(text, size=20, prefix=False):
158 def shorter(text, size=20, prefix=False):
159 postfix = '...'
159 postfix = '...'
160 if len(text) > size:
160 if len(text) > size:
161 if prefix:
161 if prefix:
162 # shorten in front
162 # shorten in front
163 return postfix + text[-(size - len(postfix)):]
163 return postfix + text[-(size - len(postfix)):]
164 else:
164 else:
165 return text[:size - len(postfix)] + postfix
165 return text[:size - len(postfix)] + postfix
166 return text
166 return text
167
167
168
168
169 def reset(name, value=None, id=NotGiven, type="reset", **attrs):
169 def reset(name, value=None, id=NotGiven, type="reset", **attrs):
170 """
170 """
171 Reset button
171 Reset button
172 """
172 """
173 return _input(type, name, value, id, attrs)
173 return _input(type, name, value, id, attrs)
174
174
175
175
176 def select(name, selected_values, options, id=NotGiven, **attrs):
176 def select(name, selected_values, options, id=NotGiven, **attrs):
177
177
178 if isinstance(options, (list, tuple)):
178 if isinstance(options, (list, tuple)):
179 options_iter = options
179 options_iter = options
180 # Handle old value,label lists ... where value also can be value,label lists
180 # Handle old value,label lists ... where value also can be value,label lists
181 options = Options()
181 options = Options()
182 for opt in options_iter:
182 for opt in options_iter:
183 if isinstance(opt, tuple) and len(opt) == 2:
183 if isinstance(opt, tuple) and len(opt) == 2:
184 value, label = opt
184 value, label = opt
185 elif isinstance(opt, basestring):
185 elif isinstance(opt, basestring):
186 value = label = opt
186 value = label = opt
187 else:
187 else:
188 raise ValueError('invalid select option type %r' % type(opt))
188 raise ValueError('invalid select option type %r' % type(opt))
189
189
190 if isinstance(value, (list, tuple)):
190 if isinstance(value, (list, tuple)):
191 option_group = options.add_optgroup(label)
191 option_group = options.add_optgroup(label)
192 for opt2 in value:
192 for opt2 in value:
193 if isinstance(opt2, tuple) and len(opt2) == 2:
193 if isinstance(opt2, tuple) and len(opt2) == 2:
194 group_value, group_label = opt2
194 group_value, group_label = opt2
195 elif isinstance(opt2, basestring):
195 elif isinstance(opt2, basestring):
196 group_value = group_label = opt2
196 group_value = group_label = opt2
197 else:
197 else:
198 raise ValueError('invalid select option type %r' % type(opt2))
198 raise ValueError('invalid select option type %r' % type(opt2))
199
199
200 option_group.add_option(group_label, group_value)
200 option_group.add_option(group_label, group_value)
201 else:
201 else:
202 options.add_option(label, value)
202 options.add_option(label, value)
203
203
204 return raw_select(name, selected_values, options, id=id, **attrs)
204 return raw_select(name, selected_values, options, id=id, **attrs)
205
205
206
206
207 def branding(name, length=40):
207 def branding(name, length=40):
208 return truncate(name, length, indicator="")
208 return truncate(name, length, indicator="")
209
209
210
210
211 def FID(raw_id, path):
211 def FID(raw_id, path):
212 """
212 """
213 Creates a unique ID for filenode based on it's hash of path and commit
213 Creates a unique ID for filenode based on it's hash of path and commit
214 it's safe to use in urls
214 it's safe to use in urls
215
215
216 :param raw_id:
216 :param raw_id:
217 :param path:
217 :param path:
218 """
218 """
219
219
220 return 'c-%s-%s' % (short_id(raw_id), md5_safe(path)[:12])
220 return 'c-%s-%s' % (short_id(raw_id), md5_safe(path)[:12])
221
221
222
222
223 class _GetError(object):
223 class _GetError(object):
224 """Get error from form_errors, and represent it as span wrapped error
224 """Get error from form_errors, and represent it as span wrapped error
225 message
225 message
226
226
227 :param field_name: field to fetch errors for
227 :param field_name: field to fetch errors for
228 :param form_errors: form errors dict
228 :param form_errors: form errors dict
229 """
229 """
230
230
231 def __call__(self, field_name, form_errors):
231 def __call__(self, field_name, form_errors):
232 tmpl = """<span class="error_msg">%s</span>"""
232 tmpl = """<span class="error_msg">%s</span>"""
233 if form_errors and field_name in form_errors:
233 if form_errors and field_name in form_errors:
234 return literal(tmpl % form_errors.get(field_name))
234 return literal(tmpl % form_errors.get(field_name))
235
235
236
236
237 get_error = _GetError()
237 get_error = _GetError()
238
238
239
239
240 class _ToolTip(object):
240 class _ToolTip(object):
241
241
242 def __call__(self, tooltip_title, trim_at=50):
242 def __call__(self, tooltip_title, trim_at=50):
243 """
243 """
244 Special function just to wrap our text into nice formatted
244 Special function just to wrap our text into nice formatted
245 autowrapped text
245 autowrapped text
246
246
247 :param tooltip_title:
247 :param tooltip_title:
248 """
248 """
249 tooltip_title = escape(tooltip_title)
249 tooltip_title = escape(tooltip_title)
250 tooltip_title = tooltip_title.replace('<', '&lt;').replace('>', '&gt;')
250 tooltip_title = tooltip_title.replace('<', '&lt;').replace('>', '&gt;')
251 return tooltip_title
251 return tooltip_title
252
252
253
253
254 tooltip = _ToolTip()
254 tooltip = _ToolTip()
255
255
256 files_icon = u'<i class="file-breadcrumb-copy tooltip icon-clipboard clipboard-action" data-clipboard-text="{}" title="Copy file path"></i>'
256 files_icon = u'<i class="file-breadcrumb-copy tooltip icon-clipboard clipboard-action" data-clipboard-text="{}" title="Copy file path"></i>'
257
257
258
258
259 def files_breadcrumbs(repo_name, commit_id, file_path, landing_ref_name=None, at_ref=None,
259 def files_breadcrumbs(repo_name, repo_type, commit_id, file_path, landing_ref_name=None, at_ref=None,
260 limit_items=False, linkify_last_item=False, hide_last_item=False, copy_path_icon=True):
260 limit_items=False, linkify_last_item=False, hide_last_item=False,
261 copy_path_icon=True):
261 if isinstance(file_path, str):
262 if isinstance(file_path, str):
262 file_path = safe_unicode(file_path)
263 file_path = safe_unicode(file_path)
263
264
264 if at_ref:
265 if at_ref:
265 route_qry = {'at': at_ref}
266 route_qry = {'at': at_ref}
266 default_commit_id = at_ref or landing_ref_name or commit_id
267 default_landing_ref = at_ref or landing_ref_name or commit_id
267 else:
268 else:
268 route_qry = None
269 route_qry = None
269 default_commit_id = commit_id
270 default_landing_ref = commit_id
270
271
271 # first segment is a `..` link to repo files
272 # first segment is a `HOME` link to repo files root location
272 root_name = literal(u'<i class="icon-home"></i>')
273 root_name = literal(u'<i class="icon-home"></i>')
274
273 url_segments = [
275 url_segments = [
274 link_to(
276 link_to(
275 root_name,
277 root_name,
276 route_path(
278 repo_files_by_ref_url(
277 'repo_files',
279 repo_name,
278 repo_name=repo_name,
280 repo_type,
279 commit_id=default_commit_id,
281 f_path=None, # None here is a special case for SVN repos,
280 f_path='',
282 # that won't prefix with a ref
281 _query=route_qry),
283 ref_name=default_landing_ref,
284 commit_id=commit_id,
285 query=route_qry
286 )
282 )]
287 )]
283
288
284 path_segments = file_path.split('/')
289 path_segments = file_path.split('/')
285 last_cnt = len(path_segments) - 1
290 last_cnt = len(path_segments) - 1
286 for cnt, segment in enumerate(path_segments):
291 for cnt, segment in enumerate(path_segments):
287 if not segment:
292 if not segment:
288 continue
293 continue
289 segment_html = escape(segment)
294 segment_html = escape(segment)
290
295
291 last_item = cnt == last_cnt
296 last_item = cnt == last_cnt
292
297
293 if last_item and hide_last_item:
298 if last_item and hide_last_item:
294 # iterate over and hide last element
299 # iterate over and hide last element
295 continue
300 continue
296
301
297 if last_item and linkify_last_item is False:
302 if last_item and linkify_last_item is False:
298 # plain version
303 # plain version
299 url_segments.append(segment_html)
304 url_segments.append(segment_html)
300 else:
305 else:
301 url_segments.append(
306 url_segments.append(
302 link_to(
307 link_to(
303 segment_html,
308 segment_html,
304 route_path(
309 repo_files_by_ref_url(
305 'repo_files',
310 repo_name,
306 repo_name=repo_name,
311 repo_type,
307 commit_id=default_commit_id,
308 f_path='/'.join(path_segments[:cnt + 1]),
312 f_path='/'.join(path_segments[:cnt + 1]),
309 _query=route_qry),
313 ref_name=default_landing_ref,
314 commit_id=commit_id,
315 query=route_qry
316 ),
310 ))
317 ))
311
318
312 limited_url_segments = url_segments[:1] + ['...'] + url_segments[-5:]
319 limited_url_segments = url_segments[:1] + ['...'] + url_segments[-5:]
313 if limit_items and len(limited_url_segments) < len(url_segments):
320 if limit_items and len(limited_url_segments) < len(url_segments):
314 url_segments = limited_url_segments
321 url_segments = limited_url_segments
315
322
316 full_path = file_path
323 full_path = file_path
317 if copy_path_icon:
324 if copy_path_icon:
318 icon = files_icon.format(escape(full_path))
325 icon = files_icon.format(escape(full_path))
319 else:
326 else:
320 icon = ''
327 icon = ''
321
328
322 if file_path == '':
329 if file_path == '':
323 return root_name
330 return root_name
324 else:
331 else:
325 return literal(' / '.join(url_segments) + icon)
332 return literal(' / '.join(url_segments) + icon)
326
333
327
334
328 def files_url_data(request):
335 def files_url_data(request):
329 matchdict = request.matchdict
336 matchdict = request.matchdict
330
337
331 if 'f_path' not in matchdict:
338 if 'f_path' not in matchdict:
332 matchdict['f_path'] = ''
339 matchdict['f_path'] = ''
333
340
334 if 'commit_id' not in matchdict:
341 if 'commit_id' not in matchdict:
335 matchdict['commit_id'] = 'tip'
342 matchdict['commit_id'] = 'tip'
336
343
337 return json.dumps(matchdict)
344 return json.dumps(matchdict)
338
345
339
346
347 def repo_files_by_ref_url(db_repo_name, db_repo_type, f_path, ref_name, commit_id, query=None, ):
348 _is_svn = is_svn(db_repo_type)
349 final_f_path = f_path
350
351 if _is_svn:
352 """
353 For SVN the ref_name cannot be used as a commit_id, it needs to be prefixed with
354 actually commit_id followed by the ref_name. This should be done only in case
355 This is a initial landing url, without additional paths.
356
357 like: /1000/tags/1.0.0/?at=tags/1.0.0
358 """
359
360 if ref_name and ref_name != 'tip':
361 # NOTE(marcink): for svn the ref_name is actually the stored path, so we prefix it
362 # for SVN we only do this magic prefix if it's root, .eg landing revision
363 # of files link. If we are in the tree we don't need this since we traverse the url
364 # that has everything stored
365 if f_path in ['', '/']:
366 final_f_path = '/'.join([ref_name, f_path])
367
368 # SVN always needs a commit_id explicitly, without a named REF
369 default_commit_id = commit_id
370 else:
371 """
372 For git and mercurial we construct a new URL using the names instead of commit_id
373 like: /master/some_path?at=master
374 """
375 # We currently do not support branches with slashes
376 if '/' in ref_name:
377 default_commit_id = commit_id
378 else:
379 default_commit_id = ref_name
380
381 # sometimes we pass f_path as None, to indicate explicit no prefix,
382 # we translate it to string to not have None
383 final_f_path = final_f_path or ''
384
385 files_url = route_path(
386 'repo_files',
387 repo_name=db_repo_name,
388 commit_id=default_commit_id,
389 f_path=final_f_path,
390 _query=query
391 )
392 return files_url
393
394
340 def code_highlight(code, lexer, formatter, use_hl_filter=False):
395 def code_highlight(code, lexer, formatter, use_hl_filter=False):
341 """
396 """
342 Lex ``code`` with ``lexer`` and format it with the formatter ``formatter``.
397 Lex ``code`` with ``lexer`` and format it with the formatter ``formatter``.
343
398
344 If ``outfile`` is given and a valid file object (an object
399 If ``outfile`` is given and a valid file object (an object
345 with a ``write`` method), the result will be written to it, otherwise
400 with a ``write`` method), the result will be written to it, otherwise
346 it is returned as a string.
401 it is returned as a string.
347 """
402 """
348 if use_hl_filter:
403 if use_hl_filter:
349 # add HL filter
404 # add HL filter
350 from rhodecode.lib.index import search_utils
405 from rhodecode.lib.index import search_utils
351 lexer.add_filter(search_utils.ElasticSearchHLFilter())
406 lexer.add_filter(search_utils.ElasticSearchHLFilter())
352 return pygments.format(pygments.lex(code, lexer), formatter)
407 return pygments.format(pygments.lex(code, lexer), formatter)
353
408
354
409
355 class CodeHtmlFormatter(HtmlFormatter):
410 class CodeHtmlFormatter(HtmlFormatter):
356 """
411 """
357 My code Html Formatter for source codes
412 My code Html Formatter for source codes
358 """
413 """
359
414
360 def wrap(self, source, outfile):
415 def wrap(self, source, outfile):
361 return self._wrap_div(self._wrap_pre(self._wrap_code(source)))
416 return self._wrap_div(self._wrap_pre(self._wrap_code(source)))
362
417
363 def _wrap_code(self, source):
418 def _wrap_code(self, source):
364 for cnt, it in enumerate(source):
419 for cnt, it in enumerate(source):
365 i, t = it
420 i, t = it
366 t = '<div id="L%s">%s</div>' % (cnt + 1, t)
421 t = '<div id="L%s">%s</div>' % (cnt + 1, t)
367 yield i, t
422 yield i, t
368
423
369 def _wrap_tablelinenos(self, inner):
424 def _wrap_tablelinenos(self, inner):
370 dummyoutfile = StringIO.StringIO()
425 dummyoutfile = StringIO.StringIO()
371 lncount = 0
426 lncount = 0
372 for t, line in inner:
427 for t, line in inner:
373 if t:
428 if t:
374 lncount += 1
429 lncount += 1
375 dummyoutfile.write(line)
430 dummyoutfile.write(line)
376
431
377 fl = self.linenostart
432 fl = self.linenostart
378 mw = len(str(lncount + fl - 1))
433 mw = len(str(lncount + fl - 1))
379 sp = self.linenospecial
434 sp = self.linenospecial
380 st = self.linenostep
435 st = self.linenostep
381 la = self.lineanchors
436 la = self.lineanchors
382 aln = self.anchorlinenos
437 aln = self.anchorlinenos
383 nocls = self.noclasses
438 nocls = self.noclasses
384 if sp:
439 if sp:
385 lines = []
440 lines = []
386
441
387 for i in range(fl, fl + lncount):
442 for i in range(fl, fl + lncount):
388 if i % st == 0:
443 if i % st == 0:
389 if i % sp == 0:
444 if i % sp == 0:
390 if aln:
445 if aln:
391 lines.append('<a href="#%s%d" class="special">%*d</a>' %
446 lines.append('<a href="#%s%d" class="special">%*d</a>' %
392 (la, i, mw, i))
447 (la, i, mw, i))
393 else:
448 else:
394 lines.append('<span class="special">%*d</span>' % (mw, i))
449 lines.append('<span class="special">%*d</span>' % (mw, i))
395 else:
450 else:
396 if aln:
451 if aln:
397 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
452 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
398 else:
453 else:
399 lines.append('%*d' % (mw, i))
454 lines.append('%*d' % (mw, i))
400 else:
455 else:
401 lines.append('')
456 lines.append('')
402 ls = '\n'.join(lines)
457 ls = '\n'.join(lines)
403 else:
458 else:
404 lines = []
459 lines = []
405 for i in range(fl, fl + lncount):
460 for i in range(fl, fl + lncount):
406 if i % st == 0:
461 if i % st == 0:
407 if aln:
462 if aln:
408 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
463 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
409 else:
464 else:
410 lines.append('%*d' % (mw, i))
465 lines.append('%*d' % (mw, i))
411 else:
466 else:
412 lines.append('')
467 lines.append('')
413 ls = '\n'.join(lines)
468 ls = '\n'.join(lines)
414
469
415 # in case you wonder about the seemingly redundant <div> here: since the
470 # in case you wonder about the seemingly redundant <div> here: since the
416 # content in the other cell also is wrapped in a div, some browsers in
471 # content in the other cell also is wrapped in a div, some browsers in
417 # some configurations seem to mess up the formatting...
472 # some configurations seem to mess up the formatting...
418 if nocls:
473 if nocls:
419 yield 0, ('<table class="%stable">' % self.cssclass +
474 yield 0, ('<table class="%stable">' % self.cssclass +
420 '<tr><td><div class="linenodiv" '
475 '<tr><td><div class="linenodiv" '
421 'style="background-color: #f0f0f0; padding-right: 10px">'
476 'style="background-color: #f0f0f0; padding-right: 10px">'
422 '<pre style="line-height: 125%">' +
477 '<pre style="line-height: 125%">' +
423 ls + '</pre></div></td><td id="hlcode" class="code">')
478 ls + '</pre></div></td><td id="hlcode" class="code">')
424 else:
479 else:
425 yield 0, ('<table class="%stable">' % self.cssclass +
480 yield 0, ('<table class="%stable">' % self.cssclass +
426 '<tr><td class="linenos"><div class="linenodiv"><pre>' +
481 '<tr><td class="linenos"><div class="linenodiv"><pre>' +
427 ls + '</pre></div></td><td id="hlcode" class="code">')
482 ls + '</pre></div></td><td id="hlcode" class="code">')
428 yield 0, dummyoutfile.getvalue()
483 yield 0, dummyoutfile.getvalue()
429 yield 0, '</td></tr></table>'
484 yield 0, '</td></tr></table>'
430
485
431
486
432 class SearchContentCodeHtmlFormatter(CodeHtmlFormatter):
487 class SearchContentCodeHtmlFormatter(CodeHtmlFormatter):
433 def __init__(self, **kw):
488 def __init__(self, **kw):
434 # only show these line numbers if set
489 # only show these line numbers if set
435 self.only_lines = kw.pop('only_line_numbers', [])
490 self.only_lines = kw.pop('only_line_numbers', [])
436 self.query_terms = kw.pop('query_terms', [])
491 self.query_terms = kw.pop('query_terms', [])
437 self.max_lines = kw.pop('max_lines', 5)
492 self.max_lines = kw.pop('max_lines', 5)
438 self.line_context = kw.pop('line_context', 3)
493 self.line_context = kw.pop('line_context', 3)
439 self.url = kw.pop('url', None)
494 self.url = kw.pop('url', None)
440
495
441 super(CodeHtmlFormatter, self).__init__(**kw)
496 super(CodeHtmlFormatter, self).__init__(**kw)
442
497
443 def _wrap_code(self, source):
498 def _wrap_code(self, source):
444 for cnt, it in enumerate(source):
499 for cnt, it in enumerate(source):
445 i, t = it
500 i, t = it
446 t = '<pre>%s</pre>' % t
501 t = '<pre>%s</pre>' % t
447 yield i, t
502 yield i, t
448
503
449 def _wrap_tablelinenos(self, inner):
504 def _wrap_tablelinenos(self, inner):
450 yield 0, '<table class="code-highlight %stable">' % self.cssclass
505 yield 0, '<table class="code-highlight %stable">' % self.cssclass
451
506
452 last_shown_line_number = 0
507 last_shown_line_number = 0
453 current_line_number = 1
508 current_line_number = 1
454
509
455 for t, line in inner:
510 for t, line in inner:
456 if not t:
511 if not t:
457 yield t, line
512 yield t, line
458 continue
513 continue
459
514
460 if current_line_number in self.only_lines:
515 if current_line_number in self.only_lines:
461 if last_shown_line_number + 1 != current_line_number:
516 if last_shown_line_number + 1 != current_line_number:
462 yield 0, '<tr>'
517 yield 0, '<tr>'
463 yield 0, '<td class="line">...</td>'
518 yield 0, '<td class="line">...</td>'
464 yield 0, '<td id="hlcode" class="code"></td>'
519 yield 0, '<td id="hlcode" class="code"></td>'
465 yield 0, '</tr>'
520 yield 0, '</tr>'
466
521
467 yield 0, '<tr>'
522 yield 0, '<tr>'
468 if self.url:
523 if self.url:
469 yield 0, '<td class="line"><a href="%s#L%i">%i</a></td>' % (
524 yield 0, '<td class="line"><a href="%s#L%i">%i</a></td>' % (
470 self.url, current_line_number, current_line_number)
525 self.url, current_line_number, current_line_number)
471 else:
526 else:
472 yield 0, '<td class="line"><a href="">%i</a></td>' % (
527 yield 0, '<td class="line"><a href="">%i</a></td>' % (
473 current_line_number)
528 current_line_number)
474 yield 0, '<td id="hlcode" class="code">' + line + '</td>'
529 yield 0, '<td id="hlcode" class="code">' + line + '</td>'
475 yield 0, '</tr>'
530 yield 0, '</tr>'
476
531
477 last_shown_line_number = current_line_number
532 last_shown_line_number = current_line_number
478
533
479 current_line_number += 1
534 current_line_number += 1
480
535
481 yield 0, '</table>'
536 yield 0, '</table>'
482
537
483
538
484 def hsv_to_rgb(h, s, v):
539 def hsv_to_rgb(h, s, v):
485 """ Convert hsv color values to rgb """
540 """ Convert hsv color values to rgb """
486
541
487 if s == 0.0:
542 if s == 0.0:
488 return v, v, v
543 return v, v, v
489 i = int(h * 6.0) # XXX assume int() truncates!
544 i = int(h * 6.0) # XXX assume int() truncates!
490 f = (h * 6.0) - i
545 f = (h * 6.0) - i
491 p = v * (1.0 - s)
546 p = v * (1.0 - s)
492 q = v * (1.0 - s * f)
547 q = v * (1.0 - s * f)
493 t = v * (1.0 - s * (1.0 - f))
548 t = v * (1.0 - s * (1.0 - f))
494 i = i % 6
549 i = i % 6
495 if i == 0:
550 if i == 0:
496 return v, t, p
551 return v, t, p
497 if i == 1:
552 if i == 1:
498 return q, v, p
553 return q, v, p
499 if i == 2:
554 if i == 2:
500 return p, v, t
555 return p, v, t
501 if i == 3:
556 if i == 3:
502 return p, q, v
557 return p, q, v
503 if i == 4:
558 if i == 4:
504 return t, p, v
559 return t, p, v
505 if i == 5:
560 if i == 5:
506 return v, p, q
561 return v, p, q
507
562
508
563
509 def unique_color_generator(n=10000, saturation=0.10, lightness=0.95):
564 def unique_color_generator(n=10000, saturation=0.10, lightness=0.95):
510 """
565 """
511 Generator for getting n of evenly distributed colors using
566 Generator for getting n of evenly distributed colors using
512 hsv color and golden ratio. It always return same order of colors
567 hsv color and golden ratio. It always return same order of colors
513
568
514 :param n: number of colors to generate
569 :param n: number of colors to generate
515 :param saturation: saturation of returned colors
570 :param saturation: saturation of returned colors
516 :param lightness: lightness of returned colors
571 :param lightness: lightness of returned colors
517 :returns: RGB tuple
572 :returns: RGB tuple
518 """
573 """
519
574
520 golden_ratio = 0.618033988749895
575 golden_ratio = 0.618033988749895
521 h = 0.22717784590367374
576 h = 0.22717784590367374
522
577
523 for _ in xrange(n):
578 for _ in xrange(n):
524 h += golden_ratio
579 h += golden_ratio
525 h %= 1
580 h %= 1
526 HSV_tuple = [h, saturation, lightness]
581 HSV_tuple = [h, saturation, lightness]
527 RGB_tuple = hsv_to_rgb(*HSV_tuple)
582 RGB_tuple = hsv_to_rgb(*HSV_tuple)
528 yield map(lambda x: str(int(x * 256)), RGB_tuple)
583 yield map(lambda x: str(int(x * 256)), RGB_tuple)
529
584
530
585
531 def color_hasher(n=10000, saturation=0.10, lightness=0.95):
586 def color_hasher(n=10000, saturation=0.10, lightness=0.95):
532 """
587 """
533 Returns a function which when called with an argument returns a unique
588 Returns a function which when called with an argument returns a unique
534 color for that argument, eg.
589 color for that argument, eg.
535
590
536 :param n: number of colors to generate
591 :param n: number of colors to generate
537 :param saturation: saturation of returned colors
592 :param saturation: saturation of returned colors
538 :param lightness: lightness of returned colors
593 :param lightness: lightness of returned colors
539 :returns: css RGB string
594 :returns: css RGB string
540
595
541 >>> color_hash = color_hasher()
596 >>> color_hash = color_hasher()
542 >>> color_hash('hello')
597 >>> color_hash('hello')
543 'rgb(34, 12, 59)'
598 'rgb(34, 12, 59)'
544 >>> color_hash('hello')
599 >>> color_hash('hello')
545 'rgb(34, 12, 59)'
600 'rgb(34, 12, 59)'
546 >>> color_hash('other')
601 >>> color_hash('other')
547 'rgb(90, 224, 159)'
602 'rgb(90, 224, 159)'
548 """
603 """
549
604
550 color_dict = {}
605 color_dict = {}
551 cgenerator = unique_color_generator(
606 cgenerator = unique_color_generator(
552 saturation=saturation, lightness=lightness)
607 saturation=saturation, lightness=lightness)
553
608
554 def get_color_string(thing):
609 def get_color_string(thing):
555 if thing in color_dict:
610 if thing in color_dict:
556 col = color_dict[thing]
611 col = color_dict[thing]
557 else:
612 else:
558 col = color_dict[thing] = cgenerator.next()
613 col = color_dict[thing] = cgenerator.next()
559 return "rgb(%s)" % (', '.join(col))
614 return "rgb(%s)" % (', '.join(col))
560
615
561 return get_color_string
616 return get_color_string
562
617
563
618
564 def get_lexer_safe(mimetype=None, filepath=None):
619 def get_lexer_safe(mimetype=None, filepath=None):
565 """
620 """
566 Tries to return a relevant pygments lexer using mimetype/filepath name,
621 Tries to return a relevant pygments lexer using mimetype/filepath name,
567 defaulting to plain text if none could be found
622 defaulting to plain text if none could be found
568 """
623 """
569 lexer = None
624 lexer = None
570 try:
625 try:
571 if mimetype:
626 if mimetype:
572 lexer = get_lexer_for_mimetype(mimetype)
627 lexer = get_lexer_for_mimetype(mimetype)
573 if not lexer:
628 if not lexer:
574 lexer = get_lexer_for_filename(filepath)
629 lexer = get_lexer_for_filename(filepath)
575 except pygments.util.ClassNotFound:
630 except pygments.util.ClassNotFound:
576 pass
631 pass
577
632
578 if not lexer:
633 if not lexer:
579 lexer = get_lexer_by_name('text')
634 lexer = get_lexer_by_name('text')
580
635
581 return lexer
636 return lexer
582
637
583
638
584 def get_lexer_for_filenode(filenode):
639 def get_lexer_for_filenode(filenode):
585 lexer = get_custom_lexer(filenode.extension) or filenode.lexer
640 lexer = get_custom_lexer(filenode.extension) or filenode.lexer
586 return lexer
641 return lexer
587
642
588
643
589 def pygmentize(filenode, **kwargs):
644 def pygmentize(filenode, **kwargs):
590 """
645 """
591 pygmentize function using pygments
646 pygmentize function using pygments
592
647
593 :param filenode:
648 :param filenode:
594 """
649 """
595 lexer = get_lexer_for_filenode(filenode)
650 lexer = get_lexer_for_filenode(filenode)
596 return literal(code_highlight(filenode.content, lexer,
651 return literal(code_highlight(filenode.content, lexer,
597 CodeHtmlFormatter(**kwargs)))
652 CodeHtmlFormatter(**kwargs)))
598
653
599
654
600 def is_following_repo(repo_name, user_id):
655 def is_following_repo(repo_name, user_id):
601 from rhodecode.model.scm import ScmModel
656 from rhodecode.model.scm import ScmModel
602 return ScmModel().is_following_repo(repo_name, user_id)
657 return ScmModel().is_following_repo(repo_name, user_id)
603
658
604
659
605 class _Message(object):
660 class _Message(object):
606 """A message returned by ``Flash.pop_messages()``.
661 """A message returned by ``Flash.pop_messages()``.
607
662
608 Converting the message to a string returns the message text. Instances
663 Converting the message to a string returns the message text. Instances
609 also have the following attributes:
664 also have the following attributes:
610
665
611 * ``message``: the message text.
666 * ``message``: the message text.
612 * ``category``: the category specified when the message was created.
667 * ``category``: the category specified when the message was created.
613 """
668 """
614
669
615 def __init__(self, category, message, sub_data=None):
670 def __init__(self, category, message, sub_data=None):
616 self.category = category
671 self.category = category
617 self.message = message
672 self.message = message
618 self.sub_data = sub_data or {}
673 self.sub_data = sub_data or {}
619
674
620 def __str__(self):
675 def __str__(self):
621 return self.message
676 return self.message
622
677
623 __unicode__ = __str__
678 __unicode__ = __str__
624
679
625 def __html__(self):
680 def __html__(self):
626 return escape(safe_unicode(self.message))
681 return escape(safe_unicode(self.message))
627
682
628
683
629 class Flash(object):
684 class Flash(object):
630 # List of allowed categories. If None, allow any category.
685 # List of allowed categories. If None, allow any category.
631 categories = ["warning", "notice", "error", "success"]
686 categories = ["warning", "notice", "error", "success"]
632
687
633 # Default category if none is specified.
688 # Default category if none is specified.
634 default_category = "notice"
689 default_category = "notice"
635
690
636 def __init__(self, session_key="flash", categories=None,
691 def __init__(self, session_key="flash", categories=None,
637 default_category=None):
692 default_category=None):
638 """
693 """
639 Instantiate a ``Flash`` object.
694 Instantiate a ``Flash`` object.
640
695
641 ``session_key`` is the key to save the messages under in the user's
696 ``session_key`` is the key to save the messages under in the user's
642 session.
697 session.
643
698
644 ``categories`` is an optional list which overrides the default list
699 ``categories`` is an optional list which overrides the default list
645 of categories.
700 of categories.
646
701
647 ``default_category`` overrides the default category used for messages
702 ``default_category`` overrides the default category used for messages
648 when none is specified.
703 when none is specified.
649 """
704 """
650 self.session_key = session_key
705 self.session_key = session_key
651 if categories is not None:
706 if categories is not None:
652 self.categories = categories
707 self.categories = categories
653 if default_category is not None:
708 if default_category is not None:
654 self.default_category = default_category
709 self.default_category = default_category
655 if self.categories and self.default_category not in self.categories:
710 if self.categories and self.default_category not in self.categories:
656 raise ValueError(
711 raise ValueError(
657 "unrecognized default category %r" % (self.default_category,))
712 "unrecognized default category %r" % (self.default_category,))
658
713
659 def pop_messages(self, session=None, request=None):
714 def pop_messages(self, session=None, request=None):
660 """
715 """
661 Return all accumulated messages and delete them from the session.
716 Return all accumulated messages and delete them from the session.
662
717
663 The return value is a list of ``Message`` objects.
718 The return value is a list of ``Message`` objects.
664 """
719 """
665 messages = []
720 messages = []
666
721
667 if not session:
722 if not session:
668 if not request:
723 if not request:
669 request = get_current_request()
724 request = get_current_request()
670 session = request.session
725 session = request.session
671
726
672 # Pop the 'old' pylons flash messages. They are tuples of the form
727 # Pop the 'old' pylons flash messages. They are tuples of the form
673 # (category, message)
728 # (category, message)
674 for cat, msg in session.pop(self.session_key, []):
729 for cat, msg in session.pop(self.session_key, []):
675 messages.append(_Message(cat, msg))
730 messages.append(_Message(cat, msg))
676
731
677 # Pop the 'new' pyramid flash messages for each category as list
732 # Pop the 'new' pyramid flash messages for each category as list
678 # of strings.
733 # of strings.
679 for cat in self.categories:
734 for cat in self.categories:
680 for msg in session.pop_flash(queue=cat):
735 for msg in session.pop_flash(queue=cat):
681 sub_data = {}
736 sub_data = {}
682 if hasattr(msg, 'rsplit'):
737 if hasattr(msg, 'rsplit'):
683 flash_data = msg.rsplit('|DELIM|', 1)
738 flash_data = msg.rsplit('|DELIM|', 1)
684 org_message = flash_data[0]
739 org_message = flash_data[0]
685 if len(flash_data) > 1:
740 if len(flash_data) > 1:
686 sub_data = json.loads(flash_data[1])
741 sub_data = json.loads(flash_data[1])
687 else:
742 else:
688 org_message = msg
743 org_message = msg
689
744
690 messages.append(_Message(cat, org_message, sub_data=sub_data))
745 messages.append(_Message(cat, org_message, sub_data=sub_data))
691
746
692 # Map messages from the default queue to the 'notice' category.
747 # Map messages from the default queue to the 'notice' category.
693 for msg in session.pop_flash():
748 for msg in session.pop_flash():
694 messages.append(_Message('notice', msg))
749 messages.append(_Message('notice', msg))
695
750
696 session.save()
751 session.save()
697 return messages
752 return messages
698
753
699 def json_alerts(self, session=None, request=None):
754 def json_alerts(self, session=None, request=None):
700 payloads = []
755 payloads = []
701 messages = flash.pop_messages(session=session, request=request) or []
756 messages = flash.pop_messages(session=session, request=request) or []
702 for message in messages:
757 for message in messages:
703 payloads.append({
758 payloads.append({
704 'message': {
759 'message': {
705 'message': u'{}'.format(message.message),
760 'message': u'{}'.format(message.message),
706 'level': message.category,
761 'level': message.category,
707 'force': True,
762 'force': True,
708 'subdata': message.sub_data
763 'subdata': message.sub_data
709 }
764 }
710 })
765 })
711 return json.dumps(payloads)
766 return json.dumps(payloads)
712
767
713 def __call__(self, message, category=None, ignore_duplicate=True,
768 def __call__(self, message, category=None, ignore_duplicate=True,
714 session=None, request=None):
769 session=None, request=None):
715
770
716 if not session:
771 if not session:
717 if not request:
772 if not request:
718 request = get_current_request()
773 request = get_current_request()
719 session = request.session
774 session = request.session
720
775
721 session.flash(
776 session.flash(
722 message, queue=category, allow_duplicate=not ignore_duplicate)
777 message, queue=category, allow_duplicate=not ignore_duplicate)
723
778
724
779
725 flash = Flash()
780 flash = Flash()
726
781
727 #==============================================================================
782 #==============================================================================
728 # SCM FILTERS available via h.
783 # SCM FILTERS available via h.
729 #==============================================================================
784 #==============================================================================
730 from rhodecode.lib.vcs.utils import author_name, author_email
785 from rhodecode.lib.vcs.utils import author_name, author_email
731 from rhodecode.lib.utils2 import credentials_filter, age, age_from_seconds
786 from rhodecode.lib.utils2 import credentials_filter, age, age_from_seconds
732 from rhodecode.model.db import User, ChangesetStatus
787 from rhodecode.model.db import User, ChangesetStatus
733
788
734 capitalize = lambda x: x.capitalize()
789 capitalize = lambda x: x.capitalize()
735 email = author_email
790 email = author_email
736 short_id = lambda x: x[:12]
791 short_id = lambda x: x[:12]
737 hide_credentials = lambda x: ''.join(credentials_filter(x))
792 hide_credentials = lambda x: ''.join(credentials_filter(x))
738
793
739
794
740 import pytz
795 import pytz
741 import tzlocal
796 import tzlocal
742 local_timezone = tzlocal.get_localzone()
797 local_timezone = tzlocal.get_localzone()
743
798
744
799
745 def age_component(datetime_iso, value=None, time_is_local=False, tooltip=True):
800 def age_component(datetime_iso, value=None, time_is_local=False, tooltip=True):
746 title = value or format_date(datetime_iso)
801 title = value or format_date(datetime_iso)
747 tzinfo = '+00:00'
802 tzinfo = '+00:00'
748
803
749 # detect if we have a timezone info, otherwise, add it
804 # detect if we have a timezone info, otherwise, add it
750 if time_is_local and isinstance(datetime_iso, datetime) and not datetime_iso.tzinfo:
805 if time_is_local and isinstance(datetime_iso, datetime) and not datetime_iso.tzinfo:
751 force_timezone = os.environ.get('RC_TIMEZONE', '')
806 force_timezone = os.environ.get('RC_TIMEZONE', '')
752 if force_timezone:
807 if force_timezone:
753 force_timezone = pytz.timezone(force_timezone)
808 force_timezone = pytz.timezone(force_timezone)
754 timezone = force_timezone or local_timezone
809 timezone = force_timezone or local_timezone
755 offset = timezone.localize(datetime_iso).strftime('%z')
810 offset = timezone.localize(datetime_iso).strftime('%z')
756 tzinfo = '{}:{}'.format(offset[:-2], offset[-2:])
811 tzinfo = '{}:{}'.format(offset[:-2], offset[-2:])
757
812
758 return literal(
813 return literal(
759 '<time class="timeago {cls}" title="{tt_title}" datetime="{dt}{tzinfo}">{title}</time>'.format(
814 '<time class="timeago {cls}" title="{tt_title}" datetime="{dt}{tzinfo}">{title}</time>'.format(
760 cls='tooltip' if tooltip else '',
815 cls='tooltip' if tooltip else '',
761 tt_title=('{title}{tzinfo}'.format(title=title, tzinfo=tzinfo)) if tooltip else '',
816 tt_title=('{title}{tzinfo}'.format(title=title, tzinfo=tzinfo)) if tooltip else '',
762 title=title, dt=datetime_iso, tzinfo=tzinfo
817 title=title, dt=datetime_iso, tzinfo=tzinfo
763 ))
818 ))
764
819
765
820
766 def _shorten_commit_id(commit_id, commit_len=None):
821 def _shorten_commit_id(commit_id, commit_len=None):
767 if commit_len is None:
822 if commit_len is None:
768 request = get_current_request()
823 request = get_current_request()
769 commit_len = request.call_context.visual.show_sha_length
824 commit_len = request.call_context.visual.show_sha_length
770 return commit_id[:commit_len]
825 return commit_id[:commit_len]
771
826
772
827
773 def show_id(commit, show_idx=None, commit_len=None):
828 def show_id(commit, show_idx=None, commit_len=None):
774 """
829 """
775 Configurable function that shows ID
830 Configurable function that shows ID
776 by default it's r123:fffeeefffeee
831 by default it's r123:fffeeefffeee
777
832
778 :param commit: commit instance
833 :param commit: commit instance
779 """
834 """
780 if show_idx is None:
835 if show_idx is None:
781 request = get_current_request()
836 request = get_current_request()
782 show_idx = request.call_context.visual.show_revision_number
837 show_idx = request.call_context.visual.show_revision_number
783
838
784 raw_id = _shorten_commit_id(commit.raw_id, commit_len=commit_len)
839 raw_id = _shorten_commit_id(commit.raw_id, commit_len=commit_len)
785 if show_idx:
840 if show_idx:
786 return 'r%s:%s' % (commit.idx, raw_id)
841 return 'r%s:%s' % (commit.idx, raw_id)
787 else:
842 else:
788 return '%s' % (raw_id, )
843 return '%s' % (raw_id, )
789
844
790
845
791 def format_date(date):
846 def format_date(date):
792 """
847 """
793 use a standardized formatting for dates used in RhodeCode
848 use a standardized formatting for dates used in RhodeCode
794
849
795 :param date: date/datetime object
850 :param date: date/datetime object
796 :return: formatted date
851 :return: formatted date
797 """
852 """
798
853
799 if date:
854 if date:
800 _fmt = "%a, %d %b %Y %H:%M:%S"
855 _fmt = "%a, %d %b %Y %H:%M:%S"
801 return safe_unicode(date.strftime(_fmt))
856 return safe_unicode(date.strftime(_fmt))
802
857
803 return u""
858 return u""
804
859
805
860
806 class _RepoChecker(object):
861 class _RepoChecker(object):
807
862
808 def __init__(self, backend_alias):
863 def __init__(self, backend_alias):
809 self._backend_alias = backend_alias
864 self._backend_alias = backend_alias
810
865
811 def __call__(self, repository):
866 def __call__(self, repository):
812 if hasattr(repository, 'alias'):
867 if hasattr(repository, 'alias'):
813 _type = repository.alias
868 _type = repository.alias
814 elif hasattr(repository, 'repo_type'):
869 elif hasattr(repository, 'repo_type'):
815 _type = repository.repo_type
870 _type = repository.repo_type
816 else:
871 else:
817 _type = repository
872 _type = repository
818 return _type == self._backend_alias
873 return _type == self._backend_alias
819
874
820
875
821 is_git = _RepoChecker('git')
876 is_git = _RepoChecker('git')
822 is_hg = _RepoChecker('hg')
877 is_hg = _RepoChecker('hg')
823 is_svn = _RepoChecker('svn')
878 is_svn = _RepoChecker('svn')
824
879
825
880
826 def get_repo_type_by_name(repo_name):
881 def get_repo_type_by_name(repo_name):
827 repo = Repository.get_by_repo_name(repo_name)
882 repo = Repository.get_by_repo_name(repo_name)
828 if repo:
883 if repo:
829 return repo.repo_type
884 return repo.repo_type
830
885
831
886
832 def is_svn_without_proxy(repository):
887 def is_svn_without_proxy(repository):
833 if is_svn(repository):
888 if is_svn(repository):
834 from rhodecode.model.settings import VcsSettingsModel
889 from rhodecode.model.settings import VcsSettingsModel
835 conf = VcsSettingsModel().get_ui_settings_as_config_obj()
890 conf = VcsSettingsModel().get_ui_settings_as_config_obj()
836 return not str2bool(conf.get('vcs_svn_proxy', 'http_requests_enabled'))
891 return not str2bool(conf.get('vcs_svn_proxy', 'http_requests_enabled'))
837 return False
892 return False
838
893
839
894
840 def discover_user(author):
895 def discover_user(author):
841 """
896 """
842 Tries to discover RhodeCode User based on the author string. Author string
897 Tries to discover RhodeCode User based on the author string. Author string
843 is typically `FirstName LastName <email@address.com>`
898 is typically `FirstName LastName <email@address.com>`
844 """
899 """
845
900
846 # if author is already an instance use it for extraction
901 # if author is already an instance use it for extraction
847 if isinstance(author, User):
902 if isinstance(author, User):
848 return author
903 return author
849
904
850 # Valid email in the attribute passed, see if they're in the system
905 # Valid email in the attribute passed, see if they're in the system
851 _email = author_email(author)
906 _email = author_email(author)
852 if _email != '':
907 if _email != '':
853 user = User.get_by_email(_email, case_insensitive=True, cache=True)
908 user = User.get_by_email(_email, case_insensitive=True, cache=True)
854 if user is not None:
909 if user is not None:
855 return user
910 return user
856
911
857 # Maybe it's a username, we try to extract it and fetch by username ?
912 # Maybe it's a username, we try to extract it and fetch by username ?
858 _author = author_name(author)
913 _author = author_name(author)
859 user = User.get_by_username(_author, case_insensitive=True, cache=True)
914 user = User.get_by_username(_author, case_insensitive=True, cache=True)
860 if user is not None:
915 if user is not None:
861 return user
916 return user
862
917
863 return None
918 return None
864
919
865
920
866 def email_or_none(author):
921 def email_or_none(author):
867 # extract email from the commit string
922 # extract email from the commit string
868 _email = author_email(author)
923 _email = author_email(author)
869
924
870 # If we have an email, use it, otherwise
925 # If we have an email, use it, otherwise
871 # see if it contains a username we can get an email from
926 # see if it contains a username we can get an email from
872 if _email != '':
927 if _email != '':
873 return _email
928 return _email
874 else:
929 else:
875 user = User.get_by_username(
930 user = User.get_by_username(
876 author_name(author), case_insensitive=True, cache=True)
931 author_name(author), case_insensitive=True, cache=True)
877
932
878 if user is not None:
933 if user is not None:
879 return user.email
934 return user.email
880
935
881 # No valid email, not a valid user in the system, none!
936 # No valid email, not a valid user in the system, none!
882 return None
937 return None
883
938
884
939
885 def link_to_user(author, length=0, **kwargs):
940 def link_to_user(author, length=0, **kwargs):
886 user = discover_user(author)
941 user = discover_user(author)
887 # user can be None, but if we have it already it means we can re-use it
942 # user can be None, but if we have it already it means we can re-use it
888 # in the person() function, so we save 1 intensive-query
943 # in the person() function, so we save 1 intensive-query
889 if user:
944 if user:
890 author = user
945 author = user
891
946
892 display_person = person(author, 'username_or_name_or_email')
947 display_person = person(author, 'username_or_name_or_email')
893 if length:
948 if length:
894 display_person = shorter(display_person, length)
949 display_person = shorter(display_person, length)
895
950
896 if user:
951 if user:
897 return link_to(
952 return link_to(
898 escape(display_person),
953 escape(display_person),
899 route_path('user_profile', username=user.username),
954 route_path('user_profile', username=user.username),
900 **kwargs)
955 **kwargs)
901 else:
956 else:
902 return escape(display_person)
957 return escape(display_person)
903
958
904
959
905 def link_to_group(users_group_name, **kwargs):
960 def link_to_group(users_group_name, **kwargs):
906 return link_to(
961 return link_to(
907 escape(users_group_name),
962 escape(users_group_name),
908 route_path('user_group_profile', user_group_name=users_group_name),
963 route_path('user_group_profile', user_group_name=users_group_name),
909 **kwargs)
964 **kwargs)
910
965
911
966
912 def person(author, show_attr="username_and_name"):
967 def person(author, show_attr="username_and_name"):
913 user = discover_user(author)
968 user = discover_user(author)
914 if user:
969 if user:
915 return getattr(user, show_attr)
970 return getattr(user, show_attr)
916 else:
971 else:
917 _author = author_name(author)
972 _author = author_name(author)
918 _email = email(author)
973 _email = email(author)
919 return _author or _email
974 return _author or _email
920
975
921
976
922 def author_string(email):
977 def author_string(email):
923 if email:
978 if email:
924 user = User.get_by_email(email, case_insensitive=True, cache=True)
979 user = User.get_by_email(email, case_insensitive=True, cache=True)
925 if user:
980 if user:
926 if user.first_name or user.last_name:
981 if user.first_name or user.last_name:
927 return '%s %s &lt;%s&gt;' % (
982 return '%s %s &lt;%s&gt;' % (
928 user.first_name, user.last_name, email)
983 user.first_name, user.last_name, email)
929 else:
984 else:
930 return email
985 return email
931 else:
986 else:
932 return email
987 return email
933 else:
988 else:
934 return None
989 return None
935
990
936
991
937 def person_by_id(id_, show_attr="username_and_name"):
992 def person_by_id(id_, show_attr="username_and_name"):
938 # attr to return from fetched user
993 # attr to return from fetched user
939 person_getter = lambda usr: getattr(usr, show_attr)
994 person_getter = lambda usr: getattr(usr, show_attr)
940
995
941 #maybe it's an ID ?
996 #maybe it's an ID ?
942 if str(id_).isdigit() or isinstance(id_, int):
997 if str(id_).isdigit() or isinstance(id_, int):
943 id_ = int(id_)
998 id_ = int(id_)
944 user = User.get(id_)
999 user = User.get(id_)
945 if user is not None:
1000 if user is not None:
946 return person_getter(user)
1001 return person_getter(user)
947 return id_
1002 return id_
948
1003
949
1004
950 def gravatar_with_user(request, author, show_disabled=False, tooltip=False):
1005 def gravatar_with_user(request, author, show_disabled=False, tooltip=False):
951 _render = request.get_partial_renderer('rhodecode:templates/base/base.mako')
1006 _render = request.get_partial_renderer('rhodecode:templates/base/base.mako')
952 return _render('gravatar_with_user', author, show_disabled=show_disabled, tooltip=tooltip)
1007 return _render('gravatar_with_user', author, show_disabled=show_disabled, tooltip=tooltip)
953
1008
954
1009
955 tags_paterns = OrderedDict((
1010 tags_paterns = OrderedDict((
956 ('lang', (re.compile(r'\[(lang|language)\ \=\&gt;\ *([a-zA-Z\-\/\#\+\.]*)\]'),
1011 ('lang', (re.compile(r'\[(lang|language)\ \=\&gt;\ *([a-zA-Z\-\/\#\+\.]*)\]'),
957 '<div class="metatag" tag="lang">\\2</div>')),
1012 '<div class="metatag" tag="lang">\\2</div>')),
958
1013
959 ('see', (re.compile(r'\[see\ \=\&gt;\ *([a-zA-Z0-9\/\=\?\&amp;\ \:\/\.\-]*)\]'),
1014 ('see', (re.compile(r'\[see\ \=\&gt;\ *([a-zA-Z0-9\/\=\?\&amp;\ \:\/\.\-]*)\]'),
960 '<div class="metatag" tag="see">see: \\1 </div>')),
1015 '<div class="metatag" tag="see">see: \\1 </div>')),
961
1016
962 ('url', (re.compile(r'\[url\ \=\&gt;\ \[([a-zA-Z0-9\ \.\-\_]+)\]\((http://|https://|/)(.*?)\)\]'),
1017 ('url', (re.compile(r'\[url\ \=\&gt;\ \[([a-zA-Z0-9\ \.\-\_]+)\]\((http://|https://|/)(.*?)\)\]'),
963 '<div class="metatag" tag="url"> <a href="\\2\\3">\\1</a> </div>')),
1018 '<div class="metatag" tag="url"> <a href="\\2\\3">\\1</a> </div>')),
964
1019
965 ('license', (re.compile(r'\[license\ \=\&gt;\ *([a-zA-Z0-9\/\=\?\&amp;\ \:\/\.\-]*)\]'),
1020 ('license', (re.compile(r'\[license\ \=\&gt;\ *([a-zA-Z0-9\/\=\?\&amp;\ \:\/\.\-]*)\]'),
966 '<div class="metatag" tag="license"><a href="http:\/\/www.opensource.org/licenses/\\1">\\1</a></div>')),
1021 '<div class="metatag" tag="license"><a href="http:\/\/www.opensource.org/licenses/\\1">\\1</a></div>')),
967
1022
968 ('ref', (re.compile(r'\[(requires|recommends|conflicts|base)\ \=\&gt;\ *([a-zA-Z0-9\-\/]*)\]'),
1023 ('ref', (re.compile(r'\[(requires|recommends|conflicts|base)\ \=\&gt;\ *([a-zA-Z0-9\-\/]*)\]'),
969 '<div class="metatag" tag="ref \\1">\\1: <a href="/\\2">\\2</a></div>')),
1024 '<div class="metatag" tag="ref \\1">\\1: <a href="/\\2">\\2</a></div>')),
970
1025
971 ('state', (re.compile(r'\[(stable|featured|stale|dead|dev|deprecated)\]'),
1026 ('state', (re.compile(r'\[(stable|featured|stale|dead|dev|deprecated)\]'),
972 '<div class="metatag" tag="state \\1">\\1</div>')),
1027 '<div class="metatag" tag="state \\1">\\1</div>')),
973
1028
974 # label in grey
1029 # label in grey
975 ('label', (re.compile(r'\[([a-z]+)\]'),
1030 ('label', (re.compile(r'\[([a-z]+)\]'),
976 '<div class="metatag" tag="label">\\1</div>')),
1031 '<div class="metatag" tag="label">\\1</div>')),
977
1032
978 # generic catch all in grey
1033 # generic catch all in grey
979 ('generic', (re.compile(r'\[([a-zA-Z0-9\.\-\_]+)\]'),
1034 ('generic', (re.compile(r'\[([a-zA-Z0-9\.\-\_]+)\]'),
980 '<div class="metatag" tag="generic">\\1</div>')),
1035 '<div class="metatag" tag="generic">\\1</div>')),
981 ))
1036 ))
982
1037
983
1038
984 def extract_metatags(value):
1039 def extract_metatags(value):
985 """
1040 """
986 Extract supported meta-tags from given text value
1041 Extract supported meta-tags from given text value
987 """
1042 """
988 tags = []
1043 tags = []
989 if not value:
1044 if not value:
990 return tags, ''
1045 return tags, ''
991
1046
992 for key, val in tags_paterns.items():
1047 for key, val in tags_paterns.items():
993 pat, replace_html = val
1048 pat, replace_html = val
994 tags.extend([(key, x.group()) for x in pat.finditer(value)])
1049 tags.extend([(key, x.group()) for x in pat.finditer(value)])
995 value = pat.sub('', value)
1050 value = pat.sub('', value)
996
1051
997 return tags, value
1052 return tags, value
998
1053
999
1054
1000 def style_metatag(tag_type, value):
1055 def style_metatag(tag_type, value):
1001 """
1056 """
1002 converts tags from value into html equivalent
1057 converts tags from value into html equivalent
1003 """
1058 """
1004 if not value:
1059 if not value:
1005 return ''
1060 return ''
1006
1061
1007 html_value = value
1062 html_value = value
1008 tag_data = tags_paterns.get(tag_type)
1063 tag_data = tags_paterns.get(tag_type)
1009 if tag_data:
1064 if tag_data:
1010 pat, replace_html = tag_data
1065 pat, replace_html = tag_data
1011 # convert to plain `unicode` instead of a markup tag to be used in
1066 # convert to plain `unicode` instead of a markup tag to be used in
1012 # regex expressions. safe_unicode doesn't work here
1067 # regex expressions. safe_unicode doesn't work here
1013 html_value = pat.sub(replace_html, unicode(value))
1068 html_value = pat.sub(replace_html, unicode(value))
1014
1069
1015 return html_value
1070 return html_value
1016
1071
1017
1072
1018 def bool2icon(value, show_at_false=True):
1073 def bool2icon(value, show_at_false=True):
1019 """
1074 """
1020 Returns boolean value of a given value, represented as html element with
1075 Returns boolean value of a given value, represented as html element with
1021 classes that will represent icons
1076 classes that will represent icons
1022
1077
1023 :param value: given value to convert to html node
1078 :param value: given value to convert to html node
1024 """
1079 """
1025
1080
1026 if value: # does bool conversion
1081 if value: # does bool conversion
1027 return HTML.tag('i', class_="icon-true", title='True')
1082 return HTML.tag('i', class_="icon-true", title='True')
1028 else: # not true as bool
1083 else: # not true as bool
1029 if show_at_false:
1084 if show_at_false:
1030 return HTML.tag('i', class_="icon-false", title='False')
1085 return HTML.tag('i', class_="icon-false", title='False')
1031 return HTML.tag('i')
1086 return HTML.tag('i')
1032
1087
1033 #==============================================================================
1088 #==============================================================================
1034 # PERMS
1089 # PERMS
1035 #==============================================================================
1090 #==============================================================================
1036 from rhodecode.lib.auth import (
1091 from rhodecode.lib.auth import (
1037 HasPermissionAny, HasPermissionAll,
1092 HasPermissionAny, HasPermissionAll,
1038 HasRepoPermissionAny, HasRepoPermissionAll, HasRepoGroupPermissionAll,
1093 HasRepoPermissionAny, HasRepoPermissionAll, HasRepoGroupPermissionAll,
1039 HasRepoGroupPermissionAny, HasRepoPermissionAnyApi, get_csrf_token,
1094 HasRepoGroupPermissionAny, HasRepoPermissionAnyApi, get_csrf_token,
1040 csrf_token_key, AuthUser)
1095 csrf_token_key, AuthUser)
1041
1096
1042
1097
1043 #==============================================================================
1098 #==============================================================================
1044 # GRAVATAR URL
1099 # GRAVATAR URL
1045 #==============================================================================
1100 #==============================================================================
1046 class InitialsGravatar(object):
1101 class InitialsGravatar(object):
1047 def __init__(self, email_address, first_name, last_name, size=30,
1102 def __init__(self, email_address, first_name, last_name, size=30,
1048 background=None, text_color='#fff'):
1103 background=None, text_color='#fff'):
1049 self.size = size
1104 self.size = size
1050 self.first_name = first_name
1105 self.first_name = first_name
1051 self.last_name = last_name
1106 self.last_name = last_name
1052 self.email_address = email_address
1107 self.email_address = email_address
1053 self.background = background or self.str2color(email_address)
1108 self.background = background or self.str2color(email_address)
1054 self.text_color = text_color
1109 self.text_color = text_color
1055
1110
1056 def get_color_bank(self):
1111 def get_color_bank(self):
1057 """
1112 """
1058 returns a predefined list of colors that gravatars can use.
1113 returns a predefined list of colors that gravatars can use.
1059 Those are randomized distinct colors that guarantee readability and
1114 Those are randomized distinct colors that guarantee readability and
1060 uniqueness.
1115 uniqueness.
1061
1116
1062 generated with: http://phrogz.net/css/distinct-colors.html
1117 generated with: http://phrogz.net/css/distinct-colors.html
1063 """
1118 """
1064 return [
1119 return [
1065 '#bf3030', '#a67f53', '#00ff00', '#5989b3', '#392040', '#d90000',
1120 '#bf3030', '#a67f53', '#00ff00', '#5989b3', '#392040', '#d90000',
1066 '#402910', '#204020', '#79baf2', '#a700b3', '#bf6060', '#7f5320',
1121 '#402910', '#204020', '#79baf2', '#a700b3', '#bf6060', '#7f5320',
1067 '#008000', '#003059', '#ee00ff', '#ff0000', '#8c4b00', '#007300',
1122 '#008000', '#003059', '#ee00ff', '#ff0000', '#8c4b00', '#007300',
1068 '#005fb3', '#de73e6', '#ff4040', '#ffaa00', '#3df255', '#203140',
1123 '#005fb3', '#de73e6', '#ff4040', '#ffaa00', '#3df255', '#203140',
1069 '#47004d', '#591616', '#664400', '#59b365', '#0d2133', '#83008c',
1124 '#47004d', '#591616', '#664400', '#59b365', '#0d2133', '#83008c',
1070 '#592d2d', '#bf9f60', '#73e682', '#1d3f73', '#73006b', '#402020',
1125 '#592d2d', '#bf9f60', '#73e682', '#1d3f73', '#73006b', '#402020',
1071 '#b2862d', '#397341', '#597db3', '#e600d6', '#a60000', '#736039',
1126 '#b2862d', '#397341', '#597db3', '#e600d6', '#a60000', '#736039',
1072 '#00b318', '#79aaf2', '#330d30', '#ff8080', '#403010', '#16591f',
1127 '#00b318', '#79aaf2', '#330d30', '#ff8080', '#403010', '#16591f',
1073 '#002459', '#8c4688', '#e50000', '#ffbf40', '#00732e', '#102340',
1128 '#002459', '#8c4688', '#e50000', '#ffbf40', '#00732e', '#102340',
1074 '#bf60ac', '#8c4646', '#cc8800', '#00a642', '#1d3473', '#b32d98',
1129 '#bf60ac', '#8c4646', '#cc8800', '#00a642', '#1d3473', '#b32d98',
1075 '#660e00', '#ffd580', '#80ffb2', '#7391e6', '#733967', '#d97b6c',
1130 '#660e00', '#ffd580', '#80ffb2', '#7391e6', '#733967', '#d97b6c',
1076 '#8c5e00', '#59b389', '#3967e6', '#590047', '#73281d', '#665200',
1131 '#8c5e00', '#59b389', '#3967e6', '#590047', '#73281d', '#665200',
1077 '#00e67a', '#2d50b3', '#8c2377', '#734139', '#b2982d', '#16593a',
1132 '#00e67a', '#2d50b3', '#8c2377', '#734139', '#b2982d', '#16593a',
1078 '#001859', '#ff00aa', '#a65e53', '#ffcc00', '#0d3321', '#2d3959',
1133 '#001859', '#ff00aa', '#a65e53', '#ffcc00', '#0d3321', '#2d3959',
1079 '#731d56', '#401610', '#4c3d00', '#468c6c', '#002ca6', '#d936a3',
1134 '#731d56', '#401610', '#4c3d00', '#468c6c', '#002ca6', '#d936a3',
1080 '#d94c36', '#403920', '#36d9a3', '#0d1733', '#592d4a', '#993626',
1135 '#d94c36', '#403920', '#36d9a3', '#0d1733', '#592d4a', '#993626',
1081 '#cca300', '#00734d', '#46598c', '#8c005e', '#7f1100', '#8c7000',
1136 '#cca300', '#00734d', '#46598c', '#8c005e', '#7f1100', '#8c7000',
1082 '#00a66f', '#7382e6', '#b32d74', '#d9896c', '#ffe680', '#1d7362',
1137 '#00a66f', '#7382e6', '#b32d74', '#d9896c', '#ffe680', '#1d7362',
1083 '#364cd9', '#73003d', '#d93a00', '#998a4d', '#59b3a1', '#5965b3',
1138 '#364cd9', '#73003d', '#d93a00', '#998a4d', '#59b3a1', '#5965b3',
1084 '#e5007a', '#73341d', '#665f00', '#00b38f', '#0018b3', '#59163a',
1139 '#e5007a', '#73341d', '#665f00', '#00b38f', '#0018b3', '#59163a',
1085 '#b2502d', '#bfb960', '#00ffcc', '#23318c', '#a6537f', '#734939',
1140 '#b2502d', '#bfb960', '#00ffcc', '#23318c', '#a6537f', '#734939',
1086 '#b2a700', '#104036', '#3d3df2', '#402031', '#e56739', '#736f39',
1141 '#b2a700', '#104036', '#3d3df2', '#402031', '#e56739', '#736f39',
1087 '#79f2ea', '#000059', '#401029', '#4c1400', '#ffee00', '#005953',
1142 '#79f2ea', '#000059', '#401029', '#4c1400', '#ffee00', '#005953',
1088 '#101040', '#990052', '#402820', '#403d10', '#00ffee', '#0000d9',
1143 '#101040', '#990052', '#402820', '#403d10', '#00ffee', '#0000d9',
1089 '#ff80c4', '#a66953', '#eeff00', '#00ccbe', '#8080ff', '#e673a1',
1144 '#ff80c4', '#a66953', '#eeff00', '#00ccbe', '#8080ff', '#e673a1',
1090 '#a62c00', '#474d00', '#1a3331', '#46468c', '#733950', '#662900',
1145 '#a62c00', '#474d00', '#1a3331', '#46468c', '#733950', '#662900',
1091 '#858c23', '#238c85', '#0f0073', '#b20047', '#d9986c', '#becc00',
1146 '#858c23', '#238c85', '#0f0073', '#b20047', '#d9986c', '#becc00',
1092 '#396f73', '#281d73', '#ff0066', '#ff6600', '#dee673', '#59adb3',
1147 '#396f73', '#281d73', '#ff0066', '#ff6600', '#dee673', '#59adb3',
1093 '#6559b3', '#590024', '#b2622d', '#98b32d', '#36ced9', '#332d59',
1148 '#6559b3', '#590024', '#b2622d', '#98b32d', '#36ced9', '#332d59',
1094 '#40001a', '#733f1d', '#526600', '#005359', '#242040', '#bf6079',
1149 '#40001a', '#733f1d', '#526600', '#005359', '#242040', '#bf6079',
1095 '#735039', '#cef23d', '#007780', '#5630bf', '#66001b', '#b24700',
1150 '#735039', '#cef23d', '#007780', '#5630bf', '#66001b', '#b24700',
1096 '#acbf60', '#1d6273', '#25008c', '#731d34', '#a67453', '#50592d',
1151 '#acbf60', '#1d6273', '#25008c', '#731d34', '#a67453', '#50592d',
1097 '#00ccff', '#6600ff', '#ff0044', '#4c1f00', '#8a994d', '#79daf2',
1152 '#00ccff', '#6600ff', '#ff0044', '#4c1f00', '#8a994d', '#79daf2',
1098 '#a173e6', '#d93662', '#402310', '#aaff00', '#2d98b3', '#8c40ff',
1153 '#a173e6', '#d93662', '#402310', '#aaff00', '#2d98b3', '#8c40ff',
1099 '#592d39', '#ff8c40', '#354020', '#103640', '#1a0040', '#331a20',
1154 '#592d39', '#ff8c40', '#354020', '#103640', '#1a0040', '#331a20',
1100 '#331400', '#334d00', '#1d5673', '#583973', '#7f0022', '#4c3626',
1155 '#331400', '#334d00', '#1d5673', '#583973', '#7f0022', '#4c3626',
1101 '#88cc00', '#36a3d9', '#3d0073', '#d9364c', '#33241a', '#698c23',
1156 '#88cc00', '#36a3d9', '#3d0073', '#d9364c', '#33241a', '#698c23',
1102 '#5995b3', '#300059', '#e57382', '#7f3300', '#366600', '#00aaff',
1157 '#5995b3', '#300059', '#e57382', '#7f3300', '#366600', '#00aaff',
1103 '#3a1659', '#733941', '#663600', '#74b32d', '#003c59', '#7f53a6',
1158 '#3a1659', '#733941', '#663600', '#74b32d', '#003c59', '#7f53a6',
1104 '#73000f', '#ff8800', '#baf279', '#79caf2', '#291040', '#a6293a',
1159 '#73000f', '#ff8800', '#baf279', '#79caf2', '#291040', '#a6293a',
1105 '#b2742d', '#587339', '#0077b3', '#632699', '#400009', '#d9a66c',
1160 '#b2742d', '#587339', '#0077b3', '#632699', '#400009', '#d9a66c',
1106 '#294010', '#2d4a59', '#aa00ff', '#4c131b', '#b25f00', '#5ce600',
1161 '#294010', '#2d4a59', '#aa00ff', '#4c131b', '#b25f00', '#5ce600',
1107 '#267399', '#a336d9', '#990014', '#664e33', '#86bf60', '#0088ff',
1162 '#267399', '#a336d9', '#990014', '#664e33', '#86bf60', '#0088ff',
1108 '#7700b3', '#593a16', '#073300', '#1d4b73', '#ac60bf', '#e59539',
1163 '#7700b3', '#593a16', '#073300', '#1d4b73', '#ac60bf', '#e59539',
1109 '#4f8c46', '#368dd9', '#5c0073'
1164 '#4f8c46', '#368dd9', '#5c0073'
1110 ]
1165 ]
1111
1166
1112 def rgb_to_hex_color(self, rgb_tuple):
1167 def rgb_to_hex_color(self, rgb_tuple):
1113 """
1168 """
1114 Converts an rgb_tuple passed to an hex color.
1169 Converts an rgb_tuple passed to an hex color.
1115
1170
1116 :param rgb_tuple: tuple with 3 ints represents rgb color space
1171 :param rgb_tuple: tuple with 3 ints represents rgb color space
1117 """
1172 """
1118 return '#' + ("".join(map(chr, rgb_tuple)).encode('hex'))
1173 return '#' + ("".join(map(chr, rgb_tuple)).encode('hex'))
1119
1174
1120 def email_to_int_list(self, email_str):
1175 def email_to_int_list(self, email_str):
1121 """
1176 """
1122 Get every byte of the hex digest value of email and turn it to integer.
1177 Get every byte of the hex digest value of email and turn it to integer.
1123 It's going to be always between 0-255
1178 It's going to be always between 0-255
1124 """
1179 """
1125 digest = md5_safe(email_str.lower())
1180 digest = md5_safe(email_str.lower())
1126 return [int(digest[i * 2:i * 2 + 2], 16) for i in range(16)]
1181 return [int(digest[i * 2:i * 2 + 2], 16) for i in range(16)]
1127
1182
1128 def pick_color_bank_index(self, email_str, color_bank):
1183 def pick_color_bank_index(self, email_str, color_bank):
1129 return self.email_to_int_list(email_str)[0] % len(color_bank)
1184 return self.email_to_int_list(email_str)[0] % len(color_bank)
1130
1185
1131 def str2color(self, email_str):
1186 def str2color(self, email_str):
1132 """
1187 """
1133 Tries to map in a stable algorithm an email to color
1188 Tries to map in a stable algorithm an email to color
1134
1189
1135 :param email_str:
1190 :param email_str:
1136 """
1191 """
1137 color_bank = self.get_color_bank()
1192 color_bank = self.get_color_bank()
1138 # pick position (module it's length so we always find it in the
1193 # pick position (module it's length so we always find it in the
1139 # bank even if it's smaller than 256 values
1194 # bank even if it's smaller than 256 values
1140 pos = self.pick_color_bank_index(email_str, color_bank)
1195 pos = self.pick_color_bank_index(email_str, color_bank)
1141 return color_bank[pos]
1196 return color_bank[pos]
1142
1197
1143 def normalize_email(self, email_address):
1198 def normalize_email(self, email_address):
1144 import unicodedata
1199 import unicodedata
1145 # default host used to fill in the fake/missing email
1200 # default host used to fill in the fake/missing email
1146 default_host = u'localhost'
1201 default_host = u'localhost'
1147
1202
1148 if not email_address:
1203 if not email_address:
1149 email_address = u'%s@%s' % (User.DEFAULT_USER, default_host)
1204 email_address = u'%s@%s' % (User.DEFAULT_USER, default_host)
1150
1205
1151 email_address = safe_unicode(email_address)
1206 email_address = safe_unicode(email_address)
1152
1207
1153 if u'@' not in email_address:
1208 if u'@' not in email_address:
1154 email_address = u'%s@%s' % (email_address, default_host)
1209 email_address = u'%s@%s' % (email_address, default_host)
1155
1210
1156 if email_address.endswith(u'@'):
1211 if email_address.endswith(u'@'):
1157 email_address = u'%s%s' % (email_address, default_host)
1212 email_address = u'%s%s' % (email_address, default_host)
1158
1213
1159 email_address = unicodedata.normalize('NFKD', email_address)\
1214 email_address = unicodedata.normalize('NFKD', email_address)\
1160 .encode('ascii', 'ignore')
1215 .encode('ascii', 'ignore')
1161 return email_address
1216 return email_address
1162
1217
1163 def get_initials(self):
1218 def get_initials(self):
1164 """
1219 """
1165 Returns 2 letter initials calculated based on the input.
1220 Returns 2 letter initials calculated based on the input.
1166 The algorithm picks first given email address, and takes first letter
1221 The algorithm picks first given email address, and takes first letter
1167 of part before @, and then the first letter of server name. In case
1222 of part before @, and then the first letter of server name. In case
1168 the part before @ is in a format of `somestring.somestring2` it replaces
1223 the part before @ is in a format of `somestring.somestring2` it replaces
1169 the server letter with first letter of somestring2
1224 the server letter with first letter of somestring2
1170
1225
1171 In case function was initialized with both first and lastname, this
1226 In case function was initialized with both first and lastname, this
1172 overrides the extraction from email by first letter of the first and
1227 overrides the extraction from email by first letter of the first and
1173 last name. We add special logic to that functionality, In case Full name
1228 last name. We add special logic to that functionality, In case Full name
1174 is compound, like Guido Von Rossum, we use last part of the last name
1229 is compound, like Guido Von Rossum, we use last part of the last name
1175 (Von Rossum) picking `R`.
1230 (Von Rossum) picking `R`.
1176
1231
1177 Function also normalizes the non-ascii characters to they ascii
1232 Function also normalizes the non-ascii characters to they ascii
1178 representation, eg Δ„ => A
1233 representation, eg Δ„ => A
1179 """
1234 """
1180 import unicodedata
1235 import unicodedata
1181 # replace non-ascii to ascii
1236 # replace non-ascii to ascii
1182 first_name = unicodedata.normalize(
1237 first_name = unicodedata.normalize(
1183 'NFKD', safe_unicode(self.first_name)).encode('ascii', 'ignore')
1238 'NFKD', safe_unicode(self.first_name)).encode('ascii', 'ignore')
1184 last_name = unicodedata.normalize(
1239 last_name = unicodedata.normalize(
1185 'NFKD', safe_unicode(self.last_name)).encode('ascii', 'ignore')
1240 'NFKD', safe_unicode(self.last_name)).encode('ascii', 'ignore')
1186
1241
1187 # do NFKD encoding, and also make sure email has proper format
1242 # do NFKD encoding, and also make sure email has proper format
1188 email_address = self.normalize_email(self.email_address)
1243 email_address = self.normalize_email(self.email_address)
1189
1244
1190 # first push the email initials
1245 # first push the email initials
1191 prefix, server = email_address.split('@', 1)
1246 prefix, server = email_address.split('@', 1)
1192
1247
1193 # check if prefix is maybe a 'first_name.last_name' syntax
1248 # check if prefix is maybe a 'first_name.last_name' syntax
1194 _dot_split = prefix.rsplit('.', 1)
1249 _dot_split = prefix.rsplit('.', 1)
1195 if len(_dot_split) == 2 and _dot_split[1]:
1250 if len(_dot_split) == 2 and _dot_split[1]:
1196 initials = [_dot_split[0][0], _dot_split[1][0]]
1251 initials = [_dot_split[0][0], _dot_split[1][0]]
1197 else:
1252 else:
1198 initials = [prefix[0], server[0]]
1253 initials = [prefix[0], server[0]]
1199
1254
1200 # then try to replace either first_name or last_name
1255 # then try to replace either first_name or last_name
1201 fn_letter = (first_name or " ")[0].strip()
1256 fn_letter = (first_name or " ")[0].strip()
1202 ln_letter = (last_name.split(' ', 1)[-1] or " ")[0].strip()
1257 ln_letter = (last_name.split(' ', 1)[-1] or " ")[0].strip()
1203
1258
1204 if fn_letter:
1259 if fn_letter:
1205 initials[0] = fn_letter
1260 initials[0] = fn_letter
1206
1261
1207 if ln_letter:
1262 if ln_letter:
1208 initials[1] = ln_letter
1263 initials[1] = ln_letter
1209
1264
1210 return ''.join(initials).upper()
1265 return ''.join(initials).upper()
1211
1266
1212 def get_img_data_by_type(self, font_family, img_type):
1267 def get_img_data_by_type(self, font_family, img_type):
1213 default_user = """
1268 default_user = """
1214 <svg xmlns="http://www.w3.org/2000/svg"
1269 <svg xmlns="http://www.w3.org/2000/svg"
1215 version="1.1" x="0px" y="0px" width="{size}" height="{size}"
1270 version="1.1" x="0px" y="0px" width="{size}" height="{size}"
1216 viewBox="-15 -10 439.165 429.164"
1271 viewBox="-15 -10 439.165 429.164"
1217
1272
1218 xml:space="preserve"
1273 xml:space="preserve"
1219 style="background:{background};" >
1274 style="background:{background};" >
1220
1275
1221 <path d="M204.583,216.671c50.664,0,91.74-48.075,
1276 <path d="M204.583,216.671c50.664,0,91.74-48.075,
1222 91.74-107.378c0-82.237-41.074-107.377-91.74-107.377
1277 91.74-107.378c0-82.237-41.074-107.377-91.74-107.377
1223 c-50.668,0-91.74,25.14-91.74,107.377C112.844,
1278 c-50.668,0-91.74,25.14-91.74,107.377C112.844,
1224 168.596,153.916,216.671,
1279 168.596,153.916,216.671,
1225 204.583,216.671z" fill="{text_color}"/>
1280 204.583,216.671z" fill="{text_color}"/>
1226 <path d="M407.164,374.717L360.88,
1281 <path d="M407.164,374.717L360.88,
1227 270.454c-2.117-4.771-5.836-8.728-10.465-11.138l-71.83-37.392
1282 270.454c-2.117-4.771-5.836-8.728-10.465-11.138l-71.83-37.392
1228 c-1.584-0.823-3.502-0.663-4.926,0.415c-20.316,
1283 c-1.584-0.823-3.502-0.663-4.926,0.415c-20.316,
1229 15.366-44.203,23.488-69.076,23.488c-24.877,
1284 15.366-44.203,23.488-69.076,23.488c-24.877,
1230 0-48.762-8.122-69.078-23.488
1285 0-48.762-8.122-69.078-23.488
1231 c-1.428-1.078-3.346-1.238-4.93-0.415L58.75,
1286 c-1.428-1.078-3.346-1.238-4.93-0.415L58.75,
1232 259.316c-4.631,2.41-8.346,6.365-10.465,11.138L2.001,374.717
1287 259.316c-4.631,2.41-8.346,6.365-10.465,11.138L2.001,374.717
1233 c-3.191,7.188-2.537,15.412,1.75,22.005c4.285,
1288 c-3.191,7.188-2.537,15.412,1.75,22.005c4.285,
1234 6.592,11.537,10.526,19.4,10.526h362.861c7.863,0,15.117-3.936,
1289 6.592,11.537,10.526,19.4,10.526h362.861c7.863,0,15.117-3.936,
1235 19.402-10.527 C409.699,390.129,
1290 19.402-10.527 C409.699,390.129,
1236 410.355,381.902,407.164,374.717z" fill="{text_color}"/>
1291 410.355,381.902,407.164,374.717z" fill="{text_color}"/>
1237 </svg>""".format(
1292 </svg>""".format(
1238 size=self.size,
1293 size=self.size,
1239 background='#979797', # @grey4
1294 background='#979797', # @grey4
1240 text_color=self.text_color,
1295 text_color=self.text_color,
1241 font_family=font_family)
1296 font_family=font_family)
1242
1297
1243 return {
1298 return {
1244 "default_user": default_user
1299 "default_user": default_user
1245 }[img_type]
1300 }[img_type]
1246
1301
1247 def get_img_data(self, svg_type=None):
1302 def get_img_data(self, svg_type=None):
1248 """
1303 """
1249 generates the svg metadata for image
1304 generates the svg metadata for image
1250 """
1305 """
1251 fonts = [
1306 fonts = [
1252 '-apple-system',
1307 '-apple-system',
1253 'BlinkMacSystemFont',
1308 'BlinkMacSystemFont',
1254 'Segoe UI',
1309 'Segoe UI',
1255 'Roboto',
1310 'Roboto',
1256 'Oxygen-Sans',
1311 'Oxygen-Sans',
1257 'Ubuntu',
1312 'Ubuntu',
1258 'Cantarell',
1313 'Cantarell',
1259 'Helvetica Neue',
1314 'Helvetica Neue',
1260 'sans-serif'
1315 'sans-serif'
1261 ]
1316 ]
1262 font_family = ','.join(fonts)
1317 font_family = ','.join(fonts)
1263 if svg_type:
1318 if svg_type:
1264 return self.get_img_data_by_type(font_family, svg_type)
1319 return self.get_img_data_by_type(font_family, svg_type)
1265
1320
1266 initials = self.get_initials()
1321 initials = self.get_initials()
1267 img_data = """
1322 img_data = """
1268 <svg xmlns="http://www.w3.org/2000/svg" pointer-events="none"
1323 <svg xmlns="http://www.w3.org/2000/svg" pointer-events="none"
1269 width="{size}" height="{size}"
1324 width="{size}" height="{size}"
1270 style="width: 100%; height: 100%; background-color: {background}"
1325 style="width: 100%; height: 100%; background-color: {background}"
1271 viewBox="0 0 {size} {size}">
1326 viewBox="0 0 {size} {size}">
1272 <text text-anchor="middle" y="50%" x="50%" dy="0.35em"
1327 <text text-anchor="middle" y="50%" x="50%" dy="0.35em"
1273 pointer-events="auto" fill="{text_color}"
1328 pointer-events="auto" fill="{text_color}"
1274 font-family="{font_family}"
1329 font-family="{font_family}"
1275 style="font-weight: 400; font-size: {f_size}px;">{text}
1330 style="font-weight: 400; font-size: {f_size}px;">{text}
1276 </text>
1331 </text>
1277 </svg>""".format(
1332 </svg>""".format(
1278 size=self.size,
1333 size=self.size,
1279 f_size=self.size/2.05, # scale the text inside the box nicely
1334 f_size=self.size/2.05, # scale the text inside the box nicely
1280 background=self.background,
1335 background=self.background,
1281 text_color=self.text_color,
1336 text_color=self.text_color,
1282 text=initials.upper(),
1337 text=initials.upper(),
1283 font_family=font_family)
1338 font_family=font_family)
1284
1339
1285 return img_data
1340 return img_data
1286
1341
1287 def generate_svg(self, svg_type=None):
1342 def generate_svg(self, svg_type=None):
1288 img_data = self.get_img_data(svg_type)
1343 img_data = self.get_img_data(svg_type)
1289 return "data:image/svg+xml;base64,%s" % img_data.encode('base64')
1344 return "data:image/svg+xml;base64,%s" % img_data.encode('base64')
1290
1345
1291
1346
1292 def initials_gravatar(email_address, first_name, last_name, size=30):
1347 def initials_gravatar(email_address, first_name, last_name, size=30):
1293 svg_type = None
1348 svg_type = None
1294 if email_address == User.DEFAULT_USER_EMAIL:
1349 if email_address == User.DEFAULT_USER_EMAIL:
1295 svg_type = 'default_user'
1350 svg_type = 'default_user'
1296 klass = InitialsGravatar(email_address, first_name, last_name, size)
1351 klass = InitialsGravatar(email_address, first_name, last_name, size)
1297 return klass.generate_svg(svg_type=svg_type)
1352 return klass.generate_svg(svg_type=svg_type)
1298
1353
1299
1354
1300 def gravatar_url(email_address, size=30, request=None):
1355 def gravatar_url(email_address, size=30, request=None):
1301 request = get_current_request()
1356 request = get_current_request()
1302 _use_gravatar = request.call_context.visual.use_gravatar
1357 _use_gravatar = request.call_context.visual.use_gravatar
1303 _gravatar_url = request.call_context.visual.gravatar_url
1358 _gravatar_url = request.call_context.visual.gravatar_url
1304
1359
1305 _gravatar_url = _gravatar_url or User.DEFAULT_GRAVATAR_URL
1360 _gravatar_url = _gravatar_url or User.DEFAULT_GRAVATAR_URL
1306
1361
1307 email_address = email_address or User.DEFAULT_USER_EMAIL
1362 email_address = email_address or User.DEFAULT_USER_EMAIL
1308 if isinstance(email_address, unicode):
1363 if isinstance(email_address, unicode):
1309 # hashlib crashes on unicode items
1364 # hashlib crashes on unicode items
1310 email_address = safe_str(email_address)
1365 email_address = safe_str(email_address)
1311
1366
1312 # empty email or default user
1367 # empty email or default user
1313 if not email_address or email_address == User.DEFAULT_USER_EMAIL:
1368 if not email_address or email_address == User.DEFAULT_USER_EMAIL:
1314 return initials_gravatar(User.DEFAULT_USER_EMAIL, '', '', size=size)
1369 return initials_gravatar(User.DEFAULT_USER_EMAIL, '', '', size=size)
1315
1370
1316 if _use_gravatar:
1371 if _use_gravatar:
1317 # TODO: Disuse pyramid thread locals. Think about another solution to
1372 # TODO: Disuse pyramid thread locals. Think about another solution to
1318 # get the host and schema here.
1373 # get the host and schema here.
1319 request = get_current_request()
1374 request = get_current_request()
1320 tmpl = safe_str(_gravatar_url)
1375 tmpl = safe_str(_gravatar_url)
1321 tmpl = tmpl.replace('{email}', email_address)\
1376 tmpl = tmpl.replace('{email}', email_address)\
1322 .replace('{md5email}', md5_safe(email_address.lower())) \
1377 .replace('{md5email}', md5_safe(email_address.lower())) \
1323 .replace('{netloc}', request.host)\
1378 .replace('{netloc}', request.host)\
1324 .replace('{scheme}', request.scheme)\
1379 .replace('{scheme}', request.scheme)\
1325 .replace('{size}', safe_str(size))
1380 .replace('{size}', safe_str(size))
1326 return tmpl
1381 return tmpl
1327 else:
1382 else:
1328 return initials_gravatar(email_address, '', '', size=size)
1383 return initials_gravatar(email_address, '', '', size=size)
1329
1384
1330
1385
1331 def breadcrumb_repo_link(repo):
1386 def breadcrumb_repo_link(repo):
1332 """
1387 """
1333 Makes a breadcrumbs path link to repo
1388 Makes a breadcrumbs path link to repo
1334
1389
1335 ex::
1390 ex::
1336 group >> subgroup >> repo
1391 group >> subgroup >> repo
1337
1392
1338 :param repo: a Repository instance
1393 :param repo: a Repository instance
1339 """
1394 """
1340
1395
1341 path = [
1396 path = [
1342 link_to(group.name, route_path('repo_group_home', repo_group_name=group.group_name),
1397 link_to(group.name, route_path('repo_group_home', repo_group_name=group.group_name),
1343 title='last change:{}'.format(format_date(group.last_commit_change)))
1398 title='last change:{}'.format(format_date(group.last_commit_change)))
1344 for group in repo.groups_with_parents
1399 for group in repo.groups_with_parents
1345 ] + [
1400 ] + [
1346 link_to(repo.just_name, route_path('repo_summary', repo_name=repo.repo_name),
1401 link_to(repo.just_name, route_path('repo_summary', repo_name=repo.repo_name),
1347 title='last change:{}'.format(format_date(repo.last_commit_change)))
1402 title='last change:{}'.format(format_date(repo.last_commit_change)))
1348 ]
1403 ]
1349
1404
1350 return literal(' &raquo; '.join(path))
1405 return literal(' &raquo; '.join(path))
1351
1406
1352
1407
1353 def breadcrumb_repo_group_link(repo_group):
1408 def breadcrumb_repo_group_link(repo_group):
1354 """
1409 """
1355 Makes a breadcrumbs path link to repo
1410 Makes a breadcrumbs path link to repo
1356
1411
1357 ex::
1412 ex::
1358 group >> subgroup
1413 group >> subgroup
1359
1414
1360 :param repo_group: a Repository Group instance
1415 :param repo_group: a Repository Group instance
1361 """
1416 """
1362
1417
1363 path = [
1418 path = [
1364 link_to(group.name,
1419 link_to(group.name,
1365 route_path('repo_group_home', repo_group_name=group.group_name),
1420 route_path('repo_group_home', repo_group_name=group.group_name),
1366 title='last change:{}'.format(format_date(group.last_commit_change)))
1421 title='last change:{}'.format(format_date(group.last_commit_change)))
1367 for group in repo_group.parents
1422 for group in repo_group.parents
1368 ] + [
1423 ] + [
1369 link_to(repo_group.name,
1424 link_to(repo_group.name,
1370 route_path('repo_group_home', repo_group_name=repo_group.group_name),
1425 route_path('repo_group_home', repo_group_name=repo_group.group_name),
1371 title='last change:{}'.format(format_date(repo_group.last_commit_change)))
1426 title='last change:{}'.format(format_date(repo_group.last_commit_change)))
1372 ]
1427 ]
1373
1428
1374 return literal(' &raquo; '.join(path))
1429 return literal(' &raquo; '.join(path))
1375
1430
1376
1431
1377 def format_byte_size_binary(file_size):
1432 def format_byte_size_binary(file_size):
1378 """
1433 """
1379 Formats file/folder sizes to standard.
1434 Formats file/folder sizes to standard.
1380 """
1435 """
1381 if file_size is None:
1436 if file_size is None:
1382 file_size = 0
1437 file_size = 0
1383
1438
1384 formatted_size = format_byte_size(file_size, binary=True)
1439 formatted_size = format_byte_size(file_size, binary=True)
1385 return formatted_size
1440 return formatted_size
1386
1441
1387
1442
1388 def urlify_text(text_, safe=True, **href_attrs):
1443 def urlify_text(text_, safe=True, **href_attrs):
1389 """
1444 """
1390 Extract urls from text and make html links out of them
1445 Extract urls from text and make html links out of them
1391 """
1446 """
1392
1447
1393 url_pat = re.compile(r'''(http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@#.&+]'''
1448 url_pat = re.compile(r'''(http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@#.&+]'''
1394 '''|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)''')
1449 '''|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)''')
1395
1450
1396 def url_func(match_obj):
1451 def url_func(match_obj):
1397 url_full = match_obj.groups()[0]
1452 url_full = match_obj.groups()[0]
1398 a_options = dict(href_attrs)
1453 a_options = dict(href_attrs)
1399 a_options['href'] = url_full
1454 a_options['href'] = url_full
1400 a_text = url_full
1455 a_text = url_full
1401 return HTML.tag("a", a_text, **a_options)
1456 return HTML.tag("a", a_text, **a_options)
1402
1457
1403 _new_text = url_pat.sub(url_func, text_)
1458 _new_text = url_pat.sub(url_func, text_)
1404
1459
1405 if safe:
1460 if safe:
1406 return literal(_new_text)
1461 return literal(_new_text)
1407 return _new_text
1462 return _new_text
1408
1463
1409
1464
1410 def urlify_commits(text_, repo_name):
1465 def urlify_commits(text_, repo_name):
1411 """
1466 """
1412 Extract commit ids from text and make link from them
1467 Extract commit ids from text and make link from them
1413
1468
1414 :param text_:
1469 :param text_:
1415 :param repo_name: repo name to build the URL with
1470 :param repo_name: repo name to build the URL with
1416 """
1471 """
1417
1472
1418 url_pat = re.compile(r'(^|\s)([0-9a-fA-F]{12,40})($|\s)')
1473 url_pat = re.compile(r'(^|\s)([0-9a-fA-F]{12,40})($|\s)')
1419
1474
1420 def url_func(match_obj):
1475 def url_func(match_obj):
1421 commit_id = match_obj.groups()[1]
1476 commit_id = match_obj.groups()[1]
1422 pref = match_obj.groups()[0]
1477 pref = match_obj.groups()[0]
1423 suf = match_obj.groups()[2]
1478 suf = match_obj.groups()[2]
1424
1479
1425 tmpl = (
1480 tmpl = (
1426 '%(pref)s<a class="tooltip-hovercard %(cls)s" href="%(url)s" data-hovercard-alt="%(hovercard_alt)s" data-hovercard-url="%(hovercard_url)s">'
1481 '%(pref)s<a class="tooltip-hovercard %(cls)s" href="%(url)s" data-hovercard-alt="%(hovercard_alt)s" data-hovercard-url="%(hovercard_url)s">'
1427 '%(commit_id)s</a>%(suf)s'
1482 '%(commit_id)s</a>%(suf)s'
1428 )
1483 )
1429 return tmpl % {
1484 return tmpl % {
1430 'pref': pref,
1485 'pref': pref,
1431 'cls': 'revision-link',
1486 'cls': 'revision-link',
1432 'url': route_url(
1487 'url': route_url(
1433 'repo_commit', repo_name=repo_name, commit_id=commit_id),
1488 'repo_commit', repo_name=repo_name, commit_id=commit_id),
1434 'commit_id': commit_id,
1489 'commit_id': commit_id,
1435 'suf': suf,
1490 'suf': suf,
1436 'hovercard_alt': 'Commit: {}'.format(commit_id),
1491 'hovercard_alt': 'Commit: {}'.format(commit_id),
1437 'hovercard_url': route_url(
1492 'hovercard_url': route_url(
1438 'hovercard_repo_commit', repo_name=repo_name, commit_id=commit_id)
1493 'hovercard_repo_commit', repo_name=repo_name, commit_id=commit_id)
1439 }
1494 }
1440
1495
1441 new_text = url_pat.sub(url_func, text_)
1496 new_text = url_pat.sub(url_func, text_)
1442
1497
1443 return new_text
1498 return new_text
1444
1499
1445
1500
1446 def _process_url_func(match_obj, repo_name, uid, entry,
1501 def _process_url_func(match_obj, repo_name, uid, entry,
1447 return_raw_data=False, link_format='html'):
1502 return_raw_data=False, link_format='html'):
1448 pref = ''
1503 pref = ''
1449 if match_obj.group().startswith(' '):
1504 if match_obj.group().startswith(' '):
1450 pref = ' '
1505 pref = ' '
1451
1506
1452 issue_id = ''.join(match_obj.groups())
1507 issue_id = ''.join(match_obj.groups())
1453
1508
1454 if link_format == 'html':
1509 if link_format == 'html':
1455 tmpl = (
1510 tmpl = (
1456 '%(pref)s<a class="tooltip %(cls)s" href="%(url)s" title="%(title)s">'
1511 '%(pref)s<a class="tooltip %(cls)s" href="%(url)s" title="%(title)s">'
1457 '%(issue-prefix)s%(id-repr)s'
1512 '%(issue-prefix)s%(id-repr)s'
1458 '</a>')
1513 '</a>')
1459 elif link_format == 'html+hovercard':
1514 elif link_format == 'html+hovercard':
1460 tmpl = (
1515 tmpl = (
1461 '%(pref)s<a class="tooltip-hovercard %(cls)s" href="%(url)s" data-hovercard-url="%(hovercard_url)s">'
1516 '%(pref)s<a class="tooltip-hovercard %(cls)s" href="%(url)s" data-hovercard-url="%(hovercard_url)s">'
1462 '%(issue-prefix)s%(id-repr)s'
1517 '%(issue-prefix)s%(id-repr)s'
1463 '</a>')
1518 '</a>')
1464 elif link_format in ['rst', 'rst+hovercard']:
1519 elif link_format in ['rst', 'rst+hovercard']:
1465 tmpl = '`%(issue-prefix)s%(id-repr)s <%(url)s>`_'
1520 tmpl = '`%(issue-prefix)s%(id-repr)s <%(url)s>`_'
1466 elif link_format in ['markdown', 'markdown+hovercard']:
1521 elif link_format in ['markdown', 'markdown+hovercard']:
1467 tmpl = '[%(pref)s%(issue-prefix)s%(id-repr)s](%(url)s)'
1522 tmpl = '[%(pref)s%(issue-prefix)s%(id-repr)s](%(url)s)'
1468 else:
1523 else:
1469 raise ValueError('Bad link_format:{}'.format(link_format))
1524 raise ValueError('Bad link_format:{}'.format(link_format))
1470
1525
1471 (repo_name_cleaned,
1526 (repo_name_cleaned,
1472 parent_group_name) = RepoGroupModel()._get_group_name_and_parent(repo_name)
1527 parent_group_name) = RepoGroupModel()._get_group_name_and_parent(repo_name)
1473
1528
1474 # variables replacement
1529 # variables replacement
1475 named_vars = {
1530 named_vars = {
1476 'id': issue_id,
1531 'id': issue_id,
1477 'repo': repo_name,
1532 'repo': repo_name,
1478 'repo_name': repo_name_cleaned,
1533 'repo_name': repo_name_cleaned,
1479 'group_name': parent_group_name,
1534 'group_name': parent_group_name,
1480 # set dummy keys so we always have them
1535 # set dummy keys so we always have them
1481 'hostname': '',
1536 'hostname': '',
1482 'netloc': '',
1537 'netloc': '',
1483 'scheme': ''
1538 'scheme': ''
1484 }
1539 }
1485
1540
1486 request = get_current_request()
1541 request = get_current_request()
1487 if request:
1542 if request:
1488 # exposes, hostname, netloc, scheme
1543 # exposes, hostname, netloc, scheme
1489 host_data = get_host_info(request)
1544 host_data = get_host_info(request)
1490 named_vars.update(host_data)
1545 named_vars.update(host_data)
1491
1546
1492 # named regex variables
1547 # named regex variables
1493 named_vars.update(match_obj.groupdict())
1548 named_vars.update(match_obj.groupdict())
1494 _url = string.Template(entry['url']).safe_substitute(**named_vars)
1549 _url = string.Template(entry['url']).safe_substitute(**named_vars)
1495 desc = string.Template(entry['desc']).safe_substitute(**named_vars)
1550 desc = string.Template(entry['desc']).safe_substitute(**named_vars)
1496 hovercard_url = string.Template(entry.get('hovercard_url', '')).safe_substitute(**named_vars)
1551 hovercard_url = string.Template(entry.get('hovercard_url', '')).safe_substitute(**named_vars)
1497
1552
1498 def quote_cleaner(input_str):
1553 def quote_cleaner(input_str):
1499 """Remove quotes as it's HTML"""
1554 """Remove quotes as it's HTML"""
1500 return input_str.replace('"', '')
1555 return input_str.replace('"', '')
1501
1556
1502 data = {
1557 data = {
1503 'pref': pref,
1558 'pref': pref,
1504 'cls': quote_cleaner('issue-tracker-link'),
1559 'cls': quote_cleaner('issue-tracker-link'),
1505 'url': quote_cleaner(_url),
1560 'url': quote_cleaner(_url),
1506 'id-repr': issue_id,
1561 'id-repr': issue_id,
1507 'issue-prefix': entry['pref'],
1562 'issue-prefix': entry['pref'],
1508 'serv': entry['url'],
1563 'serv': entry['url'],
1509 'title': bleach.clean(desc, strip=True),
1564 'title': bleach.clean(desc, strip=True),
1510 'hovercard_url': hovercard_url
1565 'hovercard_url': hovercard_url
1511 }
1566 }
1512
1567
1513 if return_raw_data:
1568 if return_raw_data:
1514 return {
1569 return {
1515 'id': issue_id,
1570 'id': issue_id,
1516 'url': _url
1571 'url': _url
1517 }
1572 }
1518 return tmpl % data
1573 return tmpl % data
1519
1574
1520
1575
1521 def get_active_pattern_entries(repo_name):
1576 def get_active_pattern_entries(repo_name):
1522 repo = None
1577 repo = None
1523 if repo_name:
1578 if repo_name:
1524 # Retrieving repo_name to avoid invalid repo_name to explode on
1579 # Retrieving repo_name to avoid invalid repo_name to explode on
1525 # IssueTrackerSettingsModel but still passing invalid name further down
1580 # IssueTrackerSettingsModel but still passing invalid name further down
1526 repo = Repository.get_by_repo_name(repo_name, cache=True)
1581 repo = Repository.get_by_repo_name(repo_name, cache=True)
1527
1582
1528 settings_model = IssueTrackerSettingsModel(repo=repo)
1583 settings_model = IssueTrackerSettingsModel(repo=repo)
1529 active_entries = settings_model.get_settings(cache=True)
1584 active_entries = settings_model.get_settings(cache=True)
1530 return active_entries
1585 return active_entries
1531
1586
1532
1587
1533 pr_pattern_re = re.compile(r'(?:(?:^!)|(?: !))(\d+)')
1588 pr_pattern_re = re.compile(r'(?:(?:^!)|(?: !))(\d+)')
1534
1589
1535
1590
1536 def process_patterns(text_string, repo_name, link_format='html', active_entries=None):
1591 def process_patterns(text_string, repo_name, link_format='html', active_entries=None):
1537
1592
1538 allowed_formats = ['html', 'rst', 'markdown',
1593 allowed_formats = ['html', 'rst', 'markdown',
1539 'html+hovercard', 'rst+hovercard', 'markdown+hovercard']
1594 'html+hovercard', 'rst+hovercard', 'markdown+hovercard']
1540 if link_format not in allowed_formats:
1595 if link_format not in allowed_formats:
1541 raise ValueError('Link format can be only one of:{} got {}'.format(
1596 raise ValueError('Link format can be only one of:{} got {}'.format(
1542 allowed_formats, link_format))
1597 allowed_formats, link_format))
1543
1598
1544 if active_entries is None:
1599 if active_entries is None:
1545 log.debug('Fetch active patterns for repo: %s', repo_name)
1600 log.debug('Fetch active patterns for repo: %s', repo_name)
1546 active_entries = get_active_pattern_entries(repo_name)
1601 active_entries = get_active_pattern_entries(repo_name)
1547
1602
1548 issues_data = []
1603 issues_data = []
1549 new_text = text_string
1604 new_text = text_string
1550
1605
1551 log.debug('Got %s entries to process', len(active_entries))
1606 log.debug('Got %s entries to process', len(active_entries))
1552 for uid, entry in active_entries.items():
1607 for uid, entry in active_entries.items():
1553 log.debug('found issue tracker entry with uid %s', uid)
1608 log.debug('found issue tracker entry with uid %s', uid)
1554
1609
1555 if not (entry['pat'] and entry['url']):
1610 if not (entry['pat'] and entry['url']):
1556 log.debug('skipping due to missing data')
1611 log.debug('skipping due to missing data')
1557 continue
1612 continue
1558
1613
1559 log.debug('issue tracker entry: uid: `%s` PAT:%s URL:%s PREFIX:%s',
1614 log.debug('issue tracker entry: uid: `%s` PAT:%s URL:%s PREFIX:%s',
1560 uid, entry['pat'], entry['url'], entry['pref'])
1615 uid, entry['pat'], entry['url'], entry['pref'])
1561
1616
1562 if entry.get('pat_compiled'):
1617 if entry.get('pat_compiled'):
1563 pattern = entry['pat_compiled']
1618 pattern = entry['pat_compiled']
1564 else:
1619 else:
1565 try:
1620 try:
1566 pattern = re.compile(r'%s' % entry['pat'])
1621 pattern = re.compile(r'%s' % entry['pat'])
1567 except re.error:
1622 except re.error:
1568 log.exception('issue tracker pattern: `%s` failed to compile', entry['pat'])
1623 log.exception('issue tracker pattern: `%s` failed to compile', entry['pat'])
1569 continue
1624 continue
1570
1625
1571 data_func = partial(
1626 data_func = partial(
1572 _process_url_func, repo_name=repo_name, entry=entry, uid=uid,
1627 _process_url_func, repo_name=repo_name, entry=entry, uid=uid,
1573 return_raw_data=True)
1628 return_raw_data=True)
1574
1629
1575 for match_obj in pattern.finditer(text_string):
1630 for match_obj in pattern.finditer(text_string):
1576 issues_data.append(data_func(match_obj))
1631 issues_data.append(data_func(match_obj))
1577
1632
1578 url_func = partial(
1633 url_func = partial(
1579 _process_url_func, repo_name=repo_name, entry=entry, uid=uid,
1634 _process_url_func, repo_name=repo_name, entry=entry, uid=uid,
1580 link_format=link_format)
1635 link_format=link_format)
1581
1636
1582 new_text = pattern.sub(url_func, new_text)
1637 new_text = pattern.sub(url_func, new_text)
1583 log.debug('processed prefix:uid `%s`', uid)
1638 log.debug('processed prefix:uid `%s`', uid)
1584
1639
1585 # finally use global replace, eg !123 -> pr-link, those will not catch
1640 # finally use global replace, eg !123 -> pr-link, those will not catch
1586 # if already similar pattern exists
1641 # if already similar pattern exists
1587 server_url = '${scheme}://${netloc}'
1642 server_url = '${scheme}://${netloc}'
1588 pr_entry = {
1643 pr_entry = {
1589 'pref': '!',
1644 'pref': '!',
1590 'url': server_url + '/_admin/pull-requests/${id}',
1645 'url': server_url + '/_admin/pull-requests/${id}',
1591 'desc': 'Pull Request !${id}',
1646 'desc': 'Pull Request !${id}',
1592 'hovercard_url': server_url + '/_hovercard/pull_request/${id}'
1647 'hovercard_url': server_url + '/_hovercard/pull_request/${id}'
1593 }
1648 }
1594 pr_url_func = partial(
1649 pr_url_func = partial(
1595 _process_url_func, repo_name=repo_name, entry=pr_entry, uid=None,
1650 _process_url_func, repo_name=repo_name, entry=pr_entry, uid=None,
1596 link_format=link_format+'+hovercard')
1651 link_format=link_format+'+hovercard')
1597 new_text = pr_pattern_re.sub(pr_url_func, new_text)
1652 new_text = pr_pattern_re.sub(pr_url_func, new_text)
1598 log.debug('processed !pr pattern')
1653 log.debug('processed !pr pattern')
1599
1654
1600 return new_text, issues_data
1655 return new_text, issues_data
1601
1656
1602
1657
1603 def urlify_commit_message(commit_text, repository=None, active_pattern_entries=None):
1658 def urlify_commit_message(commit_text, repository=None, active_pattern_entries=None):
1604 """
1659 """
1605 Parses given text message and makes proper links.
1660 Parses given text message and makes proper links.
1606 issues are linked to given issue-server, and rest is a commit link
1661 issues are linked to given issue-server, and rest is a commit link
1607 """
1662 """
1608
1663
1609 def escaper(_text):
1664 def escaper(_text):
1610 return _text.replace('<', '&lt;').replace('>', '&gt;')
1665 return _text.replace('<', '&lt;').replace('>', '&gt;')
1611
1666
1612 new_text = escaper(commit_text)
1667 new_text = escaper(commit_text)
1613
1668
1614 # extract http/https links and make them real urls
1669 # extract http/https links and make them real urls
1615 new_text = urlify_text(new_text, safe=False)
1670 new_text = urlify_text(new_text, safe=False)
1616
1671
1617 # urlify commits - extract commit ids and make link out of them, if we have
1672 # urlify commits - extract commit ids and make link out of them, if we have
1618 # the scope of repository present.
1673 # the scope of repository present.
1619 if repository:
1674 if repository:
1620 new_text = urlify_commits(new_text, repository)
1675 new_text = urlify_commits(new_text, repository)
1621
1676
1622 # process issue tracker patterns
1677 # process issue tracker patterns
1623 new_text, issues = process_patterns(new_text, repository or '',
1678 new_text, issues = process_patterns(new_text, repository or '',
1624 active_entries=active_pattern_entries)
1679 active_entries=active_pattern_entries)
1625
1680
1626 return literal(new_text)
1681 return literal(new_text)
1627
1682
1628
1683
1629 def render_binary(repo_name, file_obj):
1684 def render_binary(repo_name, file_obj):
1630 """
1685 """
1631 Choose how to render a binary file
1686 Choose how to render a binary file
1632 """
1687 """
1633
1688
1634 # unicode
1689 # unicode
1635 filename = file_obj.name
1690 filename = file_obj.name
1636
1691
1637 # images
1692 # images
1638 for ext in ['*.png', '*.jpeg', '*.jpg', '*.ico', '*.gif']:
1693 for ext in ['*.png', '*.jpeg', '*.jpg', '*.ico', '*.gif']:
1639 if fnmatch.fnmatch(filename, pat=ext):
1694 if fnmatch.fnmatch(filename, pat=ext):
1640 src = route_path(
1695 src = route_path(
1641 'repo_file_raw', repo_name=repo_name,
1696 'repo_file_raw', repo_name=repo_name,
1642 commit_id=file_obj.commit.raw_id,
1697 commit_id=file_obj.commit.raw_id,
1643 f_path=file_obj.path)
1698 f_path=file_obj.path)
1644
1699
1645 return literal(
1700 return literal(
1646 '<img class="rendered-binary" alt="rendered-image" src="{}">'.format(src))
1701 '<img class="rendered-binary" alt="rendered-image" src="{}">'.format(src))
1647
1702
1648
1703
1649 def renderer_from_filename(filename, exclude=None):
1704 def renderer_from_filename(filename, exclude=None):
1650 """
1705 """
1651 choose a renderer based on filename, this works only for text based files
1706 choose a renderer based on filename, this works only for text based files
1652 """
1707 """
1653
1708
1654 # ipython
1709 # ipython
1655 for ext in ['*.ipynb']:
1710 for ext in ['*.ipynb']:
1656 if fnmatch.fnmatch(filename, pat=ext):
1711 if fnmatch.fnmatch(filename, pat=ext):
1657 return 'jupyter'
1712 return 'jupyter'
1658
1713
1659 is_markup = MarkupRenderer.renderer_from_filename(filename, exclude=exclude)
1714 is_markup = MarkupRenderer.renderer_from_filename(filename, exclude=exclude)
1660 if is_markup:
1715 if is_markup:
1661 return is_markup
1716 return is_markup
1662 return None
1717 return None
1663
1718
1664
1719
1665 def render(source, renderer='rst', mentions=False, relative_urls=None,
1720 def render(source, renderer='rst', mentions=False, relative_urls=None,
1666 repo_name=None, active_pattern_entries=None):
1721 repo_name=None, active_pattern_entries=None):
1667
1722
1668 def maybe_convert_relative_links(html_source):
1723 def maybe_convert_relative_links(html_source):
1669 if relative_urls:
1724 if relative_urls:
1670 return relative_links(html_source, relative_urls)
1725 return relative_links(html_source, relative_urls)
1671 return html_source
1726 return html_source
1672
1727
1673 if renderer == 'plain':
1728 if renderer == 'plain':
1674 return literal(
1729 return literal(
1675 MarkupRenderer.plain(source, leading_newline=False))
1730 MarkupRenderer.plain(source, leading_newline=False))
1676
1731
1677 elif renderer == 'rst':
1732 elif renderer == 'rst':
1678 if repo_name:
1733 if repo_name:
1679 # process patterns on comments if we pass in repo name
1734 # process patterns on comments if we pass in repo name
1680 source, issues = process_patterns(
1735 source, issues = process_patterns(
1681 source, repo_name, link_format='rst',
1736 source, repo_name, link_format='rst',
1682 active_entries=active_pattern_entries)
1737 active_entries=active_pattern_entries)
1683
1738
1684 return literal(
1739 return literal(
1685 '<div class="rst-block">%s</div>' %
1740 '<div class="rst-block">%s</div>' %
1686 maybe_convert_relative_links(
1741 maybe_convert_relative_links(
1687 MarkupRenderer.rst(source, mentions=mentions)))
1742 MarkupRenderer.rst(source, mentions=mentions)))
1688
1743
1689 elif renderer == 'markdown':
1744 elif renderer == 'markdown':
1690 if repo_name:
1745 if repo_name:
1691 # process patterns on comments if we pass in repo name
1746 # process patterns on comments if we pass in repo name
1692 source, issues = process_patterns(
1747 source, issues = process_patterns(
1693 source, repo_name, link_format='markdown',
1748 source, repo_name, link_format='markdown',
1694 active_entries=active_pattern_entries)
1749 active_entries=active_pattern_entries)
1695
1750
1696 return literal(
1751 return literal(
1697 '<div class="markdown-block">%s</div>' %
1752 '<div class="markdown-block">%s</div>' %
1698 maybe_convert_relative_links(
1753 maybe_convert_relative_links(
1699 MarkupRenderer.markdown(source, flavored=True,
1754 MarkupRenderer.markdown(source, flavored=True,
1700 mentions=mentions)))
1755 mentions=mentions)))
1701
1756
1702 elif renderer == 'jupyter':
1757 elif renderer == 'jupyter':
1703 return literal(
1758 return literal(
1704 '<div class="ipynb">%s</div>' %
1759 '<div class="ipynb">%s</div>' %
1705 maybe_convert_relative_links(
1760 maybe_convert_relative_links(
1706 MarkupRenderer.jupyter(source)))
1761 MarkupRenderer.jupyter(source)))
1707
1762
1708 # None means just show the file-source
1763 # None means just show the file-source
1709 return None
1764 return None
1710
1765
1711
1766
1712 def commit_status(repo, commit_id):
1767 def commit_status(repo, commit_id):
1713 return ChangesetStatusModel().get_status(repo, commit_id)
1768 return ChangesetStatusModel().get_status(repo, commit_id)
1714
1769
1715
1770
1716 def commit_status_lbl(commit_status):
1771 def commit_status_lbl(commit_status):
1717 return dict(ChangesetStatus.STATUSES).get(commit_status)
1772 return dict(ChangesetStatus.STATUSES).get(commit_status)
1718
1773
1719
1774
1720 def commit_time(repo_name, commit_id):
1775 def commit_time(repo_name, commit_id):
1721 repo = Repository.get_by_repo_name(repo_name)
1776 repo = Repository.get_by_repo_name(repo_name)
1722 commit = repo.get_commit(commit_id=commit_id)
1777 commit = repo.get_commit(commit_id=commit_id)
1723 return commit.date
1778 return commit.date
1724
1779
1725
1780
1726 def get_permission_name(key):
1781 def get_permission_name(key):
1727 return dict(Permission.PERMS).get(key)
1782 return dict(Permission.PERMS).get(key)
1728
1783
1729
1784
1730 def journal_filter_help(request):
1785 def journal_filter_help(request):
1731 _ = request.translate
1786 _ = request.translate
1732 from rhodecode.lib.audit_logger import ACTIONS
1787 from rhodecode.lib.audit_logger import ACTIONS
1733 actions = '\n'.join(textwrap.wrap(', '.join(sorted(ACTIONS.keys())), 80))
1788 actions = '\n'.join(textwrap.wrap(', '.join(sorted(ACTIONS.keys())), 80))
1734
1789
1735 return _(
1790 return _(
1736 'Example filter terms:\n' +
1791 'Example filter terms:\n' +
1737 ' repository:vcs\n' +
1792 ' repository:vcs\n' +
1738 ' username:marcin\n' +
1793 ' username:marcin\n' +
1739 ' username:(NOT marcin)\n' +
1794 ' username:(NOT marcin)\n' +
1740 ' action:*push*\n' +
1795 ' action:*push*\n' +
1741 ' ip:127.0.0.1\n' +
1796 ' ip:127.0.0.1\n' +
1742 ' date:20120101\n' +
1797 ' date:20120101\n' +
1743 ' date:[20120101100000 TO 20120102]\n' +
1798 ' date:[20120101100000 TO 20120102]\n' +
1744 '\n' +
1799 '\n' +
1745 'Actions: {actions}\n' +
1800 'Actions: {actions}\n' +
1746 '\n' +
1801 '\n' +
1747 'Generate wildcards using \'*\' character:\n' +
1802 'Generate wildcards using \'*\' character:\n' +
1748 ' "repository:vcs*" - search everything starting with \'vcs\'\n' +
1803 ' "repository:vcs*" - search everything starting with \'vcs\'\n' +
1749 ' "repository:*vcs*" - search for repository containing \'vcs\'\n' +
1804 ' "repository:*vcs*" - search for repository containing \'vcs\'\n' +
1750 '\n' +
1805 '\n' +
1751 'Optional AND / OR operators in queries\n' +
1806 'Optional AND / OR operators in queries\n' +
1752 ' "repository:vcs OR repository:test"\n' +
1807 ' "repository:vcs OR repository:test"\n' +
1753 ' "username:test AND repository:test*"\n'
1808 ' "username:test AND repository:test*"\n'
1754 ).format(actions=actions)
1809 ).format(actions=actions)
1755
1810
1756
1811
1757 def not_mapped_error(repo_name):
1812 def not_mapped_error(repo_name):
1758 from rhodecode.translation import _
1813 from rhodecode.translation import _
1759 flash(_('%s repository is not mapped to db perhaps'
1814 flash(_('%s repository is not mapped to db perhaps'
1760 ' it was created or renamed from the filesystem'
1815 ' it was created or renamed from the filesystem'
1761 ' please run the application again'
1816 ' please run the application again'
1762 ' in order to rescan repositories') % repo_name, category='error')
1817 ' in order to rescan repositories') % repo_name, category='error')
1763
1818
1764
1819
1765 def ip_range(ip_addr):
1820 def ip_range(ip_addr):
1766 from rhodecode.model.db import UserIpMap
1821 from rhodecode.model.db import UserIpMap
1767 s, e = UserIpMap._get_ip_range(ip_addr)
1822 s, e = UserIpMap._get_ip_range(ip_addr)
1768 return '%s - %s' % (s, e)
1823 return '%s - %s' % (s, e)
1769
1824
1770
1825
1771 def form(url, method='post', needs_csrf_token=True, **attrs):
1826 def form(url, method='post', needs_csrf_token=True, **attrs):
1772 """Wrapper around webhelpers.tags.form to prevent CSRF attacks."""
1827 """Wrapper around webhelpers.tags.form to prevent CSRF attacks."""
1773 if method.lower() != 'get' and needs_csrf_token:
1828 if method.lower() != 'get' and needs_csrf_token:
1774 raise Exception(
1829 raise Exception(
1775 'Forms to POST/PUT/DELETE endpoints should have (in general) a ' +
1830 'Forms to POST/PUT/DELETE endpoints should have (in general) a ' +
1776 'CSRF token. If the endpoint does not require such token you can ' +
1831 'CSRF token. If the endpoint does not require such token you can ' +
1777 'explicitly set the parameter needs_csrf_token to false.')
1832 'explicitly set the parameter needs_csrf_token to false.')
1778
1833
1779 return insecure_form(url, method=method, **attrs)
1834 return insecure_form(url, method=method, **attrs)
1780
1835
1781
1836
1782 def secure_form(form_url, method="POST", multipart=False, **attrs):
1837 def secure_form(form_url, method="POST", multipart=False, **attrs):
1783 """Start a form tag that points the action to an url. This
1838 """Start a form tag that points the action to an url. This
1784 form tag will also include the hidden field containing
1839 form tag will also include the hidden field containing
1785 the auth token.
1840 the auth token.
1786
1841
1787 The url options should be given either as a string, or as a
1842 The url options should be given either as a string, or as a
1788 ``url()`` function. The method for the form defaults to POST.
1843 ``url()`` function. The method for the form defaults to POST.
1789
1844
1790 Options:
1845 Options:
1791
1846
1792 ``multipart``
1847 ``multipart``
1793 If set to True, the enctype is set to "multipart/form-data".
1848 If set to True, the enctype is set to "multipart/form-data".
1794 ``method``
1849 ``method``
1795 The method to use when submitting the form, usually either
1850 The method to use when submitting the form, usually either
1796 "GET" or "POST". If "PUT", "DELETE", or another verb is used, a
1851 "GET" or "POST". If "PUT", "DELETE", or another verb is used, a
1797 hidden input with name _method is added to simulate the verb
1852 hidden input with name _method is added to simulate the verb
1798 over POST.
1853 over POST.
1799
1854
1800 """
1855 """
1801
1856
1802 if 'request' in attrs:
1857 if 'request' in attrs:
1803 session = attrs['request'].session
1858 session = attrs['request'].session
1804 del attrs['request']
1859 del attrs['request']
1805 else:
1860 else:
1806 raise ValueError(
1861 raise ValueError(
1807 'Calling this form requires request= to be passed as argument')
1862 'Calling this form requires request= to be passed as argument')
1808
1863
1809 _form = insecure_form(form_url, method, multipart, **attrs)
1864 _form = insecure_form(form_url, method, multipart, **attrs)
1810 token = literal(
1865 token = literal(
1811 '<input type="hidden" name="{}" value="{}">'.format(
1866 '<input type="hidden" name="{}" value="{}">'.format(
1812 csrf_token_key, get_csrf_token(session)))
1867 csrf_token_key, get_csrf_token(session)))
1813
1868
1814 return literal("%s\n%s" % (_form, token))
1869 return literal("%s\n%s" % (_form, token))
1815
1870
1816
1871
1817 def dropdownmenu(name, selected, options, enable_filter=False, **attrs):
1872 def dropdownmenu(name, selected, options, enable_filter=False, **attrs):
1818 select_html = select(name, selected, options, **attrs)
1873 select_html = select(name, selected, options, **attrs)
1819
1874
1820 select2 = """
1875 select2 = """
1821 <script>
1876 <script>
1822 $(document).ready(function() {
1877 $(document).ready(function() {
1823 $('#%s').select2({
1878 $('#%s').select2({
1824 containerCssClass: 'drop-menu %s',
1879 containerCssClass: 'drop-menu %s',
1825 dropdownCssClass: 'drop-menu-dropdown',
1880 dropdownCssClass: 'drop-menu-dropdown',
1826 dropdownAutoWidth: true%s
1881 dropdownAutoWidth: true%s
1827 });
1882 });
1828 });
1883 });
1829 </script>
1884 </script>
1830 """
1885 """
1831
1886
1832 filter_option = """,
1887 filter_option = """,
1833 minimumResultsForSearch: -1
1888 minimumResultsForSearch: -1
1834 """
1889 """
1835 input_id = attrs.get('id') or name
1890 input_id = attrs.get('id') or name
1836 extra_classes = ' '.join(attrs.pop('extra_classes', []))
1891 extra_classes = ' '.join(attrs.pop('extra_classes', []))
1837 filter_enabled = "" if enable_filter else filter_option
1892 filter_enabled = "" if enable_filter else filter_option
1838 select_script = literal(select2 % (input_id, extra_classes, filter_enabled))
1893 select_script = literal(select2 % (input_id, extra_classes, filter_enabled))
1839
1894
1840 return literal(select_html+select_script)
1895 return literal(select_html+select_script)
1841
1896
1842
1897
1843 def get_visual_attr(tmpl_context_var, attr_name):
1898 def get_visual_attr(tmpl_context_var, attr_name):
1844 """
1899 """
1845 A safe way to get a variable from visual variable of template context
1900 A safe way to get a variable from visual variable of template context
1846
1901
1847 :param tmpl_context_var: instance of tmpl_context, usually present as `c`
1902 :param tmpl_context_var: instance of tmpl_context, usually present as `c`
1848 :param attr_name: name of the attribute we fetch from the c.visual
1903 :param attr_name: name of the attribute we fetch from the c.visual
1849 """
1904 """
1850 visual = getattr(tmpl_context_var, 'visual', None)
1905 visual = getattr(tmpl_context_var, 'visual', None)
1851 if not visual:
1906 if not visual:
1852 return
1907 return
1853 else:
1908 else:
1854 return getattr(visual, attr_name, None)
1909 return getattr(visual, attr_name, None)
1855
1910
1856
1911
1857 def get_last_path_part(file_node):
1912 def get_last_path_part(file_node):
1858 if not file_node.path:
1913 if not file_node.path:
1859 return u'/'
1914 return u'/'
1860
1915
1861 path = safe_unicode(file_node.path.split('/')[-1])
1916 path = safe_unicode(file_node.path.split('/')[-1])
1862 return u'../' + path
1917 return u'../' + path
1863
1918
1864
1919
1865 def route_url(*args, **kwargs):
1920 def route_url(*args, **kwargs):
1866 """
1921 """
1867 Wrapper around pyramids `route_url` (fully qualified url) function.
1922 Wrapper around pyramids `route_url` (fully qualified url) function.
1868 """
1923 """
1869 req = get_current_request()
1924 req = get_current_request()
1870 return req.route_url(*args, **kwargs)
1925 return req.route_url(*args, **kwargs)
1871
1926
1872
1927
1873 def route_path(*args, **kwargs):
1928 def route_path(*args, **kwargs):
1874 """
1929 """
1875 Wrapper around pyramids `route_path` function.
1930 Wrapper around pyramids `route_path` function.
1876 """
1931 """
1877 req = get_current_request()
1932 req = get_current_request()
1878 return req.route_path(*args, **kwargs)
1933 return req.route_path(*args, **kwargs)
1879
1934
1880
1935
1881 def route_path_or_none(*args, **kwargs):
1936 def route_path_or_none(*args, **kwargs):
1882 try:
1937 try:
1883 return route_path(*args, **kwargs)
1938 return route_path(*args, **kwargs)
1884 except KeyError:
1939 except KeyError:
1885 return None
1940 return None
1886
1941
1887
1942
1888 def current_route_path(request, **kw):
1943 def current_route_path(request, **kw):
1889 new_args = request.GET.mixed()
1944 new_args = request.GET.mixed()
1890 new_args.update(kw)
1945 new_args.update(kw)
1891 return request.current_route_path(_query=new_args)
1946 return request.current_route_path(_query=new_args)
1892
1947
1893
1948
1894 def curl_api_example(method, args):
1949 def curl_api_example(method, args):
1895 args_json = json.dumps(OrderedDict([
1950 args_json = json.dumps(OrderedDict([
1896 ('id', 1),
1951 ('id', 1),
1897 ('auth_token', 'SECRET'),
1952 ('auth_token', 'SECRET'),
1898 ('method', method),
1953 ('method', method),
1899 ('args', args)
1954 ('args', args)
1900 ]))
1955 ]))
1901
1956
1902 return "curl {api_url} -X POST -H 'content-type:text/plain' --data-binary '{args_json}'".format(
1957 return "curl {api_url} -X POST -H 'content-type:text/plain' --data-binary '{args_json}'".format(
1903 api_url=route_url('apiv2'),
1958 api_url=route_url('apiv2'),
1904 args_json=args_json
1959 args_json=args_json
1905 )
1960 )
1906
1961
1907
1962
1908 def api_call_example(method, args):
1963 def api_call_example(method, args):
1909 """
1964 """
1910 Generates an API call example via CURL
1965 Generates an API call example via CURL
1911 """
1966 """
1912 curl_call = curl_api_example(method, args)
1967 curl_call = curl_api_example(method, args)
1913
1968
1914 return literal(
1969 return literal(
1915 curl_call +
1970 curl_call +
1916 "<br/><br/>SECRET can be found in <a href=\"{token_url}\">auth-tokens</a> page, "
1971 "<br/><br/>SECRET can be found in <a href=\"{token_url}\">auth-tokens</a> page, "
1917 "and needs to be of `api calls` role."
1972 "and needs to be of `api calls` role."
1918 .format(token_url=route_url('my_account_auth_tokens')))
1973 .format(token_url=route_url('my_account_auth_tokens')))
1919
1974
1920
1975
1921 def notification_description(notification, request):
1976 def notification_description(notification, request):
1922 """
1977 """
1923 Generate notification human readable description based on notification type
1978 Generate notification human readable description based on notification type
1924 """
1979 """
1925 from rhodecode.model.notification import NotificationModel
1980 from rhodecode.model.notification import NotificationModel
1926 return NotificationModel().make_description(
1981 return NotificationModel().make_description(
1927 notification, translate=request.translate)
1982 notification, translate=request.translate)
1928
1983
1929
1984
1930 def go_import_header(request, db_repo=None):
1985 def go_import_header(request, db_repo=None):
1931 """
1986 """
1932 Creates a header for go-import functionality in Go Lang
1987 Creates a header for go-import functionality in Go Lang
1933 """
1988 """
1934
1989
1935 if not db_repo:
1990 if not db_repo:
1936 return
1991 return
1937 if 'go-get' not in request.GET:
1992 if 'go-get' not in request.GET:
1938 return
1993 return
1939
1994
1940 clone_url = db_repo.clone_url()
1995 clone_url = db_repo.clone_url()
1941 prefix = re.split(r'^https?:\/\/', clone_url)[-1]
1996 prefix = re.split(r'^https?:\/\/', clone_url)[-1]
1942 # we have a repo and go-get flag,
1997 # we have a repo and go-get flag,
1943 return literal('<meta name="go-import" content="{} {} {}">'.format(
1998 return literal('<meta name="go-import" content="{} {} {}">'.format(
1944 prefix, db_repo.repo_type, clone_url))
1999 prefix, db_repo.repo_type, clone_url))
1945
2000
1946
2001
1947 def reviewer_as_json(*args, **kwargs):
2002 def reviewer_as_json(*args, **kwargs):
1948 from rhodecode.apps.repository.utils import reviewer_as_json as _reviewer_as_json
2003 from rhodecode.apps.repository.utils import reviewer_as_json as _reviewer_as_json
1949 return _reviewer_as_json(*args, **kwargs)
2004 return _reviewer_as_json(*args, **kwargs)
1950
2005
1951
2006
1952 def get_repo_view_type(request):
2007 def get_repo_view_type(request):
1953 route_name = request.matched_route.name
2008 route_name = request.matched_route.name
1954 route_to_view_type = {
2009 route_to_view_type = {
1955 'repo_changelog': 'commits',
2010 'repo_changelog': 'commits',
1956 'repo_commits': 'commits',
2011 'repo_commits': 'commits',
1957 'repo_files': 'files',
2012 'repo_files': 'files',
1958 'repo_summary': 'summary',
2013 'repo_summary': 'summary',
1959 'repo_commit': 'commit'
2014 'repo_commit': 'commit'
1960 }
2015 }
1961
2016
1962 return route_to_view_type.get(route_name)
2017 return route_to_view_type.get(route_name)
1963
2018
1964
2019
1965 def is_active(menu_entry, selected):
2020 def is_active(menu_entry, selected):
1966 """
2021 """
1967 Returns active class for selecting menus in templates
2022 Returns active class for selecting menus in templates
1968 <li class=${h.is_active('settings', current_active)}></li>
2023 <li class=${h.is_active('settings', current_active)}></li>
1969 """
2024 """
1970 if not isinstance(menu_entry, list):
2025 if not isinstance(menu_entry, list):
1971 menu_entry = [menu_entry]
2026 menu_entry = [menu_entry]
1972
2027
1973 if selected in menu_entry:
2028 if selected in menu_entry:
1974 return "active"
2029 return "active"
@@ -1,81 +1,81 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2015-2020 RhodeCode GmbH
3 # Copyright (C) 2015-2020 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 from dogpile.cache import register_backend
22 from dogpile.cache import register_backend
23
23
24 register_backend(
24 register_backend(
25 "dogpile.cache.rc.memory_lru", "rhodecode.lib.rc_cache.backends",
25 "dogpile.cache.rc.memory_lru", "rhodecode.lib.rc_cache.backends",
26 "LRUMemoryBackend")
26 "LRUMemoryBackend")
27
27
28 register_backend(
28 register_backend(
29 "dogpile.cache.rc.file_namespace", "rhodecode.lib.rc_cache.backends",
29 "dogpile.cache.rc.file_namespace", "rhodecode.lib.rc_cache.backends",
30 "FileNamespaceBackend")
30 "FileNamespaceBackend")
31
31
32 register_backend(
32 register_backend(
33 "dogpile.cache.rc.redis", "rhodecode.lib.rc_cache.backends",
33 "dogpile.cache.rc.redis", "rhodecode.lib.rc_cache.backends",
34 "RedisPickleBackend")
34 "RedisPickleBackend")
35
35
36 register_backend(
36 register_backend(
37 "dogpile.cache.rc.redis_msgpack", "rhodecode.lib.rc_cache.backends",
37 "dogpile.cache.rc.redis_msgpack", "rhodecode.lib.rc_cache.backends",
38 "RedisMsgPackBackend")
38 "RedisMsgPackBackend")
39
39
40
40
41 log = logging.getLogger(__name__)
41 log = logging.getLogger(__name__)
42
42
43 from . import region_meta
43 from . import region_meta
44 from .utils import (
44 from .utils import (
45 get_default_cache_settings, backend_key_generator, get_or_create_region,
45 get_default_cache_settings, backend_key_generator, get_or_create_region,
46 clear_cache_namespace, make_region, InvalidationContext,
46 clear_cache_namespace, make_region, InvalidationContext,
47 FreshRegionCache, ActiveRegionCache)
47 FreshRegionCache, ActiveRegionCache)
48
48
49
49
50 FILE_TREE_CACHE_VER = 'v3'
50 FILE_TREE_CACHE_VER = 'v4'
51
51
52
52
53 def configure_dogpile_cache(settings):
53 def configure_dogpile_cache(settings):
54 cache_dir = settings.get('cache_dir')
54 cache_dir = settings.get('cache_dir')
55 if cache_dir:
55 if cache_dir:
56 region_meta.dogpile_config_defaults['cache_dir'] = cache_dir
56 region_meta.dogpile_config_defaults['cache_dir'] = cache_dir
57
57
58 rc_cache_data = get_default_cache_settings(settings, prefixes=['rc_cache.'])
58 rc_cache_data = get_default_cache_settings(settings, prefixes=['rc_cache.'])
59
59
60 # inspect available namespaces
60 # inspect available namespaces
61 avail_regions = set()
61 avail_regions = set()
62 for key in rc_cache_data.keys():
62 for key in rc_cache_data.keys():
63 namespace_name = key.split('.', 1)[0]
63 namespace_name = key.split('.', 1)[0]
64 avail_regions.add(namespace_name)
64 avail_regions.add(namespace_name)
65 log.debug('dogpile: found following cache regions: %s', avail_regions)
65 log.debug('dogpile: found following cache regions: %s', avail_regions)
66
66
67 # register them into namespace
67 # register them into namespace
68 for region_name in avail_regions:
68 for region_name in avail_regions:
69 new_region = make_region(
69 new_region = make_region(
70 name=region_name,
70 name=region_name,
71 function_key_generator=None
71 function_key_generator=None
72 )
72 )
73
73
74 new_region.configure_from_config(settings, 'rc_cache.{}.'.format(region_name))
74 new_region.configure_from_config(settings, 'rc_cache.{}.'.format(region_name))
75 new_region.function_key_generator = backend_key_generator(new_region.actual_backend)
75 new_region.function_key_generator = backend_key_generator(new_region.actual_backend)
76 log.debug('dogpile: registering a new region %s[%s]', region_name, new_region.__dict__)
76 log.debug('dogpile: registering a new region %s[%s]', region_name, new_region.__dict__)
77 region_meta.dogpile_cache_regions[region_name] = new_region
77 region_meta.dogpile_cache_regions[region_name] = new_region
78
78
79
79
80 def includeme(config):
80 def includeme(config):
81 configure_dogpile_cache(config.registry.settings)
81 configure_dogpile_cache(config.registry.settings)
@@ -1,1201 +1,1201 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2
2
3 <%!
3 <%!
4 ## base64 filter e.g ${ example | base64 }
4 ## base64 filter e.g ${ example | base64 }
5 def base64(text):
5 def base64(text):
6 import base64
6 import base64
7 from rhodecode.lib.helpers import safe_str
7 from rhodecode.lib.helpers import safe_str
8 return base64.encodestring(safe_str(text))
8 return base64.encodestring(safe_str(text))
9 %>
9 %>
10
10
11 <%inherit file="root.mako"/>
11 <%inherit file="root.mako"/>
12
12
13 <%include file="/ejs_templates/templates.html"/>
13 <%include file="/ejs_templates/templates.html"/>
14
14
15 <div class="outerwrapper">
15 <div class="outerwrapper">
16 <!-- HEADER -->
16 <!-- HEADER -->
17 <div class="header">
17 <div class="header">
18 <div id="header-inner" class="wrapper">
18 <div id="header-inner" class="wrapper">
19 <div id="logo">
19 <div id="logo">
20 <div class="logo-wrapper">
20 <div class="logo-wrapper">
21 <a href="${h.route_path('home')}"><img src="${h.asset('images/rhodecode-logo-white-60x60.png')}" alt="RhodeCode"/></a>
21 <a href="${h.route_path('home')}"><img src="${h.asset('images/rhodecode-logo-white-60x60.png')}" alt="RhodeCode"/></a>
22 </div>
22 </div>
23 % if c.rhodecode_name:
23 % if c.rhodecode_name:
24 <div class="branding">
24 <div class="branding">
25 <a href="${h.route_path('home')}">${h.branding(c.rhodecode_name)}</a>
25 <a href="${h.route_path('home')}">${h.branding(c.rhodecode_name)}</a>
26 </div>
26 </div>
27 % endif
27 % endif
28 </div>
28 </div>
29 <!-- MENU BAR NAV -->
29 <!-- MENU BAR NAV -->
30 ${self.menu_bar_nav()}
30 ${self.menu_bar_nav()}
31 <!-- END MENU BAR NAV -->
31 <!-- END MENU BAR NAV -->
32 </div>
32 </div>
33 </div>
33 </div>
34 ${self.menu_bar_subnav()}
34 ${self.menu_bar_subnav()}
35 <!-- END HEADER -->
35 <!-- END HEADER -->
36
36
37 <!-- CONTENT -->
37 <!-- CONTENT -->
38 <div id="content" class="wrapper">
38 <div id="content" class="wrapper">
39
39
40 <rhodecode-toast id="notifications"></rhodecode-toast>
40 <rhodecode-toast id="notifications"></rhodecode-toast>
41
41
42 <div class="main">
42 <div class="main">
43 ${next.main()}
43 ${next.main()}
44 </div>
44 </div>
45 </div>
45 </div>
46 <!-- END CONTENT -->
46 <!-- END CONTENT -->
47
47
48 </div>
48 </div>
49 <!-- FOOTER -->
49 <!-- FOOTER -->
50 <div id="footer">
50 <div id="footer">
51 <div id="footer-inner" class="title wrapper">
51 <div id="footer-inner" class="title wrapper">
52 <div>
52 <div>
53 <p class="footer-link-right">
53 <p class="footer-link-right">
54 % if c.visual.show_version:
54 % if c.visual.show_version:
55 RhodeCode Enterprise ${c.rhodecode_version} ${c.rhodecode_edition}
55 RhodeCode Enterprise ${c.rhodecode_version} ${c.rhodecode_edition}
56 % endif
56 % endif
57 &copy; 2010-${h.datetime.today().year}, <a href="${h.route_url('rhodecode_official')}" target="_blank">RhodeCode GmbH</a>. All rights reserved.
57 &copy; 2010-${h.datetime.today().year}, <a href="${h.route_url('rhodecode_official')}" target="_blank">RhodeCode GmbH</a>. All rights reserved.
58 % if c.visual.rhodecode_support_url:
58 % if c.visual.rhodecode_support_url:
59 <a href="${c.visual.rhodecode_support_url}" target="_blank">${_('Support')}</a>
59 <a href="${c.visual.rhodecode_support_url}" target="_blank">${_('Support')}</a>
60 % endif
60 % endif
61 </p>
61 </p>
62 <% sid = 'block' if request.GET.get('showrcid') else 'none' %>
62 <% sid = 'block' if request.GET.get('showrcid') else 'none' %>
63 <p class="server-instance" style="display:${sid}">
63 <p class="server-instance" style="display:${sid}">
64 ## display hidden instance ID if specially defined
64 ## display hidden instance ID if specially defined
65 % if c.rhodecode_instanceid:
65 % if c.rhodecode_instanceid:
66 ${_('RhodeCode instance id: {}').format(c.rhodecode_instanceid)}
66 ${_('RhodeCode instance id: {}').format(c.rhodecode_instanceid)}
67 % endif
67 % endif
68 </p>
68 </p>
69 </div>
69 </div>
70 </div>
70 </div>
71 </div>
71 </div>
72
72
73 <!-- END FOOTER -->
73 <!-- END FOOTER -->
74
74
75 ### MAKO DEFS ###
75 ### MAKO DEFS ###
76
76
77 <%def name="menu_bar_subnav()">
77 <%def name="menu_bar_subnav()">
78 </%def>
78 </%def>
79
79
80 <%def name="breadcrumbs(class_='breadcrumbs')">
80 <%def name="breadcrumbs(class_='breadcrumbs')">
81 <div class="${class_}">
81 <div class="${class_}">
82 ${self.breadcrumbs_links()}
82 ${self.breadcrumbs_links()}
83 </div>
83 </div>
84 </%def>
84 </%def>
85
85
86 <%def name="admin_menu(active=None)">
86 <%def name="admin_menu(active=None)">
87
87
88 <div id="context-bar">
88 <div id="context-bar">
89 <div class="wrapper">
89 <div class="wrapper">
90 <div class="title">
90 <div class="title">
91 <div class="title-content">
91 <div class="title-content">
92 <div class="title-main">
92 <div class="title-main">
93 % if c.is_super_admin:
93 % if c.is_super_admin:
94 ${_('Super-admin Panel')}
94 ${_('Super-admin Panel')}
95 % else:
95 % else:
96 ${_('Delegated Admin Panel')}
96 ${_('Delegated Admin Panel')}
97 % endif
97 % endif
98 </div>
98 </div>
99 </div>
99 </div>
100 </div>
100 </div>
101
101
102 <ul id="context-pages" class="navigation horizontal-list">
102 <ul id="context-pages" class="navigation horizontal-list">
103
103
104 ## super-admin case
104 ## super-admin case
105 % if c.is_super_admin:
105 % if c.is_super_admin:
106 <li class="${h.is_active('audit_logs', active)}"><a href="${h.route_path('admin_audit_logs')}">${_('Admin audit logs')}</a></li>
106 <li class="${h.is_active('audit_logs', active)}"><a href="${h.route_path('admin_audit_logs')}">${_('Admin audit logs')}</a></li>
107 <li class="${h.is_active('repositories', active)}"><a href="${h.route_path('repos')}">${_('Repositories')}</a></li>
107 <li class="${h.is_active('repositories', active)}"><a href="${h.route_path('repos')}">${_('Repositories')}</a></li>
108 <li class="${h.is_active('repository_groups', active)}"><a href="${h.route_path('repo_groups')}">${_('Repository groups')}</a></li>
108 <li class="${h.is_active('repository_groups', active)}"><a href="${h.route_path('repo_groups')}">${_('Repository groups')}</a></li>
109 <li class="${h.is_active('users', active)}"><a href="${h.route_path('users')}">${_('Users')}</a></li>
109 <li class="${h.is_active('users', active)}"><a href="${h.route_path('users')}">${_('Users')}</a></li>
110 <li class="${h.is_active('user_groups', active)}"><a href="${h.route_path('user_groups')}">${_('User groups')}</a></li>
110 <li class="${h.is_active('user_groups', active)}"><a href="${h.route_path('user_groups')}">${_('User groups')}</a></li>
111 <li class="${h.is_active('permissions', active)}"><a href="${h.route_path('admin_permissions_application')}">${_('Permissions')}</a></li>
111 <li class="${h.is_active('permissions', active)}"><a href="${h.route_path('admin_permissions_application')}">${_('Permissions')}</a></li>
112 <li class="${h.is_active('authentication', active)}"><a href="${h.route_path('auth_home', traverse='')}">${_('Authentication')}</a></li>
112 <li class="${h.is_active('authentication', active)}"><a href="${h.route_path('auth_home', traverse='')}">${_('Authentication')}</a></li>
113 <li class="${h.is_active('integrations', active)}"><a href="${h.route_path('global_integrations_home')}">${_('Integrations')}</a></li>
113 <li class="${h.is_active('integrations', active)}"><a href="${h.route_path('global_integrations_home')}">${_('Integrations')}</a></li>
114 <li class="${h.is_active('defaults', active)}"><a href="${h.route_path('admin_defaults_repositories')}">${_('Defaults')}</a></li>
114 <li class="${h.is_active('defaults', active)}"><a href="${h.route_path('admin_defaults_repositories')}">${_('Defaults')}</a></li>
115 <li class="${h.is_active('settings', active)}"><a href="${h.route_path('admin_settings')}">${_('Settings')}</a></li>
115 <li class="${h.is_active('settings', active)}"><a href="${h.route_path('admin_settings')}">${_('Settings')}</a></li>
116
116
117 ## delegated admin
117 ## delegated admin
118 % elif c.is_delegated_admin:
118 % elif c.is_delegated_admin:
119 <%
119 <%
120 repositories=c.auth_user.repositories_admin or c.can_create_repo
120 repositories=c.auth_user.repositories_admin or c.can_create_repo
121 repository_groups=c.auth_user.repository_groups_admin or c.can_create_repo_group
121 repository_groups=c.auth_user.repository_groups_admin or c.can_create_repo_group
122 user_groups=c.auth_user.user_groups_admin or c.can_create_user_group
122 user_groups=c.auth_user.user_groups_admin or c.can_create_user_group
123 %>
123 %>
124
124
125 %if repositories:
125 %if repositories:
126 <li class="${h.is_active('repositories', active)} local-admin-repos"><a href="${h.route_path('repos')}">${_('Repositories')}</a></li>
126 <li class="${h.is_active('repositories', active)} local-admin-repos"><a href="${h.route_path('repos')}">${_('Repositories')}</a></li>
127 %endif
127 %endif
128 %if repository_groups:
128 %if repository_groups:
129 <li class="${h.is_active('repository_groups', active)} local-admin-repo-groups"><a href="${h.route_path('repo_groups')}">${_('Repository groups')}</a></li>
129 <li class="${h.is_active('repository_groups', active)} local-admin-repo-groups"><a href="${h.route_path('repo_groups')}">${_('Repository groups')}</a></li>
130 %endif
130 %endif
131 %if user_groups:
131 %if user_groups:
132 <li class="${h.is_active('user_groups', active)} local-admin-user-groups"><a href="${h.route_path('user_groups')}">${_('User groups')}</a></li>
132 <li class="${h.is_active('user_groups', active)} local-admin-user-groups"><a href="${h.route_path('user_groups')}">${_('User groups')}</a></li>
133 %endif
133 %endif
134 % endif
134 % endif
135 </ul>
135 </ul>
136
136
137 </div>
137 </div>
138 <div class="clear"></div>
138 <div class="clear"></div>
139 </div>
139 </div>
140 </%def>
140 </%def>
141
141
142 <%def name="dt_info_panel(elements)">
142 <%def name="dt_info_panel(elements)">
143 <dl class="dl-horizontal">
143 <dl class="dl-horizontal">
144 %for dt, dd, title, show_items in elements:
144 %for dt, dd, title, show_items in elements:
145 <dt>${dt}:</dt>
145 <dt>${dt}:</dt>
146 <dd title="${h.tooltip(title)}">
146 <dd title="${h.tooltip(title)}">
147 %if callable(dd):
147 %if callable(dd):
148 ## allow lazy evaluation of elements
148 ## allow lazy evaluation of elements
149 ${dd()}
149 ${dd()}
150 %else:
150 %else:
151 ${dd}
151 ${dd}
152 %endif
152 %endif
153 %if show_items:
153 %if show_items:
154 <span class="btn-collapse" data-toggle="item-${h.md5_safe(dt)[:6]}-details">${_('Show More')} </span>
154 <span class="btn-collapse" data-toggle="item-${h.md5_safe(dt)[:6]}-details">${_('Show More')} </span>
155 %endif
155 %endif
156 </dd>
156 </dd>
157
157
158 %if show_items:
158 %if show_items:
159 <div class="collapsable-content" data-toggle="item-${h.md5_safe(dt)[:6]}-details" style="display: none">
159 <div class="collapsable-content" data-toggle="item-${h.md5_safe(dt)[:6]}-details" style="display: none">
160 %for item in show_items:
160 %for item in show_items:
161 <dt></dt>
161 <dt></dt>
162 <dd>${item}</dd>
162 <dd>${item}</dd>
163 %endfor
163 %endfor
164 </div>
164 </div>
165 %endif
165 %endif
166
166
167 %endfor
167 %endfor
168 </dl>
168 </dl>
169 </%def>
169 </%def>
170
170
171 <%def name="tr_info_entry(element)">
171 <%def name="tr_info_entry(element)">
172 <% key, val, title, show_items = element %>
172 <% key, val, title, show_items = element %>
173
173
174 <tr>
174 <tr>
175 <td style="vertical-align: top">${key}</td>
175 <td style="vertical-align: top">${key}</td>
176 <td title="${h.tooltip(title)}">
176 <td title="${h.tooltip(title)}">
177 %if callable(val):
177 %if callable(val):
178 ## allow lazy evaluation of elements
178 ## allow lazy evaluation of elements
179 ${val()}
179 ${val()}
180 %else:
180 %else:
181 ${val}
181 ${val}
182 %endif
182 %endif
183 %if show_items:
183 %if show_items:
184 <div class="collapsable-content" data-toggle="item-${h.md5_safe(val)[:6]}-details" style="display: none">
184 <div class="collapsable-content" data-toggle="item-${h.md5_safe(val)[:6]}-details" style="display: none">
185 % for item in show_items:
185 % for item in show_items:
186 <dt></dt>
186 <dt></dt>
187 <dd>${item}</dd>
187 <dd>${item}</dd>
188 % endfor
188 % endfor
189 </div>
189 </div>
190 %endif
190 %endif
191 </td>
191 </td>
192 <td style="vertical-align: top">
192 <td style="vertical-align: top">
193 %if show_items:
193 %if show_items:
194 <span class="btn-collapse" data-toggle="item-${h.md5_safe(val)[:6]}-details">${_('Show More')} </span>
194 <span class="btn-collapse" data-toggle="item-${h.md5_safe(val)[:6]}-details">${_('Show More')} </span>
195 %endif
195 %endif
196 </td>
196 </td>
197 </tr>
197 </tr>
198
198
199 </%def>
199 </%def>
200
200
201 <%def name="gravatar(email, size=16, tooltip=False, tooltip_alt=None, user=None, extra_class=None)">
201 <%def name="gravatar(email, size=16, tooltip=False, tooltip_alt=None, user=None, extra_class=None)">
202 <%
202 <%
203 if size > 16:
203 if size > 16:
204 gravatar_class = ['gravatar','gravatar-large']
204 gravatar_class = ['gravatar','gravatar-large']
205 else:
205 else:
206 gravatar_class = ['gravatar']
206 gravatar_class = ['gravatar']
207
207
208 data_hovercard_url = ''
208 data_hovercard_url = ''
209 data_hovercard_alt = tooltip_alt.replace('<', '&lt;').replace('>', '&gt;') if tooltip_alt else ''
209 data_hovercard_alt = tooltip_alt.replace('<', '&lt;').replace('>', '&gt;') if tooltip_alt else ''
210
210
211 if tooltip:
211 if tooltip:
212 gravatar_class += ['tooltip-hovercard']
212 gravatar_class += ['tooltip-hovercard']
213 if extra_class:
213 if extra_class:
214 gravatar_class += extra_class
214 gravatar_class += extra_class
215 if tooltip and user:
215 if tooltip and user:
216 if user.username == h.DEFAULT_USER:
216 if user.username == h.DEFAULT_USER:
217 gravatar_class.pop(-1)
217 gravatar_class.pop(-1)
218 else:
218 else:
219 data_hovercard_url = request.route_path('hovercard_user', user_id=getattr(user, 'user_id', ''))
219 data_hovercard_url = request.route_path('hovercard_user', user_id=getattr(user, 'user_id', ''))
220 gravatar_class = ' '.join(gravatar_class)
220 gravatar_class = ' '.join(gravatar_class)
221
221
222 %>
222 %>
223 <%doc>
223 <%doc>
224 TODO: johbo: For now we serve double size images to make it smooth
224 TODO: johbo: For now we serve double size images to make it smooth
225 for retina. This is how it worked until now. Should be replaced
225 for retina. This is how it worked until now. Should be replaced
226 with a better solution at some point.
226 with a better solution at some point.
227 </%doc>
227 </%doc>
228
228
229 <img class="${gravatar_class}" height="${size}" width="${size}" data-hovercard-url="${data_hovercard_url}" data-hovercard-alt="${data_hovercard_alt}" src="${h.gravatar_url(email, size * 2)}" />
229 <img class="${gravatar_class}" height="${size}" width="${size}" data-hovercard-url="${data_hovercard_url}" data-hovercard-alt="${data_hovercard_alt}" src="${h.gravatar_url(email, size * 2)}" />
230 </%def>
230 </%def>
231
231
232
232
233 <%def name="gravatar_with_user(contact, size=16, show_disabled=False, tooltip=False, _class='rc-user')">
233 <%def name="gravatar_with_user(contact, size=16, show_disabled=False, tooltip=False, _class='rc-user')">
234 <%
234 <%
235 email = h.email_or_none(contact)
235 email = h.email_or_none(contact)
236 rc_user = h.discover_user(contact)
236 rc_user = h.discover_user(contact)
237 %>
237 %>
238
238
239 <div class="${_class}">
239 <div class="${_class}">
240 ${self.gravatar(email, size, tooltip=tooltip, tooltip_alt=contact, user=rc_user)}
240 ${self.gravatar(email, size, tooltip=tooltip, tooltip_alt=contact, user=rc_user)}
241 <span class="${('user user-disabled' if show_disabled else 'user')}"> ${h.link_to_user(rc_user or contact)}</span>
241 <span class="${('user user-disabled' if show_disabled else 'user')}"> ${h.link_to_user(rc_user or contact)}</span>
242 </div>
242 </div>
243 </%def>
243 </%def>
244
244
245
245
246 <%def name="user_group_icon(user_group=None, size=16, tooltip=False)">
246 <%def name="user_group_icon(user_group=None, size=16, tooltip=False)">
247 <%
247 <%
248 if (size > 16):
248 if (size > 16):
249 gravatar_class = 'icon-user-group-alt'
249 gravatar_class = 'icon-user-group-alt'
250 else:
250 else:
251 gravatar_class = 'icon-user-group-alt'
251 gravatar_class = 'icon-user-group-alt'
252
252
253 if tooltip:
253 if tooltip:
254 gravatar_class += ' tooltip-hovercard'
254 gravatar_class += ' tooltip-hovercard'
255
255
256 data_hovercard_url = request.route_path('hovercard_user_group', user_group_id=user_group.users_group_id)
256 data_hovercard_url = request.route_path('hovercard_user_group', user_group_id=user_group.users_group_id)
257 %>
257 %>
258 <%doc>
258 <%doc>
259 TODO: johbo: For now we serve double size images to make it smooth
259 TODO: johbo: For now we serve double size images to make it smooth
260 for retina. This is how it worked until now. Should be replaced
260 for retina. This is how it worked until now. Should be replaced
261 with a better solution at some point.
261 with a better solution at some point.
262 </%doc>
262 </%doc>
263
263
264 <i style="font-size: ${size}px" class="${gravatar_class} x-icon-size-${size}" data-hovercard-url="${data_hovercard_url}"></i>
264 <i style="font-size: ${size}px" class="${gravatar_class} x-icon-size-${size}" data-hovercard-url="${data_hovercard_url}"></i>
265 </%def>
265 </%def>
266
266
267 <%def name="repo_page_title(repo_instance)">
267 <%def name="repo_page_title(repo_instance)">
268 <div class="title-content repo-title">
268 <div class="title-content repo-title">
269
269
270 <div class="title-main">
270 <div class="title-main">
271 ## SVN/HG/GIT icons
271 ## SVN/HG/GIT icons
272 %if h.is_hg(repo_instance):
272 %if h.is_hg(repo_instance):
273 <i class="icon-hg"></i>
273 <i class="icon-hg"></i>
274 %endif
274 %endif
275 %if h.is_git(repo_instance):
275 %if h.is_git(repo_instance):
276 <i class="icon-git"></i>
276 <i class="icon-git"></i>
277 %endif
277 %endif
278 %if h.is_svn(repo_instance):
278 %if h.is_svn(repo_instance):
279 <i class="icon-svn"></i>
279 <i class="icon-svn"></i>
280 %endif
280 %endif
281
281
282 ## public/private
282 ## public/private
283 %if repo_instance.private:
283 %if repo_instance.private:
284 <i class="icon-repo-private"></i>
284 <i class="icon-repo-private"></i>
285 %else:
285 %else:
286 <i class="icon-repo-public"></i>
286 <i class="icon-repo-public"></i>
287 %endif
287 %endif
288
288
289 ## repo name with group name
289 ## repo name with group name
290 ${h.breadcrumb_repo_link(repo_instance)}
290 ${h.breadcrumb_repo_link(repo_instance)}
291
291
292 ## Context Actions
292 ## Context Actions
293 <div class="pull-right">
293 <div class="pull-right">
294 %if c.rhodecode_user.username != h.DEFAULT_USER:
294 %if c.rhodecode_user.username != h.DEFAULT_USER:
295 <a href="${h.route_path('atom_feed_home', repo_name=c.rhodecode_db_repo.repo_uid, _query=dict(auth_token=c.rhodecode_user.feed_token))}" title="${_('RSS Feed')}" class="btn btn-sm"><i class="icon-rss-sign"></i>RSS</a>
295 <a href="${h.route_path('atom_feed_home', repo_name=c.rhodecode_db_repo.repo_uid, _query=dict(auth_token=c.rhodecode_user.feed_token))}" title="${_('RSS Feed')}" class="btn btn-sm"><i class="icon-rss-sign"></i>RSS</a>
296
296
297 <a href="#WatchRepo" onclick="toggleFollowingRepo(this, templateContext.repo_id); return false" title="${_('Watch this Repository and actions on it in your personalized journal')}" class="btn btn-sm ${('watching' if c.repository_is_user_following else '')}">
297 <a href="#WatchRepo" onclick="toggleFollowingRepo(this, templateContext.repo_id); return false" title="${_('Watch this Repository and actions on it in your personalized journal')}" class="btn btn-sm ${('watching' if c.repository_is_user_following else '')}">
298 % if c.repository_is_user_following:
298 % if c.repository_is_user_following:
299 <i class="icon-eye-off"></i>${_('Unwatch')}
299 <i class="icon-eye-off"></i>${_('Unwatch')}
300 % else:
300 % else:
301 <i class="icon-eye"></i>${_('Watch')}
301 <i class="icon-eye"></i>${_('Watch')}
302 % endif
302 % endif
303
303
304 </a>
304 </a>
305 %else:
305 %else:
306 <a href="${h.route_path('atom_feed_home', repo_name=c.rhodecode_db_repo.repo_uid)}" title="${_('RSS Feed')}" class="btn btn-sm"><i class="icon-rss-sign"></i>RSS</a>
306 <a href="${h.route_path('atom_feed_home', repo_name=c.rhodecode_db_repo.repo_uid)}" title="${_('RSS Feed')}" class="btn btn-sm"><i class="icon-rss-sign"></i>RSS</a>
307 %endif
307 %endif
308 </div>
308 </div>
309
309
310 </div>
310 </div>
311
311
312 ## FORKED
312 ## FORKED
313 %if repo_instance.fork:
313 %if repo_instance.fork:
314 <p class="discreet">
314 <p class="discreet">
315 <i class="icon-code-fork"></i> ${_('Fork of')}
315 <i class="icon-code-fork"></i> ${_('Fork of')}
316 ${h.link_to_if(c.has_origin_repo_read_perm,repo_instance.fork.repo_name, h.route_path('repo_summary', repo_name=repo_instance.fork.repo_name))}
316 ${h.link_to_if(c.has_origin_repo_read_perm,repo_instance.fork.repo_name, h.route_path('repo_summary', repo_name=repo_instance.fork.repo_name))}
317 </p>
317 </p>
318 %endif
318 %endif
319
319
320 ## IMPORTED FROM REMOTE
320 ## IMPORTED FROM REMOTE
321 %if repo_instance.clone_uri:
321 %if repo_instance.clone_uri:
322 <p class="discreet">
322 <p class="discreet">
323 <i class="icon-code-fork"></i> ${_('Clone from')}
323 <i class="icon-code-fork"></i> ${_('Clone from')}
324 <a href="${h.safe_str(h.hide_credentials(repo_instance.clone_uri))}">${h.hide_credentials(repo_instance.clone_uri)}</a>
324 <a href="${h.safe_str(h.hide_credentials(repo_instance.clone_uri))}">${h.hide_credentials(repo_instance.clone_uri)}</a>
325 </p>
325 </p>
326 %endif
326 %endif
327
327
328 ## LOCKING STATUS
328 ## LOCKING STATUS
329 %if repo_instance.locked[0]:
329 %if repo_instance.locked[0]:
330 <p class="locking_locked discreet">
330 <p class="locking_locked discreet">
331 <i class="icon-repo-lock"></i>
331 <i class="icon-repo-lock"></i>
332 ${_('Repository locked by %(user)s') % {'user': h.person_by_id(repo_instance.locked[0])}}
332 ${_('Repository locked by %(user)s') % {'user': h.person_by_id(repo_instance.locked[0])}}
333 </p>
333 </p>
334 %elif repo_instance.enable_locking:
334 %elif repo_instance.enable_locking:
335 <p class="locking_unlocked discreet">
335 <p class="locking_unlocked discreet">
336 <i class="icon-repo-unlock"></i>
336 <i class="icon-repo-unlock"></i>
337 ${_('Repository not locked. Pull repository to lock it.')}
337 ${_('Repository not locked. Pull repository to lock it.')}
338 </p>
338 </p>
339 %endif
339 %endif
340
340
341 </div>
341 </div>
342 </%def>
342 </%def>
343
343
344 <%def name="repo_menu(active=None)">
344 <%def name="repo_menu(active=None)">
345 <%
345 <%
346 ## determine if we have "any" option available
346 ## determine if we have "any" option available
347 can_lock = h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name) and c.rhodecode_db_repo.enable_locking
347 can_lock = h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name) and c.rhodecode_db_repo.enable_locking
348 has_actions = can_lock
348 has_actions = can_lock
349
349
350 %>
350 %>
351 % if c.rhodecode_db_repo.archived:
351 % if c.rhodecode_db_repo.archived:
352 <div class="alert alert-warning text-center">
352 <div class="alert alert-warning text-center">
353 <strong>${_('This repository has been archived. It is now read-only.')}</strong>
353 <strong>${_('This repository has been archived. It is now read-only.')}</strong>
354 </div>
354 </div>
355 % endif
355 % endif
356
356
357 <!--- REPO CONTEXT BAR -->
357 <!--- REPO CONTEXT BAR -->
358 <div id="context-bar">
358 <div id="context-bar">
359 <div class="wrapper">
359 <div class="wrapper">
360
360
361 <div class="title">
361 <div class="title">
362 ${self.repo_page_title(c.rhodecode_db_repo)}
362 ${self.repo_page_title(c.rhodecode_db_repo)}
363 </div>
363 </div>
364
364
365 <ul id="context-pages" class="navigation horizontal-list">
365 <ul id="context-pages" class="navigation horizontal-list">
366 <li class="${h.is_active('summary', active)}"><a class="menulink" href="${h.route_path('repo_summary', repo_name=c.repo_name)}"><div class="menulabel">${_('Summary')}</div></a></li>
366 <li class="${h.is_active('summary', active)}"><a class="menulink" href="${h.route_path('repo_summary', repo_name=c.repo_name)}"><div class="menulabel">${_('Summary')}</div></a></li>
367 <li class="${h.is_active('commits', active)}"><a class="menulink" href="${h.route_path('repo_commits', repo_name=c.repo_name)}"><div class="menulabel">${_('Commits')}</div></a></li>
367 <li class="${h.is_active('commits', active)}"><a class="menulink" href="${h.route_path('repo_commits', repo_name=c.repo_name)}"><div class="menulabel">${_('Commits')}</div></a></li>
368 <li class="${h.is_active('files', active)}"><a class="menulink" href="${h.route_path('repo_files', repo_name=c.repo_name, commit_id=c.rhodecode_db_repo.landing_ref_name, f_path='', _query={'at':c.rhodecode_db_repo.landing_ref_name})}"><div class="menulabel">${_('Files')}</div></a></li>
368 <li class="${h.is_active('files', active)}"><a class="menulink" href="${h.repo_files_by_ref_url(c.repo_name, c.rhodecode_db_repo.repo_type, f_path='', ref_name=c.rhodecode_db_repo.landing_ref_name, commit_id='tip', query={'at':c.rhodecode_db_repo.landing_ref_name})}"><div class="menulabel">${_('Files')}</div></a></li>
369 <li class="${h.is_active('compare', active)}"><a class="menulink" href="${h.route_path('repo_compare_select',repo_name=c.repo_name)}"><div class="menulabel">${_('Compare')}</div></a></li>
369 <li class="${h.is_active('compare', active)}"><a class="menulink" href="${h.route_path('repo_compare_select',repo_name=c.repo_name)}"><div class="menulabel">${_('Compare')}</div></a></li>
370
370
371 ## TODO: anderson: ideally it would have a function on the scm_instance "enable_pullrequest() and enable_fork()"
371 ## TODO: anderson: ideally it would have a function on the scm_instance "enable_pullrequest() and enable_fork()"
372 %if c.rhodecode_db_repo.repo_type in ['git','hg']:
372 %if c.rhodecode_db_repo.repo_type in ['git','hg']:
373 <li class="${h.is_active('showpullrequest', active)}">
373 <li class="${h.is_active('showpullrequest', active)}">
374 <a class="menulink" href="${h.route_path('pullrequest_show_all', repo_name=c.repo_name)}" title="${h.tooltip(_('Show Pull Requests for %s') % c.repo_name)}">
374 <a class="menulink" href="${h.route_path('pullrequest_show_all', repo_name=c.repo_name)}" title="${h.tooltip(_('Show Pull Requests for %s') % c.repo_name)}">
375 <div class="menulabel">
375 <div class="menulabel">
376 ${_('Pull Requests')} <span class="menulink-counter">${c.repository_pull_requests}</span>
376 ${_('Pull Requests')} <span class="menulink-counter">${c.repository_pull_requests}</span>
377 </div>
377 </div>
378 </a>
378 </a>
379 </li>
379 </li>
380 %endif
380 %endif
381
381
382 <li class="${h.is_active('artifacts', active)}">
382 <li class="${h.is_active('artifacts', active)}">
383 <a class="menulink" href="${h.route_path('repo_artifacts_list',repo_name=c.repo_name)}">
383 <a class="menulink" href="${h.route_path('repo_artifacts_list',repo_name=c.repo_name)}">
384 <div class="menulabel">
384 <div class="menulabel">
385 ${_('Artifacts')} <span class="menulink-counter">${c.repository_artifacts}</span>
385 ${_('Artifacts')} <span class="menulink-counter">${c.repository_artifacts}</span>
386 </div>
386 </div>
387 </a>
387 </a>
388 </li>
388 </li>
389
389
390 %if h.HasRepoPermissionAll('repository.admin')(c.repo_name):
390 %if h.HasRepoPermissionAll('repository.admin')(c.repo_name):
391 <li class="${h.is_active('settings', active)}"><a class="menulink" href="${h.route_path('edit_repo',repo_name=c.repo_name)}"><div class="menulabel">${_('Repository Settings')}</div></a></li>
391 <li class="${h.is_active('settings', active)}"><a class="menulink" href="${h.route_path('edit_repo',repo_name=c.repo_name)}"><div class="menulabel">${_('Repository Settings')}</div></a></li>
392 %endif
392 %endif
393
393
394 <li class="${h.is_active('options', active)}">
394 <li class="${h.is_active('options', active)}">
395 % if has_actions:
395 % if has_actions:
396 <a class="menulink dropdown">
396 <a class="menulink dropdown">
397 <div class="menulabel">${_('Options')}<div class="show_more"></div></div>
397 <div class="menulabel">${_('Options')}<div class="show_more"></div></div>
398 </a>
398 </a>
399 <ul class="submenu">
399 <ul class="submenu">
400 %if can_lock:
400 %if can_lock:
401 %if c.rhodecode_db_repo.locked[0]:
401 %if c.rhodecode_db_repo.locked[0]:
402 <li><a class="locking_del" href="${h.route_path('repo_edit_toggle_locking',repo_name=c.repo_name)}">${_('Unlock Repository')}</a></li>
402 <li><a class="locking_del" href="${h.route_path('repo_edit_toggle_locking',repo_name=c.repo_name)}">${_('Unlock Repository')}</a></li>
403 %else:
403 %else:
404 <li><a class="locking_add" href="${h.route_path('repo_edit_toggle_locking',repo_name=c.repo_name)}">${_('Lock Repository')}</a></li>
404 <li><a class="locking_add" href="${h.route_path('repo_edit_toggle_locking',repo_name=c.repo_name)}">${_('Lock Repository')}</a></li>
405 %endif
405 %endif
406 %endif
406 %endif
407 </ul>
407 </ul>
408 % endif
408 % endif
409 </li>
409 </li>
410
410
411 </ul>
411 </ul>
412 </div>
412 </div>
413 <div class="clear"></div>
413 <div class="clear"></div>
414 </div>
414 </div>
415
415
416 <!--- REPO END CONTEXT BAR -->
416 <!--- REPO END CONTEXT BAR -->
417
417
418 </%def>
418 </%def>
419
419
420 <%def name="repo_group_page_title(repo_group_instance)">
420 <%def name="repo_group_page_title(repo_group_instance)">
421 <div class="title-content">
421 <div class="title-content">
422 <div class="title-main">
422 <div class="title-main">
423 ## Repository Group icon
423 ## Repository Group icon
424 <i class="icon-repo-group"></i>
424 <i class="icon-repo-group"></i>
425
425
426 ## repo name with group name
426 ## repo name with group name
427 ${h.breadcrumb_repo_group_link(repo_group_instance)}
427 ${h.breadcrumb_repo_group_link(repo_group_instance)}
428 </div>
428 </div>
429
429
430 <%namespace name="dt" file="/data_table/_dt_elements.mako"/>
430 <%namespace name="dt" file="/data_table/_dt_elements.mako"/>
431 <div class="repo-group-desc discreet">
431 <div class="repo-group-desc discreet">
432 ${dt.repo_group_desc(repo_group_instance.description_safe, repo_group_instance.personal, c.visual.stylify_metatags)}
432 ${dt.repo_group_desc(repo_group_instance.description_safe, repo_group_instance.personal, c.visual.stylify_metatags)}
433 </div>
433 </div>
434
434
435 </div>
435 </div>
436 </%def>
436 </%def>
437
437
438
438
439 <%def name="repo_group_menu(active=None)">
439 <%def name="repo_group_menu(active=None)">
440 <%
440 <%
441 gr_name = c.repo_group.group_name if c.repo_group else None
441 gr_name = c.repo_group.group_name if c.repo_group else None
442 # create repositories with write permission on group is set to true
442 # create repositories with write permission on group is set to true
443 group_admin = h.HasRepoGroupPermissionAny('group.admin')(gr_name, 'group admin index page')
443 group_admin = h.HasRepoGroupPermissionAny('group.admin')(gr_name, 'group admin index page')
444
444
445 %>
445 %>
446
446
447
447
448 <!--- REPO GROUP CONTEXT BAR -->
448 <!--- REPO GROUP CONTEXT BAR -->
449 <div id="context-bar">
449 <div id="context-bar">
450 <div class="wrapper">
450 <div class="wrapper">
451 <div class="title">
451 <div class="title">
452 ${self.repo_group_page_title(c.repo_group)}
452 ${self.repo_group_page_title(c.repo_group)}
453 </div>
453 </div>
454
454
455 <ul id="context-pages" class="navigation horizontal-list">
455 <ul id="context-pages" class="navigation horizontal-list">
456 <li class="${h.is_active('home', active)}">
456 <li class="${h.is_active('home', active)}">
457 <a class="menulink" href="${h.route_path('repo_group_home', repo_group_name=c.repo_group.group_name)}"><div class="menulabel">${_('Group Home')}</div></a>
457 <a class="menulink" href="${h.route_path('repo_group_home', repo_group_name=c.repo_group.group_name)}"><div class="menulabel">${_('Group Home')}</div></a>
458 </li>
458 </li>
459 % if c.is_super_admin or group_admin:
459 % if c.is_super_admin or group_admin:
460 <li class="${h.is_active('settings', active)}">
460 <li class="${h.is_active('settings', active)}">
461 <a class="menulink" href="${h.route_path('edit_repo_group',repo_group_name=c.repo_group.group_name)}" title="${_('You have admin right to this group, and can edit it')}"><div class="menulabel">${_('Group Settings')}</div></a>
461 <a class="menulink" href="${h.route_path('edit_repo_group',repo_group_name=c.repo_group.group_name)}" title="${_('You have admin right to this group, and can edit it')}"><div class="menulabel">${_('Group Settings')}</div></a>
462 </li>
462 </li>
463 % endif
463 % endif
464
464
465 </ul>
465 </ul>
466 </div>
466 </div>
467 <div class="clear"></div>
467 <div class="clear"></div>
468 </div>
468 </div>
469
469
470 <!--- REPO GROUP CONTEXT BAR -->
470 <!--- REPO GROUP CONTEXT BAR -->
471
471
472 </%def>
472 </%def>
473
473
474
474
475 <%def name="usermenu(active=False)">
475 <%def name="usermenu(active=False)">
476 <%
476 <%
477 not_anonymous = c.rhodecode_user.username != h.DEFAULT_USER
477 not_anonymous = c.rhodecode_user.username != h.DEFAULT_USER
478
478
479 gr_name = c.repo_group.group_name if (hasattr(c, 'repo_group') and c.repo_group) else None
479 gr_name = c.repo_group.group_name if (hasattr(c, 'repo_group') and c.repo_group) else None
480 # create repositories with write permission on group is set to true
480 # create repositories with write permission on group is set to true
481
481
482 can_fork = c.is_super_admin or h.HasPermissionAny('hg.fork.repository')()
482 can_fork = c.is_super_admin or h.HasPermissionAny('hg.fork.repository')()
483 create_on_write = h.HasPermissionAny('hg.create.write_on_repogroup.true')()
483 create_on_write = h.HasPermissionAny('hg.create.write_on_repogroup.true')()
484 group_write = h.HasRepoGroupPermissionAny('group.write')(gr_name, 'can write into group index page')
484 group_write = h.HasRepoGroupPermissionAny('group.write')(gr_name, 'can write into group index page')
485 group_admin = h.HasRepoGroupPermissionAny('group.admin')(gr_name, 'group admin index page')
485 group_admin = h.HasRepoGroupPermissionAny('group.admin')(gr_name, 'group admin index page')
486
486
487 can_create_repos = c.is_super_admin or c.can_create_repo
487 can_create_repos = c.is_super_admin or c.can_create_repo
488 can_create_repo_groups = c.is_super_admin or c.can_create_repo_group
488 can_create_repo_groups = c.is_super_admin or c.can_create_repo_group
489
489
490 can_create_repos_in_group = c.is_super_admin or group_admin or (group_write and create_on_write)
490 can_create_repos_in_group = c.is_super_admin or group_admin or (group_write and create_on_write)
491 can_create_repo_groups_in_group = c.is_super_admin or group_admin
491 can_create_repo_groups_in_group = c.is_super_admin or group_admin
492 %>
492 %>
493
493
494 % if not_anonymous:
494 % if not_anonymous:
495 <%
495 <%
496 default_target_group = dict()
496 default_target_group = dict()
497 if c.rhodecode_user.personal_repo_group:
497 if c.rhodecode_user.personal_repo_group:
498 default_target_group = dict(parent_group=c.rhodecode_user.personal_repo_group.group_id)
498 default_target_group = dict(parent_group=c.rhodecode_user.personal_repo_group.group_id)
499 %>
499 %>
500
500
501 ## create action
501 ## create action
502 <li>
502 <li>
503 <a href="#create-actions" onclick="return false;" class="menulink childs">
503 <a href="#create-actions" onclick="return false;" class="menulink childs">
504 <i class="tooltip icon-plus-circled" title="${_('Create')}"></i>
504 <i class="tooltip icon-plus-circled" title="${_('Create')}"></i>
505 </a>
505 </a>
506
506
507 <div class="action-menu submenu">
507 <div class="action-menu submenu">
508
508
509 <ol>
509 <ol>
510 ## scope of within a repository
510 ## scope of within a repository
511 % if hasattr(c, 'rhodecode_db_repo') and c.rhodecode_db_repo:
511 % if hasattr(c, 'rhodecode_db_repo') and c.rhodecode_db_repo:
512 <li class="submenu-title">${_('This Repository')}</li>
512 <li class="submenu-title">${_('This Repository')}</li>
513 <li>
513 <li>
514 <a href="${h.route_path('pullrequest_new',repo_name=c.repo_name)}">${_('Create Pull Request')}</a>
514 <a href="${h.route_path('pullrequest_new',repo_name=c.repo_name)}">${_('Create Pull Request')}</a>
515 </li>
515 </li>
516 % if can_fork:
516 % if can_fork:
517 <li>
517 <li>
518 <a href="${h.route_path('repo_fork_new',repo_name=c.repo_name,_query=default_target_group)}">${_('Fork this repository')}</a>
518 <a href="${h.route_path('repo_fork_new',repo_name=c.repo_name,_query=default_target_group)}">${_('Fork this repository')}</a>
519 </li>
519 </li>
520 % endif
520 % endif
521 % endif
521 % endif
522
522
523 ## scope of within repository groups
523 ## scope of within repository groups
524 % if hasattr(c, 'repo_group') and c.repo_group and (can_create_repos_in_group or can_create_repo_groups_in_group):
524 % if hasattr(c, 'repo_group') and c.repo_group and (can_create_repos_in_group or can_create_repo_groups_in_group):
525 <li class="submenu-title">${_('This Repository Group')}</li>
525 <li class="submenu-title">${_('This Repository Group')}</li>
526
526
527 % if can_create_repos_in_group:
527 % if can_create_repos_in_group:
528 <li>
528 <li>
529 <a href="${h.route_path('repo_new',_query=dict(parent_group=c.repo_group.group_id))}">${_('New Repository')}</a>
529 <a href="${h.route_path('repo_new',_query=dict(parent_group=c.repo_group.group_id))}">${_('New Repository')}</a>
530 </li>
530 </li>
531 % endif
531 % endif
532
532
533 % if can_create_repo_groups_in_group:
533 % if can_create_repo_groups_in_group:
534 <li>
534 <li>
535 <a href="${h.route_path('repo_group_new',_query=dict(parent_group=c.repo_group.group_id))}">${_(u'New Repository Group')}</a>
535 <a href="${h.route_path('repo_group_new',_query=dict(parent_group=c.repo_group.group_id))}">${_(u'New Repository Group')}</a>
536 </li>
536 </li>
537 % endif
537 % endif
538 % endif
538 % endif
539
539
540 ## personal group
540 ## personal group
541 % if c.rhodecode_user.personal_repo_group:
541 % if c.rhodecode_user.personal_repo_group:
542 <li class="submenu-title">Personal Group</li>
542 <li class="submenu-title">Personal Group</li>
543
543
544 <li>
544 <li>
545 <a href="${h.route_path('repo_new',_query=dict(parent_group=c.rhodecode_user.personal_repo_group.group_id))}" >${_('New Repository')} </a>
545 <a href="${h.route_path('repo_new',_query=dict(parent_group=c.rhodecode_user.personal_repo_group.group_id))}" >${_('New Repository')} </a>
546 </li>
546 </li>
547
547
548 <li>
548 <li>
549 <a href="${h.route_path('repo_group_new',_query=dict(parent_group=c.rhodecode_user.personal_repo_group.group_id))}">${_('New Repository Group')} </a>
549 <a href="${h.route_path('repo_group_new',_query=dict(parent_group=c.rhodecode_user.personal_repo_group.group_id))}">${_('New Repository Group')} </a>
550 </li>
550 </li>
551 % endif
551 % endif
552
552
553 ## Global actions
553 ## Global actions
554 <li class="submenu-title">RhodeCode</li>
554 <li class="submenu-title">RhodeCode</li>
555 % if can_create_repos:
555 % if can_create_repos:
556 <li>
556 <li>
557 <a href="${h.route_path('repo_new')}" >${_('New Repository')}</a>
557 <a href="${h.route_path('repo_new')}" >${_('New Repository')}</a>
558 </li>
558 </li>
559 % endif
559 % endif
560
560
561 % if can_create_repo_groups:
561 % if can_create_repo_groups:
562 <li>
562 <li>
563 <a href="${h.route_path('repo_group_new')}" >${_(u'New Repository Group')}</a>
563 <a href="${h.route_path('repo_group_new')}" >${_(u'New Repository Group')}</a>
564 </li>
564 </li>
565 % endif
565 % endif
566
566
567 <li>
567 <li>
568 <a href="${h.route_path('gists_new')}">${_(u'New Gist')}</a>
568 <a href="${h.route_path('gists_new')}">${_(u'New Gist')}</a>
569 </li>
569 </li>
570
570
571 </ol>
571 </ol>
572
572
573 </div>
573 </div>
574 </li>
574 </li>
575
575
576 ## notifications
576 ## notifications
577 <li>
577 <li>
578 <a class="${('empty' if c.unread_notifications == 0 else '')}" href="${h.route_path('notifications_show_all')}">
578 <a class="${('empty' if c.unread_notifications == 0 else '')}" href="${h.route_path('notifications_show_all')}">
579 ${c.unread_notifications}
579 ${c.unread_notifications}
580 </a>
580 </a>
581 </li>
581 </li>
582 % endif
582 % endif
583
583
584 ## USER MENU
584 ## USER MENU
585 <li id="quick_login_li" class="${'active' if active else ''}">
585 <li id="quick_login_li" class="${'active' if active else ''}">
586 % if c.rhodecode_user.username == h.DEFAULT_USER:
586 % if c.rhodecode_user.username == h.DEFAULT_USER:
587 <a id="quick_login_link" class="menulink childs" href="${h.route_path('login', _query={'came_from': h.current_route_path(request)})}">
587 <a id="quick_login_link" class="menulink childs" href="${h.route_path('login', _query={'came_from': h.current_route_path(request)})}">
588 ${gravatar(c.rhodecode_user.email, 20)}
588 ${gravatar(c.rhodecode_user.email, 20)}
589 <span class="user">
589 <span class="user">
590 <span>${_('Sign in')}</span>
590 <span>${_('Sign in')}</span>
591 </span>
591 </span>
592 </a>
592 </a>
593 % else:
593 % else:
594 ## logged in user
594 ## logged in user
595 <a id="quick_login_link" class="menulink childs">
595 <a id="quick_login_link" class="menulink childs">
596 ${gravatar(c.rhodecode_user.email, 20)}
596 ${gravatar(c.rhodecode_user.email, 20)}
597 <span class="user">
597 <span class="user">
598 <span class="menu_link_user">${c.rhodecode_user.username}</span>
598 <span class="menu_link_user">${c.rhodecode_user.username}</span>
599 <div class="show_more"></div>
599 <div class="show_more"></div>
600 </span>
600 </span>
601 </a>
601 </a>
602 ## subnav with menu for logged in user
602 ## subnav with menu for logged in user
603 <div class="user-menu submenu">
603 <div class="user-menu submenu">
604 <div id="quick_login">
604 <div id="quick_login">
605 %if c.rhodecode_user.username != h.DEFAULT_USER:
605 %if c.rhodecode_user.username != h.DEFAULT_USER:
606 <div class="">
606 <div class="">
607 <div class="big_gravatar">${gravatar(c.rhodecode_user.email, 48)}</div>
607 <div class="big_gravatar">${gravatar(c.rhodecode_user.email, 48)}</div>
608 <div class="full_name">${c.rhodecode_user.full_name_or_username}</div>
608 <div class="full_name">${c.rhodecode_user.full_name_or_username}</div>
609 <div class="email">${c.rhodecode_user.email}</div>
609 <div class="email">${c.rhodecode_user.email}</div>
610 </div>
610 </div>
611 <div class="">
611 <div class="">
612 <ol class="links">
612 <ol class="links">
613 <li>${h.link_to(_(u'My account'),h.route_path('my_account_profile'))}</li>
613 <li>${h.link_to(_(u'My account'),h.route_path('my_account_profile'))}</li>
614 % if c.rhodecode_user.personal_repo_group:
614 % if c.rhodecode_user.personal_repo_group:
615 <li>${h.link_to(_(u'My personal group'), h.route_path('repo_group_home', repo_group_name=c.rhodecode_user.personal_repo_group.group_name))}</li>
615 <li>${h.link_to(_(u'My personal group'), h.route_path('repo_group_home', repo_group_name=c.rhodecode_user.personal_repo_group.group_name))}</li>
616 % endif
616 % endif
617 <li>${h.link_to(_(u'Pull Requests'), h.route_path('my_account_pullrequests'))}</li>
617 <li>${h.link_to(_(u'Pull Requests'), h.route_path('my_account_pullrequests'))}</li>
618
618
619 % if c.debug_style:
619 % if c.debug_style:
620 <li>
620 <li>
621 <a class="menulink" title="${_('Style')}" href="${h.route_path('debug_style_home')}">
621 <a class="menulink" title="${_('Style')}" href="${h.route_path('debug_style_home')}">
622 <div class="menulabel">${_('[Style]')}</div>
622 <div class="menulabel">${_('[Style]')}</div>
623 </a>
623 </a>
624 </li>
624 </li>
625 % endif
625 % endif
626
626
627 ## bookmark-items
627 ## bookmark-items
628 <li class="bookmark-items">
628 <li class="bookmark-items">
629 ${_('Bookmarks')}
629 ${_('Bookmarks')}
630 <div class="pull-right">
630 <div class="pull-right">
631 <a href="${h.route_path('my_account_bookmarks')}">
631 <a href="${h.route_path('my_account_bookmarks')}">
632
632
633 <i class="icon-cog"></i>
633 <i class="icon-cog"></i>
634 </a>
634 </a>
635 </div>
635 </div>
636 </li>
636 </li>
637 % if not c.bookmark_items:
637 % if not c.bookmark_items:
638 <li>
638 <li>
639 <a href="${h.route_path('my_account_bookmarks')}">${_('No Bookmarks yet.')}</a>
639 <a href="${h.route_path('my_account_bookmarks')}">${_('No Bookmarks yet.')}</a>
640 </li>
640 </li>
641 % endif
641 % endif
642 % for item in c.bookmark_items:
642 % for item in c.bookmark_items:
643 <li>
643 <li>
644 % if item.repository:
644 % if item.repository:
645 <div>
645 <div>
646 <a class="bookmark-item" href="${h.route_path('my_account_goto_bookmark', bookmark_id=item.position)}">
646 <a class="bookmark-item" href="${h.route_path('my_account_goto_bookmark', bookmark_id=item.position)}">
647 <code>${item.position}</code>
647 <code>${item.position}</code>
648 % if item.repository.repo_type == 'hg':
648 % if item.repository.repo_type == 'hg':
649 <i class="icon-hg" title="${_('Repository')}" style="font-size: 16px"></i>
649 <i class="icon-hg" title="${_('Repository')}" style="font-size: 16px"></i>
650 % elif item.repository.repo_type == 'git':
650 % elif item.repository.repo_type == 'git':
651 <i class="icon-git" title="${_('Repository')}" style="font-size: 16px"></i>
651 <i class="icon-git" title="${_('Repository')}" style="font-size: 16px"></i>
652 % elif item.repository.repo_type == 'svn':
652 % elif item.repository.repo_type == 'svn':
653 <i class="icon-svn" title="${_('Repository')}" style="font-size: 16px"></i>
653 <i class="icon-svn" title="${_('Repository')}" style="font-size: 16px"></i>
654 % endif
654 % endif
655 ${(item.title or h.shorter(item.repository.repo_name, 30))}
655 ${(item.title or h.shorter(item.repository.repo_name, 30))}
656 </a>
656 </a>
657 </div>
657 </div>
658 % elif item.repository_group:
658 % elif item.repository_group:
659 <div>
659 <div>
660 <a class="bookmark-item" href="${h.route_path('my_account_goto_bookmark', bookmark_id=item.position)}">
660 <a class="bookmark-item" href="${h.route_path('my_account_goto_bookmark', bookmark_id=item.position)}">
661 <code>${item.position}</code>
661 <code>${item.position}</code>
662 <i class="icon-repo-group" title="${_('Repository group')}" style="font-size: 14px"></i>
662 <i class="icon-repo-group" title="${_('Repository group')}" style="font-size: 14px"></i>
663 ${(item.title or h.shorter(item.repository_group.group_name, 30))}
663 ${(item.title or h.shorter(item.repository_group.group_name, 30))}
664 </a>
664 </a>
665 </div>
665 </div>
666 % else:
666 % else:
667 <a class="bookmark-item" href="${h.route_path('my_account_goto_bookmark', bookmark_id=item.position)}">
667 <a class="bookmark-item" href="${h.route_path('my_account_goto_bookmark', bookmark_id=item.position)}">
668 <code>${item.position}</code>
668 <code>${item.position}</code>
669 ${item.title}
669 ${item.title}
670 </a>
670 </a>
671 % endif
671 % endif
672 </li>
672 </li>
673 % endfor
673 % endfor
674
674
675 <li class="logout">
675 <li class="logout">
676 ${h.secure_form(h.route_path('logout'), request=request)}
676 ${h.secure_form(h.route_path('logout'), request=request)}
677 ${h.submit('log_out', _(u'Sign Out'),class_="btn btn-primary")}
677 ${h.submit('log_out', _(u'Sign Out'),class_="btn btn-primary")}
678 ${h.end_form()}
678 ${h.end_form()}
679 </li>
679 </li>
680 </ol>
680 </ol>
681 </div>
681 </div>
682 %endif
682 %endif
683 </div>
683 </div>
684 </div>
684 </div>
685
685
686 % endif
686 % endif
687 </li>
687 </li>
688 </%def>
688 </%def>
689
689
690 <%def name="menu_items(active=None)">
690 <%def name="menu_items(active=None)">
691 <%
691 <%
692 notice_messages, notice_level = c.rhodecode_user.get_notice_messages()
692 notice_messages, notice_level = c.rhodecode_user.get_notice_messages()
693 notice_display = 'none' if len(notice_messages) == 0 else ''
693 notice_display = 'none' if len(notice_messages) == 0 else ''
694 %>
694 %>
695 <style>
695 <style>
696
696
697 </style>
697 </style>
698
698
699 <ul id="quick" class="main_nav navigation horizontal-list">
699 <ul id="quick" class="main_nav navigation horizontal-list">
700 ## notice box for important system messages
700 ## notice box for important system messages
701 <li style="display: ${notice_display}">
701 <li style="display: ${notice_display}">
702 <a class="notice-box" href="#openNotice" onclick="$('.notice-messages-container').toggle(); return false">
702 <a class="notice-box" href="#openNotice" onclick="$('.notice-messages-container').toggle(); return false">
703 <div class="menulabel-notice ${notice_level}" >
703 <div class="menulabel-notice ${notice_level}" >
704 ${len(notice_messages)}
704 ${len(notice_messages)}
705 </div>
705 </div>
706 </a>
706 </a>
707 </li>
707 </li>
708 <div class="notice-messages-container" style="display: none">
708 <div class="notice-messages-container" style="display: none">
709 <div class="notice-messages">
709 <div class="notice-messages">
710 <table class="rctable">
710 <table class="rctable">
711 % for notice in notice_messages:
711 % for notice in notice_messages:
712 <tr id="notice-message-${notice['msg_id']}" class="notice-message-${notice['level']}">
712 <tr id="notice-message-${notice['msg_id']}" class="notice-message-${notice['level']}">
713 <td style="vertical-align: text-top; width: 20px">
713 <td style="vertical-align: text-top; width: 20px">
714 <i class="tooltip icon-info notice-color-${notice['level']}" title="${notice['level']}"></i>
714 <i class="tooltip icon-info notice-color-${notice['level']}" title="${notice['level']}"></i>
715 </td>
715 </td>
716 <td>
716 <td>
717 <span><i class="icon-plus-squared cursor-pointer" onclick="$('#notice-${notice['msg_id']}').toggle()"></i> </span>
717 <span><i class="icon-plus-squared cursor-pointer" onclick="$('#notice-${notice['msg_id']}').toggle()"></i> </span>
718 ${notice['subject']}
718 ${notice['subject']}
719
719
720 <div id="notice-${notice['msg_id']}" style="display: none">
720 <div id="notice-${notice['msg_id']}" style="display: none">
721 ${h.render(notice['body'], renderer='markdown')}
721 ${h.render(notice['body'], renderer='markdown')}
722 </div>
722 </div>
723 </td>
723 </td>
724 <td style="vertical-align: text-top; width: 35px;">
724 <td style="vertical-align: text-top; width: 35px;">
725 <a class="tooltip" title="${_('dismiss')}" href="#dismiss" onclick="dismissNotice(${notice['msg_id']});return false">
725 <a class="tooltip" title="${_('dismiss')}" href="#dismiss" onclick="dismissNotice(${notice['msg_id']});return false">
726 <i class="icon-remove icon-filled-red"></i>
726 <i class="icon-remove icon-filled-red"></i>
727 </a>
727 </a>
728 </td>
728 </td>
729 </tr>
729 </tr>
730
730
731 % endfor
731 % endfor
732 </table>
732 </table>
733 </div>
733 </div>
734 </div>
734 </div>
735 ## Main filter
735 ## Main filter
736 <li>
736 <li>
737 <div class="menulabel main_filter_box">
737 <div class="menulabel main_filter_box">
738 <div class="main_filter_input_box">
738 <div class="main_filter_input_box">
739 <ul class="searchItems">
739 <ul class="searchItems">
740
740
741 <li class="searchTag searchTagIcon">
741 <li class="searchTag searchTagIcon">
742 <i class="icon-search"></i>
742 <i class="icon-search"></i>
743 </li>
743 </li>
744
744
745 % if c.template_context['search_context']['repo_id']:
745 % if c.template_context['search_context']['repo_id']:
746 <li class="searchTag searchTagFilter searchTagHidable" >
746 <li class="searchTag searchTagFilter searchTagHidable" >
747 ##<a href="${h.route_path('search_repo',repo_name=c.template_context['search_context']['repo_name'])}">
747 ##<a href="${h.route_path('search_repo',repo_name=c.template_context['search_context']['repo_name'])}">
748 <span class="tag">
748 <span class="tag">
749 This repo
749 This repo
750 <a href="#removeGoToFilter" onclick="removeGoToFilter(); return false"><i class="icon-cancel-circled"></i></a>
750 <a href="#removeGoToFilter" onclick="removeGoToFilter(); return false"><i class="icon-cancel-circled"></i></a>
751 </span>
751 </span>
752 ##</a>
752 ##</a>
753 </li>
753 </li>
754 % elif c.template_context['search_context']['repo_group_id']:
754 % elif c.template_context['search_context']['repo_group_id']:
755 <li class="searchTag searchTagFilter searchTagHidable">
755 <li class="searchTag searchTagFilter searchTagHidable">
756 ##<a href="${h.route_path('search_repo_group',repo_group_name=c.template_context['search_context']['repo_group_name'])}">
756 ##<a href="${h.route_path('search_repo_group',repo_group_name=c.template_context['search_context']['repo_group_name'])}">
757 <span class="tag">
757 <span class="tag">
758 This group
758 This group
759 <a href="#removeGoToFilter" onclick="removeGoToFilter(); return false"><i class="icon-cancel-circled"></i></a>
759 <a href="#removeGoToFilter" onclick="removeGoToFilter(); return false"><i class="icon-cancel-circled"></i></a>
760 </span>
760 </span>
761 ##</a>
761 ##</a>
762 </li>
762 </li>
763 % endif
763 % endif
764
764
765 <li class="searchTagInput">
765 <li class="searchTagInput">
766 <input class="main_filter_input" id="main_filter" size="25" type="text" name="main_filter" placeholder="${_('search / go to...')}" value="" />
766 <input class="main_filter_input" id="main_filter" size="25" type="text" name="main_filter" placeholder="${_('search / go to...')}" value="" />
767 </li>
767 </li>
768 <li class="searchTag searchTagHelp">
768 <li class="searchTag searchTagHelp">
769 <a href="#showFilterHelp" onclick="showMainFilterBox(); return false">?</a>
769 <a href="#showFilterHelp" onclick="showMainFilterBox(); return false">?</a>
770 </li>
770 </li>
771 </ul>
771 </ul>
772 </div>
772 </div>
773 </div>
773 </div>
774
774
775 <div id="main_filter_help" style="display: none">
775 <div id="main_filter_help" style="display: none">
776 - Use '/' key to quickly access this field.
776 - Use '/' key to quickly access this field.
777
777
778 - Enter a name of repository, or repository group for quick search.
778 - Enter a name of repository, or repository group for quick search.
779
779
780 - Prefix query to allow special search:
780 - Prefix query to allow special search:
781
781
782 user:admin, to search for usernames, always global
782 user:admin, to search for usernames, always global
783
783
784 user_group:devops, to search for user groups, always global
784 user_group:devops, to search for user groups, always global
785
785
786 pr:303, to search for pull request number, title, or description, always global
786 pr:303, to search for pull request number, title, or description, always global
787
787
788 commit:efced4, to search for commits, scoped to repositories or groups
788 commit:efced4, to search for commits, scoped to repositories or groups
789
789
790 file:models.py, to search for file paths, scoped to repositories or groups
790 file:models.py, to search for file paths, scoped to repositories or groups
791
791
792 % if c.template_context['search_context']['repo_id']:
792 % if c.template_context['search_context']['repo_id']:
793 For advanced full text search visit: <a href="${h.route_path('search_repo',repo_name=c.template_context['search_context']['repo_name'])}">repository search</a>
793 For advanced full text search visit: <a href="${h.route_path('search_repo',repo_name=c.template_context['search_context']['repo_name'])}">repository search</a>
794 % elif c.template_context['search_context']['repo_group_id']:
794 % elif c.template_context['search_context']['repo_group_id']:
795 For advanced full text search visit: <a href="${h.route_path('search_repo_group',repo_group_name=c.template_context['search_context']['repo_group_name'])}">repository group search</a>
795 For advanced full text search visit: <a href="${h.route_path('search_repo_group',repo_group_name=c.template_context['search_context']['repo_group_name'])}">repository group search</a>
796 % else:
796 % else:
797 For advanced full text search visit: <a href="${h.route_path('search')}">global search</a>
797 For advanced full text search visit: <a href="${h.route_path('search')}">global search</a>
798 % endif
798 % endif
799 </div>
799 </div>
800 </li>
800 </li>
801
801
802 ## ROOT MENU
802 ## ROOT MENU
803 <li class="${h.is_active('home', active)}">
803 <li class="${h.is_active('home', active)}">
804 <a class="menulink" title="${_('Home')}" href="${h.route_path('home')}">
804 <a class="menulink" title="${_('Home')}" href="${h.route_path('home')}">
805 <div class="menulabel">${_('Home')}</div>
805 <div class="menulabel">${_('Home')}</div>
806 </a>
806 </a>
807 </li>
807 </li>
808
808
809 %if c.rhodecode_user.username != h.DEFAULT_USER:
809 %if c.rhodecode_user.username != h.DEFAULT_USER:
810 <li class="${h.is_active('journal', active)}">
810 <li class="${h.is_active('journal', active)}">
811 <a class="menulink" title="${_('Show activity journal')}" href="${h.route_path('journal')}">
811 <a class="menulink" title="${_('Show activity journal')}" href="${h.route_path('journal')}">
812 <div class="menulabel">${_('Journal')}</div>
812 <div class="menulabel">${_('Journal')}</div>
813 </a>
813 </a>
814 </li>
814 </li>
815 %else:
815 %else:
816 <li class="${h.is_active('journal', active)}">
816 <li class="${h.is_active('journal', active)}">
817 <a class="menulink" title="${_('Show Public activity journal')}" href="${h.route_path('journal_public')}">
817 <a class="menulink" title="${_('Show Public activity journal')}" href="${h.route_path('journal_public')}">
818 <div class="menulabel">${_('Public journal')}</div>
818 <div class="menulabel">${_('Public journal')}</div>
819 </a>
819 </a>
820 </li>
820 </li>
821 %endif
821 %endif
822
822
823 <li class="${h.is_active('gists', active)}">
823 <li class="${h.is_active('gists', active)}">
824 <a class="menulink childs" title="${_('Show Gists')}" href="${h.route_path('gists_show')}">
824 <a class="menulink childs" title="${_('Show Gists')}" href="${h.route_path('gists_show')}">
825 <div class="menulabel">${_('Gists')}</div>
825 <div class="menulabel">${_('Gists')}</div>
826 </a>
826 </a>
827 </li>
827 </li>
828
828
829 % if c.is_super_admin or c.is_delegated_admin:
829 % if c.is_super_admin or c.is_delegated_admin:
830 <li class="${h.is_active('admin', active)}">
830 <li class="${h.is_active('admin', active)}">
831 <a class="menulink childs" title="${_('Admin settings')}" href="${h.route_path('admin_home')}">
831 <a class="menulink childs" title="${_('Admin settings')}" href="${h.route_path('admin_home')}">
832 <div class="menulabel">${_('Admin')} </div>
832 <div class="menulabel">${_('Admin')} </div>
833 </a>
833 </a>
834 </li>
834 </li>
835 % endif
835 % endif
836
836
837 ## render extra user menu
837 ## render extra user menu
838 ${usermenu(active=(active=='my_account'))}
838 ${usermenu(active=(active=='my_account'))}
839
839
840 </ul>
840 </ul>
841
841
842 <script type="text/javascript">
842 <script type="text/javascript">
843 var visualShowPublicIcon = "${c.visual.show_public_icon}" == "True";
843 var visualShowPublicIcon = "${c.visual.show_public_icon}" == "True";
844
844
845 var formatRepoResult = function(result, container, query, escapeMarkup) {
845 var formatRepoResult = function(result, container, query, escapeMarkup) {
846 return function(data, escapeMarkup) {
846 return function(data, escapeMarkup) {
847 if (!data.repo_id){
847 if (!data.repo_id){
848 return data.text; // optgroup text Repositories
848 return data.text; // optgroup text Repositories
849 }
849 }
850
850
851 var tmpl = '';
851 var tmpl = '';
852 var repoType = data['repo_type'];
852 var repoType = data['repo_type'];
853 var repoName = data['text'];
853 var repoName = data['text'];
854
854
855 if(data && data.type == 'repo'){
855 if(data && data.type == 'repo'){
856 if(repoType === 'hg'){
856 if(repoType === 'hg'){
857 tmpl += '<i class="icon-hg"></i> ';
857 tmpl += '<i class="icon-hg"></i> ';
858 }
858 }
859 else if(repoType === 'git'){
859 else if(repoType === 'git'){
860 tmpl += '<i class="icon-git"></i> ';
860 tmpl += '<i class="icon-git"></i> ';
861 }
861 }
862 else if(repoType === 'svn'){
862 else if(repoType === 'svn'){
863 tmpl += '<i class="icon-svn"></i> ';
863 tmpl += '<i class="icon-svn"></i> ';
864 }
864 }
865 if(data['private']){
865 if(data['private']){
866 tmpl += '<i class="icon-lock" ></i> ';
866 tmpl += '<i class="icon-lock" ></i> ';
867 }
867 }
868 else if(visualShowPublicIcon){
868 else if(visualShowPublicIcon){
869 tmpl += '<i class="icon-unlock-alt"></i> ';
869 tmpl += '<i class="icon-unlock-alt"></i> ';
870 }
870 }
871 }
871 }
872 tmpl += escapeMarkup(repoName);
872 tmpl += escapeMarkup(repoName);
873 return tmpl;
873 return tmpl;
874
874
875 }(result, escapeMarkup);
875 }(result, escapeMarkup);
876 };
876 };
877
877
878 var formatRepoGroupResult = function(result, container, query, escapeMarkup) {
878 var formatRepoGroupResult = function(result, container, query, escapeMarkup) {
879 return function(data, escapeMarkup) {
879 return function(data, escapeMarkup) {
880 if (!data.repo_group_id){
880 if (!data.repo_group_id){
881 return data.text; // optgroup text Repositories
881 return data.text; // optgroup text Repositories
882 }
882 }
883
883
884 var tmpl = '';
884 var tmpl = '';
885 var repoGroupName = data['text'];
885 var repoGroupName = data['text'];
886
886
887 if(data){
887 if(data){
888
888
889 tmpl += '<i class="icon-repo-group"></i> ';
889 tmpl += '<i class="icon-repo-group"></i> ';
890
890
891 }
891 }
892 tmpl += escapeMarkup(repoGroupName);
892 tmpl += escapeMarkup(repoGroupName);
893 return tmpl;
893 return tmpl;
894
894
895 }(result, escapeMarkup);
895 }(result, escapeMarkup);
896 };
896 };
897
897
898 var escapeRegExChars = function (value) {
898 var escapeRegExChars = function (value) {
899 return value.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
899 return value.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
900 };
900 };
901
901
902 var getRepoIcon = function(repo_type) {
902 var getRepoIcon = function(repo_type) {
903 if (repo_type === 'hg') {
903 if (repo_type === 'hg') {
904 return '<i class="icon-hg"></i> ';
904 return '<i class="icon-hg"></i> ';
905 }
905 }
906 else if (repo_type === 'git') {
906 else if (repo_type === 'git') {
907 return '<i class="icon-git"></i> ';
907 return '<i class="icon-git"></i> ';
908 }
908 }
909 else if (repo_type === 'svn') {
909 else if (repo_type === 'svn') {
910 return '<i class="icon-svn"></i> ';
910 return '<i class="icon-svn"></i> ';
911 }
911 }
912 return ''
912 return ''
913 };
913 };
914
914
915 var autocompleteMainFilterFormatResult = function (data, value, org_formatter) {
915 var autocompleteMainFilterFormatResult = function (data, value, org_formatter) {
916
916
917 if (value.split(':').length === 2) {
917 if (value.split(':').length === 2) {
918 value = value.split(':')[1]
918 value = value.split(':')[1]
919 }
919 }
920
920
921 var searchType = data['type'];
921 var searchType = data['type'];
922 var searchSubType = data['subtype'];
922 var searchSubType = data['subtype'];
923 var valueDisplay = data['value_display'];
923 var valueDisplay = data['value_display'];
924 var valueIcon = data['value_icon'];
924 var valueIcon = data['value_icon'];
925
925
926 var pattern = '(' + escapeRegExChars(value) + ')';
926 var pattern = '(' + escapeRegExChars(value) + ')';
927
927
928 valueDisplay = Select2.util.escapeMarkup(valueDisplay);
928 valueDisplay = Select2.util.escapeMarkup(valueDisplay);
929
929
930 // highlight match
930 // highlight match
931 if (searchType != 'text') {
931 if (searchType != 'text') {
932 valueDisplay = valueDisplay.replace(new RegExp(pattern, 'gi'), '<strong>$1<\/strong>');
932 valueDisplay = valueDisplay.replace(new RegExp(pattern, 'gi'), '<strong>$1<\/strong>');
933 }
933 }
934
934
935 var icon = '';
935 var icon = '';
936
936
937 if (searchType === 'hint') {
937 if (searchType === 'hint') {
938 icon += '<i class="icon-repo-group"></i> ';
938 icon += '<i class="icon-repo-group"></i> ';
939 }
939 }
940 // full text search/hints
940 // full text search/hints
941 else if (searchType === 'search') {
941 else if (searchType === 'search') {
942 if (valueIcon === undefined) {
942 if (valueIcon === undefined) {
943 icon += '<i class="icon-more"></i> ';
943 icon += '<i class="icon-more"></i> ';
944 } else {
944 } else {
945 icon += valueIcon + ' ';
945 icon += valueIcon + ' ';
946 }
946 }
947
947
948 if (searchSubType !== undefined && searchSubType == 'repo') {
948 if (searchSubType !== undefined && searchSubType == 'repo') {
949 valueDisplay += '<div class="pull-right tag">repository</div>';
949 valueDisplay += '<div class="pull-right tag">repository</div>';
950 }
950 }
951 else if (searchSubType !== undefined && searchSubType == 'repo_group') {
951 else if (searchSubType !== undefined && searchSubType == 'repo_group') {
952 valueDisplay += '<div class="pull-right tag">repo group</div>';
952 valueDisplay += '<div class="pull-right tag">repo group</div>';
953 }
953 }
954 }
954 }
955 // repository
955 // repository
956 else if (searchType === 'repo') {
956 else if (searchType === 'repo') {
957
957
958 var repoIcon = getRepoIcon(data['repo_type']);
958 var repoIcon = getRepoIcon(data['repo_type']);
959 icon += repoIcon;
959 icon += repoIcon;
960
960
961 if (data['private']) {
961 if (data['private']) {
962 icon += '<i class="icon-lock" ></i> ';
962 icon += '<i class="icon-lock" ></i> ';
963 }
963 }
964 else if (visualShowPublicIcon) {
964 else if (visualShowPublicIcon) {
965 icon += '<i class="icon-unlock-alt"></i> ';
965 icon += '<i class="icon-unlock-alt"></i> ';
966 }
966 }
967 }
967 }
968 // repository groups
968 // repository groups
969 else if (searchType === 'repo_group') {
969 else if (searchType === 'repo_group') {
970 icon += '<i class="icon-repo-group"></i> ';
970 icon += '<i class="icon-repo-group"></i> ';
971 }
971 }
972 // user group
972 // user group
973 else if (searchType === 'user_group') {
973 else if (searchType === 'user_group') {
974 icon += '<i class="icon-group"></i> ';
974 icon += '<i class="icon-group"></i> ';
975 }
975 }
976 // user
976 // user
977 else if (searchType === 'user') {
977 else if (searchType === 'user') {
978 icon += '<img class="gravatar" src="{0}"/>'.format(data['icon_link']);
978 icon += '<img class="gravatar" src="{0}"/>'.format(data['icon_link']);
979 }
979 }
980 // pull request
980 // pull request
981 else if (searchType === 'pull_request') {
981 else if (searchType === 'pull_request') {
982 icon += '<i class="icon-merge"></i> ';
982 icon += '<i class="icon-merge"></i> ';
983 }
983 }
984 // commit
984 // commit
985 else if (searchType === 'commit') {
985 else if (searchType === 'commit') {
986 var repo_data = data['repo_data'];
986 var repo_data = data['repo_data'];
987 var repoIcon = getRepoIcon(repo_data['repository_type']);
987 var repoIcon = getRepoIcon(repo_data['repository_type']);
988 if (repoIcon) {
988 if (repoIcon) {
989 icon += repoIcon;
989 icon += repoIcon;
990 } else {
990 } else {
991 icon += '<i class="icon-tag"></i>';
991 icon += '<i class="icon-tag"></i>';
992 }
992 }
993 }
993 }
994 // file
994 // file
995 else if (searchType === 'file') {
995 else if (searchType === 'file') {
996 var repo_data = data['repo_data'];
996 var repo_data = data['repo_data'];
997 var repoIcon = getRepoIcon(repo_data['repository_type']);
997 var repoIcon = getRepoIcon(repo_data['repository_type']);
998 if (repoIcon) {
998 if (repoIcon) {
999 icon += repoIcon;
999 icon += repoIcon;
1000 } else {
1000 } else {
1001 icon += '<i class="icon-tag"></i>';
1001 icon += '<i class="icon-tag"></i>';
1002 }
1002 }
1003 }
1003 }
1004 // generic text
1004 // generic text
1005 else if (searchType === 'text') {
1005 else if (searchType === 'text') {
1006 icon = '';
1006 icon = '';
1007 }
1007 }
1008
1008
1009 var tmpl = '<div class="ac-container-wrap">{0}{1}</div>';
1009 var tmpl = '<div class="ac-container-wrap">{0}{1}</div>';
1010 return tmpl.format(icon, valueDisplay);
1010 return tmpl.format(icon, valueDisplay);
1011 };
1011 };
1012
1012
1013 var handleSelect = function(element, suggestion) {
1013 var handleSelect = function(element, suggestion) {
1014 if (suggestion.type === "hint") {
1014 if (suggestion.type === "hint") {
1015 // we skip action
1015 // we skip action
1016 $('#main_filter').focus();
1016 $('#main_filter').focus();
1017 }
1017 }
1018 else if (suggestion.type === "text") {
1018 else if (suggestion.type === "text") {
1019 // we skip action
1019 // we skip action
1020 $('#main_filter').focus();
1020 $('#main_filter').focus();
1021
1021
1022 } else {
1022 } else {
1023 window.location = suggestion['url'];
1023 window.location = suggestion['url'];
1024 }
1024 }
1025 };
1025 };
1026
1026
1027 var autocompleteMainFilterResult = function (suggestion, originalQuery, queryLowerCase) {
1027 var autocompleteMainFilterResult = function (suggestion, originalQuery, queryLowerCase) {
1028 if (queryLowerCase.split(':').length === 2) {
1028 if (queryLowerCase.split(':').length === 2) {
1029 queryLowerCase = queryLowerCase.split(':')[1]
1029 queryLowerCase = queryLowerCase.split(':')[1]
1030 }
1030 }
1031 if (suggestion.type === "text") {
1031 if (suggestion.type === "text") {
1032 // special case we don't want to "skip" display for
1032 // special case we don't want to "skip" display for
1033 return true
1033 return true
1034 }
1034 }
1035 return suggestion.value_display.toLowerCase().indexOf(queryLowerCase) !== -1;
1035 return suggestion.value_display.toLowerCase().indexOf(queryLowerCase) !== -1;
1036 };
1036 };
1037
1037
1038 var cleanContext = {
1038 var cleanContext = {
1039 repo_view_type: null,
1039 repo_view_type: null,
1040
1040
1041 repo_id: null,
1041 repo_id: null,
1042 repo_name: "",
1042 repo_name: "",
1043
1043
1044 repo_group_id: null,
1044 repo_group_id: null,
1045 repo_group_name: null
1045 repo_group_name: null
1046 };
1046 };
1047 var removeGoToFilter = function () {
1047 var removeGoToFilter = function () {
1048 $('.searchTagHidable').hide();
1048 $('.searchTagHidable').hide();
1049 $('#main_filter').autocomplete(
1049 $('#main_filter').autocomplete(
1050 'setOptions', {params:{search_context: cleanContext}});
1050 'setOptions', {params:{search_context: cleanContext}});
1051 };
1051 };
1052
1052
1053 $('#main_filter').autocomplete({
1053 $('#main_filter').autocomplete({
1054 serviceUrl: pyroutes.url('goto_switcher_data'),
1054 serviceUrl: pyroutes.url('goto_switcher_data'),
1055 params: {
1055 params: {
1056 "search_context": templateContext.search_context
1056 "search_context": templateContext.search_context
1057 },
1057 },
1058 minChars:2,
1058 minChars:2,
1059 maxHeight:400,
1059 maxHeight:400,
1060 deferRequestBy: 300, //miliseconds
1060 deferRequestBy: 300, //miliseconds
1061 tabDisabled: true,
1061 tabDisabled: true,
1062 autoSelectFirst: false,
1062 autoSelectFirst: false,
1063 containerClass: 'autocomplete-qfilter-suggestions',
1063 containerClass: 'autocomplete-qfilter-suggestions',
1064 formatResult: autocompleteMainFilterFormatResult,
1064 formatResult: autocompleteMainFilterFormatResult,
1065 lookupFilter: autocompleteMainFilterResult,
1065 lookupFilter: autocompleteMainFilterResult,
1066 onSelect: function (element, suggestion) {
1066 onSelect: function (element, suggestion) {
1067 handleSelect(element, suggestion);
1067 handleSelect(element, suggestion);
1068 return false;
1068 return false;
1069 },
1069 },
1070 onSearchError: function (element, query, jqXHR, textStatus, errorThrown) {
1070 onSearchError: function (element, query, jqXHR, textStatus, errorThrown) {
1071 if (jqXHR !== 'abort') {
1071 if (jqXHR !== 'abort') {
1072 var message = formatErrorMessage(jqXHR, textStatus, errorThrown);
1072 var message = formatErrorMessage(jqXHR, textStatus, errorThrown);
1073 SwalNoAnimation.fire({
1073 SwalNoAnimation.fire({
1074 icon: 'error',
1074 icon: 'error',
1075 title: _gettext('Error during search operation'),
1075 title: _gettext('Error during search operation'),
1076 html: '<span style="white-space: pre-line">{0}</span>'.format(message),
1076 html: '<span style="white-space: pre-line">{0}</span>'.format(message),
1077 }).then(function(result) {
1077 }).then(function(result) {
1078 window.location.reload();
1078 window.location.reload();
1079 })
1079 })
1080 }
1080 }
1081 },
1081 },
1082 onSearchStart: function (params) {
1082 onSearchStart: function (params) {
1083 $('.searchTag.searchTagIcon').html('<i class="icon-spin animate-spin"></i>')
1083 $('.searchTag.searchTagIcon').html('<i class="icon-spin animate-spin"></i>')
1084 },
1084 },
1085 onSearchComplete: function (query, suggestions) {
1085 onSearchComplete: function (query, suggestions) {
1086 $('.searchTag.searchTagIcon').html('<i class="icon-search"></i>')
1086 $('.searchTag.searchTagIcon').html('<i class="icon-search"></i>')
1087 },
1087 },
1088 });
1088 });
1089
1089
1090 showMainFilterBox = function () {
1090 showMainFilterBox = function () {
1091 $('#main_filter_help').toggle();
1091 $('#main_filter_help').toggle();
1092 };
1092 };
1093
1093
1094 $('#main_filter').on('keydown.autocomplete', function (e) {
1094 $('#main_filter').on('keydown.autocomplete', function (e) {
1095
1095
1096 var BACKSPACE = 8;
1096 var BACKSPACE = 8;
1097 var el = $(e.currentTarget);
1097 var el = $(e.currentTarget);
1098 if(e.which === BACKSPACE){
1098 if(e.which === BACKSPACE){
1099 var inputVal = el.val();
1099 var inputVal = el.val();
1100 if (inputVal === ""){
1100 if (inputVal === ""){
1101 removeGoToFilter()
1101 removeGoToFilter()
1102 }
1102 }
1103 }
1103 }
1104 });
1104 });
1105
1105
1106 var dismissNotice = function(noticeId) {
1106 var dismissNotice = function(noticeId) {
1107
1107
1108 var url = pyroutes.url('user_notice_dismiss',
1108 var url = pyroutes.url('user_notice_dismiss',
1109 {"user_id": templateContext.rhodecode_user.user_id});
1109 {"user_id": templateContext.rhodecode_user.user_id});
1110
1110
1111 var postData = {
1111 var postData = {
1112 'csrf_token': CSRF_TOKEN,
1112 'csrf_token': CSRF_TOKEN,
1113 'notice_id': noticeId,
1113 'notice_id': noticeId,
1114 };
1114 };
1115
1115
1116 var success = function(response) {
1116 var success = function(response) {
1117 $('#notice-message-' + noticeId).remove();
1117 $('#notice-message-' + noticeId).remove();
1118 return false;
1118 return false;
1119 };
1119 };
1120 var failure = function(data, textStatus, xhr) {
1120 var failure = function(data, textStatus, xhr) {
1121 alert("error processing request: " + textStatus);
1121 alert("error processing request: " + textStatus);
1122 return false;
1122 return false;
1123 };
1123 };
1124 ajaxPOST(url, postData, success, failure);
1124 ajaxPOST(url, postData, success, failure);
1125 }
1125 }
1126 </script>
1126 </script>
1127 <script src="${h.asset('js/rhodecode/base/keyboard-bindings.js', ver=c.rhodecode_version_hash)}"></script>
1127 <script src="${h.asset('js/rhodecode/base/keyboard-bindings.js', ver=c.rhodecode_version_hash)}"></script>
1128 </%def>
1128 </%def>
1129
1129
1130 <div class="modal" id="help_kb" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
1130 <div class="modal" id="help_kb" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
1131 <div class="modal-dialog">
1131 <div class="modal-dialog">
1132 <div class="modal-content">
1132 <div class="modal-content">
1133 <div class="modal-header">
1133 <div class="modal-header">
1134 <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
1134 <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
1135 <h4 class="modal-title" id="myModalLabel">${_('Keyboard shortcuts')}</h4>
1135 <h4 class="modal-title" id="myModalLabel">${_('Keyboard shortcuts')}</h4>
1136 </div>
1136 </div>
1137 <div class="modal-body">
1137 <div class="modal-body">
1138 <div class="block-left">
1138 <div class="block-left">
1139 <table class="keyboard-mappings">
1139 <table class="keyboard-mappings">
1140 <tbody>
1140 <tbody>
1141 <tr>
1141 <tr>
1142 <th></th>
1142 <th></th>
1143 <th>${_('Site-wide shortcuts')}</th>
1143 <th>${_('Site-wide shortcuts')}</th>
1144 </tr>
1144 </tr>
1145 <%
1145 <%
1146 elems = [
1146 elems = [
1147 ('/', 'Use quick search box'),
1147 ('/', 'Use quick search box'),
1148 ('g h', 'Goto home page'),
1148 ('g h', 'Goto home page'),
1149 ('g g', 'Goto my private gists page'),
1149 ('g g', 'Goto my private gists page'),
1150 ('g G', 'Goto my public gists page'),
1150 ('g G', 'Goto my public gists page'),
1151 ('g 0-9', 'Goto bookmarked items from 0-9'),
1151 ('g 0-9', 'Goto bookmarked items from 0-9'),
1152 ('n r', 'New repository page'),
1152 ('n r', 'New repository page'),
1153 ('n g', 'New gist page'),
1153 ('n g', 'New gist page'),
1154 ]
1154 ]
1155 %>
1155 %>
1156 %for key, desc in elems:
1156 %for key, desc in elems:
1157 <tr>
1157 <tr>
1158 <td class="keys">
1158 <td class="keys">
1159 <span class="key tag">${key}</span>
1159 <span class="key tag">${key}</span>
1160 </td>
1160 </td>
1161 <td>${desc}</td>
1161 <td>${desc}</td>
1162 </tr>
1162 </tr>
1163 %endfor
1163 %endfor
1164 </tbody>
1164 </tbody>
1165 </table>
1165 </table>
1166 </div>
1166 </div>
1167 <div class="block-left">
1167 <div class="block-left">
1168 <table class="keyboard-mappings">
1168 <table class="keyboard-mappings">
1169 <tbody>
1169 <tbody>
1170 <tr>
1170 <tr>
1171 <th></th>
1171 <th></th>
1172 <th>${_('Repositories')}</th>
1172 <th>${_('Repositories')}</th>
1173 </tr>
1173 </tr>
1174 <%
1174 <%
1175 elems = [
1175 elems = [
1176 ('g s', 'Goto summary page'),
1176 ('g s', 'Goto summary page'),
1177 ('g c', 'Goto changelog page'),
1177 ('g c', 'Goto changelog page'),
1178 ('g f', 'Goto files page'),
1178 ('g f', 'Goto files page'),
1179 ('g F', 'Goto files page with file search activated'),
1179 ('g F', 'Goto files page with file search activated'),
1180 ('g p', 'Goto pull requests page'),
1180 ('g p', 'Goto pull requests page'),
1181 ('g o', 'Goto repository settings'),
1181 ('g o', 'Goto repository settings'),
1182 ('g O', 'Goto repository access permissions settings'),
1182 ('g O', 'Goto repository access permissions settings'),
1183 ]
1183 ]
1184 %>
1184 %>
1185 %for key, desc in elems:
1185 %for key, desc in elems:
1186 <tr>
1186 <tr>
1187 <td class="keys">
1187 <td class="keys">
1188 <span class="key tag">${key}</span>
1188 <span class="key tag">${key}</span>
1189 </td>
1189 </td>
1190 <td>${desc}</td>
1190 <td>${desc}</td>
1191 </tr>
1191 </tr>
1192 %endfor
1192 %endfor
1193 </tbody>
1193 </tbody>
1194 </table>
1194 </table>
1195 </div>
1195 </div>
1196 </div>
1196 </div>
1197 <div class="modal-footer">
1197 <div class="modal-footer">
1198 </div>
1198 </div>
1199 </div><!-- /.modal-content -->
1199 </div><!-- /.modal-content -->
1200 </div><!-- /.modal-dialog -->
1200 </div><!-- /.modal-dialog -->
1201 </div><!-- /.modal -->
1201 </div><!-- /.modal -->
@@ -1,116 +1,116 b''
1 <%inherit file="/base/base.mako"/>
1 <%inherit file="/base/base.mako"/>
2
2
3 <%def name="title()">
3 <%def name="title()">
4 ${_('{} Files Add').format(c.repo_name)}
4 ${_('{} Files Add').format(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()"></%def>
14 <%def name="breadcrumbs_links()"></%def>
15
15
16 <%def name="menu_bar_subnav()">
16 <%def name="menu_bar_subnav()">
17 ${self.repo_menu(active='files')}
17 ${self.repo_menu(active='files')}
18 </%def>
18 </%def>
19
19
20 <%def name="main()">
20 <%def name="main()">
21
21
22 <div class="box">
22 <div class="box">
23
23
24 <div class="edit-file-title">
24 <div class="edit-file-title">
25 <span class="title-heading">${_('Add new file')} @ <code>${h.show_id(c.commit)}</code></span>
25 <span class="title-heading">${_('Add new file')} @ <code>${h.show_id(c.commit)}</code></span>
26 % if c.commit.branch:
26 % if c.commit.branch:
27 <span class="tag branchtag">
27 <span class="tag branchtag">
28 <i class="icon-branch"></i> ${c.commit.branch}
28 <i class="icon-branch"></i> ${c.commit.branch}
29 </span>
29 </span>
30 % endif
30 % endif
31 </div>
31 </div>
32
32
33 ${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', request=request)}
33 ${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', request=request)}
34 <div class="edit-file-fieldset">
34 <div class="edit-file-fieldset">
35 <div class="path-items">
35 <div class="path-items">
36 <ul>
36 <ul>
37 <li class="breadcrumb-path">
37 <li class="breadcrumb-path">
38 <div>
38 <div>
39 ${h.files_breadcrumbs(c.repo_name, c.commit.raw_id, c.f_path, c.rhodecode_db_repo.landing_ref_name, request.GET.get('at'), limit_items=True, hide_last_item=False, linkify_last_item=True, copy_path_icon=False)} /
39 ${h.files_breadcrumbs(c.repo_name, c.rhodecode_db_repo.repo_type, c.commit.raw_id, c.f_path, c.rhodecode_db_repo.landing_ref_name, request.GET.get('at'), limit_items=True, hide_last_item=False, linkify_last_item=True, copy_path_icon=False)} /
40 </div>
40 </div>
41 </li>
41 </li>
42 <li class="location-path">
42 <li class="location-path">
43 <input class="file-name-input input-small" type="text" value="${request.GET.get('filename')}" name="filename" id="filename" placeholder="${_('Filename e.g example.py, or docs/readme.md')}">
43 <input class="file-name-input input-small" type="text" value="${request.GET.get('filename')}" name="filename" id="filename" placeholder="${_('Filename e.g example.py, or docs/readme.md')}">
44 </li>
44 </li>
45 </ul>
45 </ul>
46 </div>
46 </div>
47
47
48 </div>
48 </div>
49
49
50 <div class="table">
50 <div class="table">
51 <div>
51 <div>
52 <div id="codeblock" class="codeblock">
52 <div id="codeblock" class="codeblock">
53 <div class="editor-items">
53 <div class="editor-items">
54 <div class="editor-action active show-editor pull-left" onclick="fileEditor.showEditor(); return false">
54 <div class="editor-action active show-editor pull-left" onclick="fileEditor.showEditor(); return false">
55 ${_('Edit')}
55 ${_('Edit')}
56 </div>
56 </div>
57
57
58 <div class="editor-action show-preview pull-left" onclick="fileEditor.showPreview(); return false">
58 <div class="editor-action show-preview pull-left" onclick="fileEditor.showPreview(); return false">
59 ${_('Preview')}
59 ${_('Preview')}
60 </div>
60 </div>
61
61
62 <div class="pull-right">
62 <div class="pull-right">
63 ${h.dropdownmenu('line_wrap', 'off', [('on', _('Line wraps on')), ('off', _('line wraps off'))], extra_classes=['last-item'])}
63 ${h.dropdownmenu('line_wrap', 'off', [('on', _('Line wraps on')), ('off', _('line wraps off'))], extra_classes=['last-item'])}
64 </div>
64 </div>
65 <div class="pull-right">
65 <div class="pull-right">
66 ${h.dropdownmenu('set_mode','plain',[('plain', _('plain'))], enable_filter=True)}
66 ${h.dropdownmenu('set_mode','plain',[('plain', _('plain'))], enable_filter=True)}
67 </div>
67 </div>
68 </div>
68 </div>
69
69
70 <div id="editor_container">
70 <div id="editor_container">
71 <pre id="editor_pre"></pre>
71 <pre id="editor_pre"></pre>
72 <textarea id="editor" name="content" ></textarea>
72 <textarea id="editor" name="content" ></textarea>
73 <div id="editor_preview"></div>
73 <div id="editor_preview"></div>
74 </div>
74 </div>
75 </div>
75 </div>
76 </div>
76 </div>
77 </div>
77 </div>
78
78
79 <div class="edit-file-fieldset">
79 <div class="edit-file-fieldset">
80 <div class="fieldset">
80 <div class="fieldset">
81 <div class="message">
81 <div class="message">
82 <textarea id="commit" name="message" placeholder="${c.default_message}"></textarea>
82 <textarea id="commit" name="message" placeholder="${c.default_message}"></textarea>
83 </div>
83 </div>
84 </div>
84 </div>
85 <div class="pull-left">
85 <div class="pull-left">
86 ${h.submit('commit_btn',_('Commit changes'), class_="btn btn-small btn-success")}
86 ${h.submit('commit_btn',_('Commit changes'), class_="btn btn-small btn-success")}
87 </div>
87 </div>
88 </div>
88 </div>
89 ${h.end_form()}
89 ${h.end_form()}
90 </div>
90 </div>
91
91
92 <script type="text/javascript">
92 <script type="text/javascript">
93
93
94 $(document).ready(function () {
94 $(document).ready(function () {
95 var modes_select = $('#set_mode');
95 var modes_select = $('#set_mode');
96 var filename_selector = '#filename';
96 var filename_selector = '#filename';
97 fillCodeMirrorOptions(modes_select);
97 fillCodeMirrorOptions(modes_select);
98
98
99 fileEditor = new FileEditor('#editor');
99 fileEditor = new FileEditor('#editor');
100
100
101 // on change of select field set mode
101 // on change of select field set mode
102 setCodeMirrorModeFromSelect(modes_select, filename_selector, fileEditor.cm, null);
102 setCodeMirrorModeFromSelect(modes_select, filename_selector, fileEditor.cm, null);
103
103
104 // on entering the new filename set mode, from given extension
104 // on entering the new filename set mode, from given extension
105 setCodeMirrorModeFromInput(modes_select, filename_selector, fileEditor.cm, null);
105 setCodeMirrorModeFromInput(modes_select, filename_selector, fileEditor.cm, null);
106
106
107 $('#filename').focus();
107 $('#filename').focus();
108
108
109 var commit_id = "${c.commit.raw_id}";
109 var commit_id = "${c.commit.raw_id}";
110 var f_path = "${c.f_path}";
110 var f_path = "${c.f_path}";
111
111
112 checkFileHead($('#eform'), commit_id, f_path, 'create')
112 checkFileHead($('#eform'), commit_id, f_path, 'create')
113 });
113 });
114
114
115 </script>
115 </script>
116 </%def>
116 </%def>
@@ -1,98 +1,98 b''
1 <%namespace name="base" file="/base/base.mako"/>
1 <%namespace name="base" file="/base/base.mako"/>
2
2
3 <%
3 <%
4 at_ref = request.GET.get('at')
4 at_ref = request.GET.get('at')
5 if at_ref:
5 if at_ref:
6 query={'at': at_ref}
6 query={'at': at_ref}
7 default_commit_id = at_ref or c.rhodecode_db_repo.landing_ref_name
7 default_landing_ref = at_ref or c.rhodecode_db_repo.landing_ref_name
8 else:
8 else:
9 query=None
9 query=None
10 default_commit_id = c.commit.raw_id
10 default_landing_ref = c.commit.raw_id
11 %>
11 %>
12 <div id="file-tree-wrapper" class="browser-body ${('full-load' if c.full_load else '')}">
12 <div id="file-tree-wrapper" class="browser-body ${('full-load' if c.full_load else '')}">
13 <table class="code-browser rctable repo_summary">
13 <table class="code-browser rctable repo_summary">
14 <thead>
14 <thead>
15 <tr>
15 <tr>
16 <th>${_('Name')}</th>
16 <th>${_('Name')}</th>
17 <th>${_('Size')}</th>
17 <th>${_('Size')}</th>
18 <th>${_('Modified')}</th>
18 <th>${_('Modified')}</th>
19 <th>${_('Last Commit')}</th>
19 <th>${_('Last Commit')}</th>
20 <th>${_('Author')}</th>
20 <th>${_('Author')}</th>
21 </tr>
21 </tr>
22 </thead>
22 </thead>
23
23
24 <tbody id="tbody">
24 <tbody id="tbody">
25 <tr>
25 <tr>
26 <td colspan="5">
26 <td colspan="5">
27 ${h.files_breadcrumbs(c.repo_name, c.commit.raw_id, c.file.path, c.rhodecode_db_repo.landing_ref_name, request.GET.get('at'), limit_items=True)}
27 ${h.files_breadcrumbs(c.repo_name, c.rhodecode_db_repo.repo_type, c.commit.raw_id, c.file.path, c.rhodecode_db_repo.landing_ref_name, request.GET.get('at'), limit_items=True)}
28 </td>
28 </td>
29 </tr>
29 </tr>
30
30
31 <% has_files = False %>
31 <% has_files = False %>
32 % for cnt,node in enumerate(c.file):
32 % for cnt,node in enumerate(c.file):
33 <% has_files = True %>
33 <% has_files = True %>
34 <tr class="parity${(cnt % 2)}">
34 <tr class="parity${(cnt % 2)}">
35 <td class="td-componentname">
35 <td class="td-componentname">
36 % if node.is_submodule():
36 % if node.is_submodule():
37 <span class="submodule-dir">
37 <span class="submodule-dir">
38 % if node.url.startswith('http://') or node.url.startswith('https://'):
38 % if node.url.startswith('http://') or node.url.startswith('https://'):
39 <a href="${node.url}">
39 <a href="${node.url}">
40 <i class="icon-directory browser-dir"></i>${node.name}
40 <i class="icon-directory browser-dir"></i>${node.name}
41 </a>
41 </a>
42 % else:
42 % else:
43 <i class="icon-directory browser-dir"></i>${node.name}
43 <i class="icon-directory browser-dir"></i>${node.name}
44 % endif
44 % endif
45 </span>
45 </span>
46 % else:
46 % else:
47 <a href="${h.route_path('repo_files',repo_name=c.repo_name,commit_id=default_commit_id,f_path=h.safe_unicode(node.path), _query=query)}">
47 <a href="${h.repo_files_by_ref_url(c.repo_name, c.rhodecode_db_repo.repo_type, f_path=h.safe_unicode(node.path), ref_name=default_landing_ref, commit_id=c.commit.raw_id, query=query)}">
48 <i class="${('icon-file-text browser-file' if node.is_file() else 'icon-directory browser-dir')}"></i>${node.name}
48 <i class="${('icon-file-text browser-file' if node.is_file() else 'icon-directory browser-dir')}"></i>${node.name}
49 </a>
49 </a>
50 % endif
50 % endif
51 </td>
51 </td>
52 %if node.is_file():
52 %if node.is_file():
53 <td class="td-size" data-attr-name="size">
53 <td class="td-size" data-attr-name="size">
54 % if c.full_load:
54 % if c.full_load:
55 <span data-size="${node.size}">${h.format_byte_size_binary(node.size)}</span>
55 <span data-size="${node.size}">${h.format_byte_size_binary(node.size)}</span>
56 % else:
56 % else:
57 ${_('Loading ...')}
57 ${_('Loading ...')}
58 % endif
58 % endif
59 </td>
59 </td>
60 <td class="td-time" data-attr-name="modified_at">
60 <td class="td-time" data-attr-name="modified_at">
61 % if c.full_load:
61 % if c.full_load:
62 <span data-date="${node.last_commit.date}">${h.age_component(node.last_commit.date)}</span>
62 <span data-date="${node.last_commit.date}">${h.age_component(node.last_commit.date)}</span>
63 % endif
63 % endif
64 </td>
64 </td>
65 <td class="td-hash" data-attr-name="commit_id">
65 <td class="td-hash" data-attr-name="commit_id">
66 % if c.full_load:
66 % if c.full_load:
67 <div class="tooltip-hovercard" data-hovercard-alt="${node.last_commit.message}" data-hovercard-url="${h.route_path('hovercard_repo_commit', repo_name=c.repo_name, commit_id=node.last_commit.raw_id)}">
67 <div class="tooltip-hovercard" data-hovercard-alt="${node.last_commit.message}" data-hovercard-url="${h.route_path('hovercard_repo_commit', repo_name=c.repo_name, commit_id=node.last_commit.raw_id)}">
68 <pre data-commit-id="${node.last_commit.raw_id}">r${node.last_commit.idx}:${node.last_commit.short_id}</pre>
68 <pre data-commit-id="${node.last_commit.raw_id}">r${node.last_commit.idx}:${node.last_commit.short_id}</pre>
69 </div>
69 </div>
70 % endif
70 % endif
71 </td>
71 </td>
72 <td class="td-user" data-attr-name="author">
72 <td class="td-user" data-attr-name="author">
73 % if c.full_load:
73 % if c.full_load:
74 <span data-author="${node.last_commit.author}">${h.gravatar_with_user(request, node.last_commit.author, tooltip=True)|n}</span>
74 <span data-author="${node.last_commit.author}">${h.gravatar_with_user(request, node.last_commit.author, tooltip=True)|n}</span>
75 % endif
75 % endif
76 </td>
76 </td>
77 %else:
77 %else:
78 <td></td>
78 <td></td>
79 <td></td>
79 <td></td>
80 <td></td>
80 <td></td>
81 <td></td>
81 <td></td>
82 %endif
82 %endif
83 </tr>
83 </tr>
84 % endfor
84 % endfor
85
85
86 % if not has_files:
86 % if not has_files:
87 <tr>
87 <tr>
88 <td colspan="5">
88 <td colspan="5">
89 ##empty-dir mostly SVN
89 ##empty-dir mostly SVN
90 &nbsp;
90 &nbsp;
91 </td>
91 </td>
92 </tr>
92 </tr>
93 % endif
93 % endif
94
94
95 </tbody>
95 </tbody>
96 <tbody id="tbody_filtered"></tbody>
96 <tbody id="tbody_filtered"></tbody>
97 </table>
97 </table>
98 </div>
98 </div>
@@ -1,92 +1,92 b''
1 <%inherit file="/base/base.mako"/>
1 <%inherit file="/base/base.mako"/>
2
2
3 <%def name="title()">
3 <%def name="title()">
4 ${_('{} Files Delete').format(c.repo_name)}
4 ${_('{} Files Delete').format(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()"></%def>
14 <%def name="breadcrumbs_links()"></%def>
15
15
16 <%def name="menu_bar_subnav()">
16 <%def name="menu_bar_subnav()">
17 ${self.repo_menu(active='files')}
17 ${self.repo_menu(active='files')}
18 </%def>
18 </%def>
19
19
20 <%def name="main()">
20 <%def name="main()">
21
21
22 <div class="box">
22 <div class="box">
23
23
24 <div class="edit-file-title">
24 <div class="edit-file-title">
25 <span class="title-heading">${_('Delete file')} @ <code>${h.show_id(c.commit)}</code></span>
25 <span class="title-heading">${_('Delete file')} @ <code>${h.show_id(c.commit)}</code></span>
26 % if c.commit.branch:
26 % if c.commit.branch:
27 <span class="tag branchtag">
27 <span class="tag branchtag">
28 <i class="icon-branch"></i> ${c.commit.branch}
28 <i class="icon-branch"></i> ${c.commit.branch}
29 </span>
29 </span>
30 % endif
30 % endif
31 </div>
31 </div>
32
32
33 ${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', request=request)}
33 ${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', request=request)}
34 <div class="edit-file-fieldset">
34 <div class="edit-file-fieldset">
35 <div class="path-items">
35 <div class="path-items">
36 <li class="breadcrumb-path">
36 <li class="breadcrumb-path">
37 <div>
37 <div>
38 ${h.files_breadcrumbs(c.repo_name, c.commit.raw_id, c.file.path, c.rhodecode_db_repo.landing_ref_name, request.GET.get('at'), limit_items=True, hide_last_item=True, copy_path_icon=False)} /
38 ${h.files_breadcrumbs(c.repo_name, c.rhodecode_db_repo.repo_type, c.commit.raw_id, c.file.path, c.rhodecode_db_repo.landing_ref_name, request.GET.get('at'), limit_items=True, hide_last_item=True, copy_path_icon=False)} /
39 </div>
39 </div>
40 </li>
40 </li>
41 <li class="location-path">
41 <li class="location-path">
42 <input type="hidden" value="${c.f_path}" name="root_path">
42 <input type="hidden" value="${c.f_path}" name="root_path">
43 <input class="file-name-input input-small" type="text" value="${c.file.name}" name="filename" id="filename" disabled="disabled">
43 <input class="file-name-input input-small" type="text" value="${c.file.name}" name="filename" id="filename" disabled="disabled">
44 </li>
44 </li>
45 </div>
45 </div>
46
46
47 </div>
47 </div>
48
48
49 <div id="codeblock" class="codeblock delete-file-preview">
49 <div id="codeblock" class="codeblock delete-file-preview">
50 <div class="code-body">
50 <div class="code-body">
51 %if c.file.is_binary:
51 %if c.file.is_binary:
52 ${_('Binary file (%s)') % c.file.mimetype}
52 ${_('Binary file (%s)') % c.file.mimetype}
53 %else:
53 %else:
54 %if c.file.size < c.visual.cut_off_limit_file:
54 %if c.file.size < c.visual.cut_off_limit_file:
55 ${h.pygmentize(c.file,linenos=True,anchorlinenos=False,cssclass="code-highlight")}
55 ${h.pygmentize(c.file,linenos=True,anchorlinenos=False,cssclass="code-highlight")}
56 %else:
56 %else:
57 ${_('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'),
57 ${_('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'),
58 h.route_path('repo_file_raw',repo_name=c.repo_name,commit_id=c.commit.raw_id,f_path=c.f_path))}
58 h.route_path('repo_file_raw',repo_name=c.repo_name,commit_id=c.commit.raw_id,f_path=c.f_path))}
59 %endif
59 %endif
60 %endif
60 %endif
61 </div>
61 </div>
62 </div>
62 </div>
63
63
64 <div class="edit-file-fieldset">
64 <div class="edit-file-fieldset">
65 <div class="fieldset">
65 <div class="fieldset">
66 <div class="message">
66 <div class="message">
67 <textarea id="commit" name="message" placeholder="${c.default_message}"></textarea>
67 <textarea id="commit" name="message" placeholder="${c.default_message}"></textarea>
68 </div>
68 </div>
69 </div>
69 </div>
70 <div class="pull-left">
70 <div class="pull-left">
71 ${h.submit('commit_btn',_('Commit changes'),class_="btn btn-small btn-danger-action")}
71 ${h.submit('commit_btn',_('Commit changes'),class_="btn btn-small btn-danger-action")}
72 </div>
72 </div>
73 </div>
73 </div>
74 ${h.end_form()}
74 ${h.end_form()}
75 </div>
75 </div>
76
76
77
77
78 <script type="text/javascript">
78 <script type="text/javascript">
79
79
80 $(document).ready(function () {
80 $(document).ready(function () {
81
81
82 fileEditor = new FileEditor('#editor');
82 fileEditor = new FileEditor('#editor');
83
83
84 var commit_id = "${c.commit.raw_id}";
84 var commit_id = "${c.commit.raw_id}";
85 var f_path = "${c.f_path}";
85 var f_path = "${c.f_path}";
86
86
87 checkFileHead($('#eform'), commit_id, f_path, 'delete');
87 checkFileHead($('#eform'), commit_id, f_path, 'delete');
88 });
88 });
89
89
90 </script>
90 </script>
91
91
92 </%def>
92 </%def>
@@ -1,128 +1,128 b''
1 <%inherit file="/base/base.mako"/>
1 <%inherit file="/base/base.mako"/>
2
2
3 <%def name="title()">
3 <%def name="title()">
4 ${_('{} Files Edit').format(c.repo_name)}
4 ${_('{} Files Edit').format(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()"></%def>
14 <%def name="breadcrumbs_links()"></%def>
15
15
16 <%def name="menu_bar_subnav()">
16 <%def name="menu_bar_subnav()">
17 ${self.repo_menu(active='files')}
17 ${self.repo_menu(active='files')}
18 </%def>
18 </%def>
19
19
20 <%def name="main()">
20 <%def name="main()">
21
21
22 <div class="box">
22 <div class="box">
23
23
24 <div class="edit-file-title">
24 <div class="edit-file-title">
25 <span class="title-heading">${_('Edit file')} @ <code>${h.show_id(c.commit)}</code></span>
25 <span class="title-heading">${_('Edit file')} @ <code>${h.show_id(c.commit)}</code></span>
26 % if c.commit.branch:
26 % if c.commit.branch:
27 <span class="tag branchtag">
27 <span class="tag branchtag">
28 <i class="icon-branch"></i> ${c.commit.branch}
28 <i class="icon-branch"></i> ${c.commit.branch}
29 </span>
29 </span>
30 % endif
30 % endif
31 </div>
31 </div>
32
32
33 ${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)}
33 ${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)}
34 <div class="edit-file-fieldset">
34 <div class="edit-file-fieldset">
35 <div class="path-items">
35 <div class="path-items">
36 <ul>
36 <ul>
37 <li class="breadcrumb-path">
37 <li class="breadcrumb-path">
38 <div>
38 <div>
39 ${h.files_breadcrumbs(c.repo_name, c.commit.raw_id, c.file.path, c.rhodecode_db_repo.landing_ref_name, request.GET.get('at'), limit_items=True, hide_last_item=True, copy_path_icon=False)} /
39 ${h.files_breadcrumbs(c.repo_name, c.rhodecode_db_repo.repo_type, c.commit.raw_id, c.file.path, c.rhodecode_db_repo.landing_ref_name, request.GET.get('at'), limit_items=True, hide_last_item=True, copy_path_icon=False)} /
40 </div>
40 </div>
41 </li>
41 </li>
42 <li class="location-path">
42 <li class="location-path">
43 <input type="hidden" value="${c.f_path}" name="root_path">
43 <input type="hidden" value="${c.f_path}" name="root_path">
44 <input class="file-name-input input-small" type="text" value="${c.file.name}" name="filename" id="filename" placeholder="${_('Filename e.g example.py, or docs/readme.md')}">
44 <input class="file-name-input input-small" type="text" value="${c.file.name}" name="filename" id="filename" placeholder="${_('Filename e.g example.py, or docs/readme.md')}">
45 </li>
45 </li>
46 </ul>
46 </ul>
47 </div>
47 </div>
48
48
49 </div>
49 </div>
50
50
51 <div class="table">
51 <div class="table">
52 <div>
52 <div>
53
53
54 <div id="codeblock" class="codeblock">
54 <div id="codeblock" class="codeblock">
55 <div class="editor-items">
55 <div class="editor-items">
56 <div class="editor-action active show-editor pull-left" onclick="fileEditor.showEditor(); return false">
56 <div class="editor-action active show-editor pull-left" onclick="fileEditor.showEditor(); return false">
57 ${_('Edit')}
57 ${_('Edit')}
58 </div>
58 </div>
59
59
60 <div class="editor-action show-preview pull-left" onclick="fileEditor.showPreview(); return false">
60 <div class="editor-action show-preview pull-left" onclick="fileEditor.showPreview(); return false">
61 ${_('Preview')}
61 ${_('Preview')}
62 </div>
62 </div>
63
63
64 <div class="pull-right">
64 <div class="pull-right">
65 ${h.dropdownmenu('line_wrap', 'off', [('on', _('Line wraps on')), ('off', _('line wraps off')),])}
65 ${h.dropdownmenu('line_wrap', 'off', [('on', _('Line wraps on')), ('off', _('line wraps off')),])}
66 </div>
66 </div>
67 <div class="pull-right">
67 <div class="pull-right">
68 ${h.dropdownmenu('set_mode','plain',[('plain', _('plain'))],enable_filter=True)}
68 ${h.dropdownmenu('set_mode','plain',[('plain', _('plain'))],enable_filter=True)}
69 </div>
69 </div>
70 </div>
70 </div>
71
71
72 <div id="editor_container">
72 <div id="editor_container">
73 <pre id="editor_pre"></pre>
73 <pre id="editor_pre"></pre>
74 <textarea id="editor" name="content" >${h.escape(c.file.content)|n}</textarea>
74 <textarea id="editor" name="content" >${h.escape(c.file.content)|n}</textarea>
75 <div id="editor_preview" ></div>
75 <div id="editor_preview" ></div>
76 </div>
76 </div>
77 </div>
77 </div>
78 </div>
78 </div>
79 </div>
79 </div>
80
80
81 <div class="edit-file-fieldset">
81 <div class="edit-file-fieldset">
82 <div class="fieldset">
82 <div class="fieldset">
83 <div class="message">
83 <div class="message">
84 <textarea id="commit" name="message" placeholder="${c.default_message}"></textarea>
84 <textarea id="commit" name="message" placeholder="${c.default_message}"></textarea>
85 </div>
85 </div>
86 </div>
86 </div>
87 <div class="pull-left">
87 <div class="pull-left">
88 ${h.submit('commit_btn',_('Commit changes'), class_="btn btn-small btn-success")}
88 ${h.submit('commit_btn',_('Commit changes'), class_="btn btn-small btn-success")}
89 </div>
89 </div>
90 </div>
90 </div>
91 ${h.end_form()}
91 ${h.end_form()}
92 </div>
92 </div>
93
93
94 <script type="text/javascript">
94 <script type="text/javascript">
95
95
96 $(document).ready(function() {
96 $(document).ready(function() {
97 var modes_select = $('#set_mode');
97 var modes_select = $('#set_mode');
98 var filename_selector = '#filename';
98 var filename_selector = '#filename';
99 fillCodeMirrorOptions(modes_select);
99 fillCodeMirrorOptions(modes_select);
100
100
101 fileEditor = new FileEditor('#editor');
101 fileEditor = new FileEditor('#editor');
102
102
103 // try to detect the mode based on the file we edit
103 // try to detect the mode based on the file we edit
104 var detected_mode = detectCodeMirrorMode("${c.file.name}", "${c.file.mimetype}");
104 var detected_mode = detectCodeMirrorMode("${c.file.name}", "${c.file.mimetype}");
105
105
106 if (detected_mode) {
106 if (detected_mode) {
107 setCodeMirrorMode(fileEditor.cm, detected_mode);
107 setCodeMirrorMode(fileEditor.cm, detected_mode);
108
108
109 var mimetype = $(modes_select).find("option[mode={0}]".format(detected_mode)).val();
109 var mimetype = $(modes_select).find("option[mode={0}]".format(detected_mode)).val();
110 $(modes_select).select2("val", mimetype).trigger('change');
110 $(modes_select).select2("val", mimetype).trigger('change');
111 }
111 }
112
112
113 // on change of select field set mode
113 // on change of select field set mode
114 setCodeMirrorModeFromSelect(modes_select, filename_selector, fileEditor.cm, null);
114 setCodeMirrorModeFromSelect(modes_select, filename_selector, fileEditor.cm, null);
115
115
116 // on entering the new filename set mode, from given extension
116 // on entering the new filename set mode, from given extension
117 setCodeMirrorModeFromInput(modes_select, filename_selector, fileEditor.cm, null);
117 setCodeMirrorModeFromInput(modes_select, filename_selector, fileEditor.cm, null);
118
118
119 var commit_id = "${c.commit.raw_id}";
119 var commit_id = "${c.commit.raw_id}";
120 var f_path = "${c.f_path}";
120 var f_path = "${c.f_path}";
121
121
122 checkFileHead($('#eform'), commit_id, f_path, 'edit')
122 checkFileHead($('#eform'), commit_id, f_path, 'edit')
123
123
124 });
124 });
125
125
126
126
127 </script>
127 </script>
128 </%def>
128 </%def>
@@ -1,189 +1,189 b''
1 <%namespace name="sourceblock" file="/codeblocks/source.mako"/>
1 <%namespace name="sourceblock" file="/codeblocks/source.mako"/>
2
2
3 <%
3 <%
4 at_ref = request.GET.get('at')
4 at_ref = request.GET.get('at')
5 if at_ref:
5 if at_ref:
6 query={'at': at_ref}
6 query={'at': at_ref}
7 default_commit_id = at_ref or c.rhodecode_db_repo.landing_ref_name
7 default_commit_id = at_ref or c.rhodecode_db_repo.landing_ref_name
8 else:
8 else:
9 query=None
9 query=None
10 default_commit_id = c.commit.raw_id
10 default_commit_id = c.commit.raw_id
11 %>
11 %>
12
12
13 <div id="codeblock" class="browserblock">
13 <div id="codeblock" class="browserblock">
14 <div class="browser-header">
14 <div class="browser-header">
15 <div class="browser-nav">
15 <div class="browser-nav">
16 <div class="pull-left">
16 <div class="pull-left">
17 ## loads the history for a file
17 ## loads the history for a file
18 ${h.hidden('file_refs_filter')}
18 ${h.hidden('file_refs_filter')}
19 </div>
19 </div>
20
20
21 <div class="pull-right">
21 <div class="pull-right">
22
22
23 ## Download
23 ## Download
24 % if c.lf_node:
24 % if c.lf_node:
25 <a class="btn btn-default" href="${h.route_path('repo_file_download',repo_name=c.repo_name,commit_id=c.commit.raw_id,f_path=c.f_path, _query=dict(lf=1))}">
25 <a class="btn btn-default" href="${h.route_path('repo_file_download',repo_name=c.repo_name,commit_id=c.commit.raw_id,f_path=c.f_path, _query=dict(lf=1))}">
26 ${_('Download largefile')}
26 ${_('Download largefile')}
27 </a>
27 </a>
28 % else:
28 % else:
29 <a class="btn btn-default" href="${h.route_path('repo_file_download',repo_name=c.repo_name,commit_id=c.commit.raw_id,f_path=c.f_path)}">
29 <a class="btn btn-default" href="${h.route_path('repo_file_download',repo_name=c.repo_name,commit_id=c.commit.raw_id,f_path=c.f_path)}">
30 ${_('Download file')}
30 ${_('Download file')}
31 </a>
31 </a>
32 % endif
32 % endif
33
33
34 %if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name):
34 %if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name):
35 ## on branch head, can edit files
35 ## on branch head, can edit files
36 %if c.on_branch_head and c.branch_or_raw_id:
36 %if c.on_branch_head and c.branch_or_raw_id:
37 ## binary files are delete only
37 ## binary files are delete only
38 % if c.file.is_binary:
38 % if c.file.is_binary:
39 ${h.link_to(_('Edit'), '#Edit', class_="btn btn-default disabled tooltip", title=_('Editing binary files not allowed'))}
39 ${h.link_to(_('Edit'), '#Edit', class_="btn btn-default disabled tooltip", title=_('Editing binary files not allowed'))}
40 ${h.link_to(_('Delete'), h.route_path('repo_files_remove_file',repo_name=c.repo_name,commit_id=c.branch_or_raw_id,f_path=c.f_path, _query=query),class_="btn btn-danger")}
40 ${h.link_to(_('Delete'), h.route_path('repo_files_remove_file',repo_name=c.repo_name,commit_id=c.branch_or_raw_id,f_path=c.f_path, _query=query),class_="btn btn-danger")}
41 % else:
41 % else:
42 <a class="btn btn-default" href="${h.route_path('repo_files_edit_file',repo_name=c.repo_name,commit_id=c.branch_or_raw_id,f_path=c.f_path, _query=query)}">
42 <a class="btn btn-default" href="${h.route_path('repo_files_edit_file',repo_name=c.repo_name,commit_id=c.branch_or_raw_id,f_path=c.f_path, _query=query)}">
43 ${_('Edit on branch: ')}<code>${c.branch_name}</code>
43 ${_('Edit on branch: ')}<code>${c.branch_name}</code>
44 </a>
44 </a>
45
45
46 <a class="btn btn-danger" href="${h.route_path('repo_files_remove_file',repo_name=c.repo_name,commit_id=c.branch_or_raw_id,f_path=c.f_path, _query=query)}">
46 <a class="btn btn-danger" href="${h.route_path('repo_files_remove_file',repo_name=c.repo_name,commit_id=c.branch_or_raw_id,f_path=c.f_path, _query=query)}">
47 ${_('Delete')}
47 ${_('Delete')}
48 </a>
48 </a>
49 % endif
49 % endif
50 ## not on head, forbid all
50 ## not on head, forbid all
51 % else:
51 % else:
52 ${h.link_to(_('Edit'), '#Edit', class_="btn btn-default disabled tooltip", title=_('Editing files allowed only when on branch head commit'))}
52 ${h.link_to(_('Edit'), '#Edit', class_="btn btn-default disabled tooltip", title=_('Editing files allowed only when on branch head commit'))}
53 ${h.link_to(_('Delete'), '#Delete', class_="btn btn-default btn-danger disabled tooltip", title=_('Deleting files allowed only when on branch head commit'))}
53 ${h.link_to(_('Delete'), '#Delete', class_="btn btn-default btn-danger disabled tooltip", title=_('Deleting files allowed only when on branch head commit'))}
54 % endif
54 % endif
55 %endif
55 %endif
56
56
57 </div>
57 </div>
58 </div>
58 </div>
59 <div id="file_history_container"></div>
59 <div id="file_history_container"></div>
60
60
61 </div>
61 </div>
62 </div>
62 </div>
63
63
64 <div class="codeblock">
64 <div class="codeblock">
65 <div class=" codeblock-header">
65 <div class=" codeblock-header">
66 <div class="file-filename">
66 <div class="file-filename">
67 <i class="icon-file"></i> ${c.file}
67 <i class="icon-file"></i> ${c.file}
68 </div>
68 </div>
69
69
70 <div class="file-stats">
70 <div class="file-stats">
71
71
72 <div class="stats-info">
72 <div class="stats-info">
73 <span class="stats-first-item">
73 <span class="stats-first-item">
74 % if c.file_size_too_big:
74 % if c.file_size_too_big:
75 0 ${(_('lines'))}
75 0 ${(_('lines'))}
76 % else:
76 % else:
77 ${c.file.lines()[0]} ${_ungettext('line', 'lines', c.file.lines()[0])}
77 ${c.file.lines()[0]} ${_ungettext('line', 'lines', c.file.lines()[0])}
78 % endif
78 % endif
79 </span>
79 </span>
80
80
81 <span> | ${h.format_byte_size_binary(c.file.size)}</span>
81 <span> | ${h.format_byte_size_binary(c.file.size)}</span>
82 % if c.lf_node:
82 % if c.lf_node:
83 <span title="${_('This file is a pointer to large binary file')}"> | ${_('LargeFile')} ${h.format_byte_size_binary(c.lf_node.size)} </span>
83 <span title="${_('This file is a pointer to large binary file')}"> | ${_('LargeFile')} ${h.format_byte_size_binary(c.lf_node.size)} </span>
84 % endif
84 % endif
85 <span>
85 <span>
86 | ${c.file.mimetype}
86 | ${c.file.mimetype}
87 </span>
87 </span>
88
88
89 % if not c.file_size_too_big:
89 % if not c.file_size_too_big:
90 <span> |
90 <span> |
91 ${h.get_lexer_for_filenode(c.file).__class__.__name__}
91 ${h.get_lexer_for_filenode(c.file).__class__.__name__}
92 </span>
92 </span>
93 % endif
93 % endif
94
94
95 </div>
95 </div>
96 </div>
96 </div>
97 </div>
97 </div>
98
98
99 <div class="path clear-fix">
99 <div class="path clear-fix">
100 <div class="pull-left">
100 <div class="pull-left">
101 ${h.files_breadcrumbs(c.repo_name, c.commit.raw_id, c.file.path, c.rhodecode_db_repo.landing_ref_name, request.GET.get('at'))}
101 ${h.files_breadcrumbs(c.repo_name, c.rhodecode_db_repo.repo_type, c.commit.raw_id, c.file.path, c.rhodecode_db_repo.landing_ref_name, request.GET.get('at'))}
102 </div>
102 </div>
103
103
104 <div class="pull-right stats">
104 <div class="pull-right stats">
105 <a id="file_history_overview" href="#loadHistory">
105 <a id="file_history_overview" href="#loadHistory">
106 ${_('History')}
106 ${_('History')}
107 </a>
107 </a>
108 |
108 |
109 %if c.annotate:
109 %if c.annotate:
110 ${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))}
110 ${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))}
111 %else:
111 %else:
112 ${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))}
112 ${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))}
113 %endif
113 %endif
114 | ${h.link_to(_('Raw'), h.route_path('repo_file_raw',repo_name=c.repo_name,commit_id=c.commit.raw_id,f_path=c.f_path))}
114 | ${h.link_to(_('Raw'), h.route_path('repo_file_raw',repo_name=c.repo_name,commit_id=c.commit.raw_id,f_path=c.f_path))}
115 % if not c.file.is_binary:
115 % if not c.file.is_binary:
116 |<a href="#copySource" onclick="return false;" class="no-grey clipboard-action" data-clipboard-text="${c.file.content}">${_('Copy content')}</a>
116 |<a href="#copySource" onclick="return false;" class="no-grey clipboard-action" data-clipboard-text="${c.file.content}">${_('Copy content')}</a>
117 |<a href="#copyPermaLink" onclick="return false;" class="no-grey clipboard-action" data-clipboard-text="${h.route_url('repo_files', repo_name=c.repo_name,commit_id=c.commit.raw_id,f_path=c.f_path)}">${_('Copy permalink')}</a>
117 |<a href="#copyPermaLink" onclick="return false;" class="no-grey clipboard-action" data-clipboard-text="${h.route_url('repo_files', repo_name=c.repo_name,commit_id=c.commit.raw_id,f_path=c.f_path)}">${_('Copy permalink')}</a>
118 % endif
118 % endif
119
119
120 </div>
120 </div>
121 <div class="clear-fix"></div>
121 <div class="clear-fix"></div>
122 </div>
122 </div>
123
123
124 <div class="code-body clear-fix ">
124 <div class="code-body clear-fix ">
125 %if c.file.is_binary:
125 %if c.file.is_binary:
126 <% rendered_binary = h.render_binary(c.repo_name, c.file)%>
126 <% rendered_binary = h.render_binary(c.repo_name, c.file)%>
127 % if rendered_binary:
127 % if rendered_binary:
128 <div class="text-center">
128 <div class="text-center">
129 ${rendered_binary}
129 ${rendered_binary}
130 </div>
130 </div>
131 % else:
131 % else:
132 <div>
132 <div>
133 ${_('Binary file ({})').format(c.file.mimetype)}
133 ${_('Binary file ({})').format(c.file.mimetype)}
134 </div>
134 </div>
135 % endif
135 % endif
136 %else:
136 %else:
137 % if c.file_size_too_big:
137 % if c.file_size_too_big:
138 ${_('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'),
138 ${_('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'),
139 h.route_path('repo_file_raw',repo_name=c.repo_name,commit_id=c.commit.raw_id,f_path=c.f_path))}
139 h.route_path('repo_file_raw',repo_name=c.repo_name,commit_id=c.commit.raw_id,f_path=c.f_path))}
140 % else:
140 % else:
141 %if c.renderer and not c.annotate:
141 %if c.renderer and not c.annotate:
142 ## pick relative url based on renderer
142 ## pick relative url based on renderer
143 <%
143 <%
144 relative_urls = {
144 relative_urls = {
145 'raw': h.route_path('repo_file_raw',repo_name=c.repo_name,commit_id=c.commit.raw_id,f_path=c.f_path),
145 'raw': h.route_path('repo_file_raw',repo_name=c.repo_name,commit_id=c.commit.raw_id,f_path=c.f_path),
146 'standard': h.route_path('repo_files',repo_name=c.repo_name,commit_id=c.commit.raw_id,f_path=c.f_path),
146 'standard': h.route_path('repo_files',repo_name=c.repo_name,commit_id=c.commit.raw_id,f_path=c.f_path),
147 }
147 }
148 %>
148 %>
149 ${h.render(c.file.content, renderer=c.renderer, relative_urls=relative_urls)}
149 ${h.render(c.file.content, renderer=c.renderer, relative_urls=relative_urls)}
150 %else:
150 %else:
151 <table class="cb codehilite">
151 <table class="cb codehilite">
152 %if c.annotate:
152 %if c.annotate:
153 <% color_hasher = h.color_hasher() %>
153 <% color_hasher = h.color_hasher() %>
154 %for annotation, lines in c.annotated_lines:
154 %for annotation, lines in c.annotated_lines:
155 ${sourceblock.render_annotation_lines(annotation, lines, color_hasher)}
155 ${sourceblock.render_annotation_lines(annotation, lines, color_hasher)}
156 %endfor
156 %endfor
157 %else:
157 %else:
158 %for line_num, tokens in enumerate(c.lines, 1):
158 %for line_num, tokens in enumerate(c.lines, 1):
159 ${sourceblock.render_line(line_num, tokens)}
159 ${sourceblock.render_line(line_num, tokens)}
160 %endfor
160 %endfor
161 %endif
161 %endif
162 </table>
162 </table>
163 %endif
163 %endif
164 % endif
164 % endif
165 %endif
165 %endif
166 </div>
166 </div>
167
167
168 </div>
168 </div>
169
169
170 <script type="text/javascript">
170 <script type="text/javascript">
171 % if request.GET.get('mark'):
171 % if request.GET.get('mark'):
172
172
173 $(function(){
173 $(function(){
174 $(".codehilite").mark(
174 $(".codehilite").mark(
175 "${request.GET.get('mark')}",
175 "${request.GET.get('mark')}",
176 {
176 {
177 "className": 'match',
177 "className": 'match',
178 "accuracy": "complementary",
178 "accuracy": "complementary",
179 "ignorePunctuation": ":._(){}[]!'+=".split(""),
179 "ignorePunctuation": ":._(){}[]!'+=".split(""),
180 "each": function(el) {
180 "each": function(el) {
181 // and also highlight lines !
181 // and also highlight lines !
182 $($(el).closest('tr')).find('td.cb-lineno').addClass('cb-line-selected');
182 $($(el).closest('tr')).find('td.cb-lineno').addClass('cb-line-selected');
183 }
183 }
184 }
184 }
185 );
185 );
186
186
187 });
187 });
188 % endif
188 % endif
189 </script>
189 </script>
@@ -1,165 +1,165 b''
1 <%namespace name="search" file="/search/search.mako"/>
1 <%namespace name="search" file="/search/search.mako"/>
2
2
3 <%def name="highlight_text_file(has_matched_content, file_content, lexer, html_formatter, matching_lines, shown_matching_lines, url, use_hl_filter)">
3 <%def name="highlight_text_file(has_matched_content, file_content, lexer, html_formatter, matching_lines, shown_matching_lines, url, use_hl_filter)">
4 % if has_matched_content:
4 % if has_matched_content:
5 ${h.code_highlight(file_content, lexer, html_formatter, use_hl_filter=use_hl_filter)|n}
5 ${h.code_highlight(file_content, lexer, html_formatter, use_hl_filter=use_hl_filter)|n}
6 % else:
6 % else:
7 ${_('No content matched')} <br/>
7 ${_('No content matched')} <br/>
8 % endif
8 % endif
9
9
10 %if len(matching_lines) > shown_matching_lines:
10 %if len(matching_lines) > shown_matching_lines:
11 <a href="${url}">
11 <a href="${url}">
12 ${len(matching_lines) - shown_matching_lines} ${_('more matches in this file')}
12 ${len(matching_lines) - shown_matching_lines} ${_('more matches in this file')}
13 </a>
13 </a>
14 %endif
14 %endif
15 </%def>
15 </%def>
16
16
17 <div class="search-results">
17 <div class="search-results">
18 <% query_mark = c.searcher.query_to_mark(c.cur_query, 'content') %>
18 <% query_mark = c.searcher.query_to_mark(c.cur_query, 'content') %>
19
19
20 %for entry in c.formatted_results:
20 %for entry in c.formatted_results:
21
21
22 <%
22 <%
23 file_content = entry['content_highlight'] or entry['content']
23 file_content = entry['content_highlight'] or entry['content']
24 mimetype = entry.get('mimetype')
24 mimetype = entry.get('mimetype')
25 filepath = entry.get('path')
25 filepath = entry.get('path')
26 max_lines = h.safe_int(request.GET.get('max_lines', '10'))
26 max_lines = h.safe_int(request.GET.get('max_lines', '10'))
27 line_context = h.safe_int(request.GET.get('line_contenxt', '3'))
27 line_context = h.safe_int(request.GET.get('line_contenxt', '3'))
28
28
29 match_file_url=h.route_path('repo_files',repo_name=entry['repository'], commit_id=entry.get('commit_id', 'tip'),f_path=entry['f_path'], _query={"mark": query_mark})
29 match_file_url=h.route_path('repo_files',repo_name=entry['repository'], commit_id=entry.get('commit_id', 'tip'),f_path=entry['f_path'], _query={"mark": query_mark})
30 terms = c.cur_query
30 terms = c.cur_query
31
31
32 if c.searcher.is_es_6:
32 if c.searcher.is_es_6:
33 # use empty terms so we default to markers usage
33 # use empty terms so we default to markers usage
34 total_lines, matching_lines = h.get_matching_line_offsets(file_content, terms=None)
34 total_lines, matching_lines = h.get_matching_line_offsets(file_content, terms=None)
35 else:
35 else:
36 total_lines, matching_lines = h.get_matching_line_offsets(file_content, terms)
36 total_lines, matching_lines = h.get_matching_line_offsets(file_content, terms)
37
37
38 shown_matching_lines = 0
38 shown_matching_lines = 0
39 lines_of_interest = set()
39 lines_of_interest = set()
40 for line_number in matching_lines:
40 for line_number in matching_lines:
41 if len(lines_of_interest) < max_lines:
41 if len(lines_of_interest) < max_lines:
42 lines_of_interest |= set(range(
42 lines_of_interest |= set(range(
43 max(line_number - line_context, 0),
43 max(line_number - line_context, 0),
44 min(line_number + line_context, total_lines + 1)))
44 min(line_number + line_context, total_lines + 1)))
45 shown_matching_lines += 1
45 shown_matching_lines += 1
46 lexer = h.get_lexer_safe(mimetype=mimetype, filepath=filepath)
46 lexer = h.get_lexer_safe(mimetype=mimetype, filepath=filepath)
47
47
48 html_formatter = h.SearchContentCodeHtmlFormatter(
48 html_formatter = h.SearchContentCodeHtmlFormatter(
49 linenos=True,
49 linenos=True,
50 cssclass="code-highlight",
50 cssclass="code-highlight",
51 url=match_file_url,
51 url=match_file_url,
52 query_terms=terms,
52 query_terms=terms,
53 only_line_numbers=lines_of_interest
53 only_line_numbers=lines_of_interest
54 )
54 )
55
55
56 has_matched_content = len(lines_of_interest) >= 1
56 has_matched_content = len(lines_of_interest) >= 1
57
57
58 %>
58 %>
59 ## search results are additionally filtered, and this check is just a safe gate
59 ## search results are additionally filtered, and this check is just a safe gate
60 % if c.rhodecode_user.is_admin or h.HasRepoPermissionAny('repository.write','repository.read','repository.admin')(entry['repository'], 'search results content check'):
60 % if c.rhodecode_user.is_admin or h.HasRepoPermissionAny('repository.write','repository.read','repository.admin')(entry['repository'], 'search results content check'):
61 <div class="codeblock">
61 <div class="codeblock">
62 <h1>
62 <h1>
63 <% repo_type = entry.get('repo_type') or h.get_repo_type_by_name(entry.get('repository')) %>
63 <% repo_type = entry.get('repo_type') or h.get_repo_type_by_name(entry.get('repository')) %>
64 ${search.repo_icon(repo_type)}
64 ${search.repo_icon(repo_type)}
65 ${h.link_to(entry['repository'], h.route_path('repo_summary', repo_name=entry['repository']))}
65 ${h.link_to(entry['repository'], h.route_path('repo_summary', repo_name=entry['repository']))}
66 </h1>
66 </h1>
67
67
68 <div class="codeblock-header">
68 <div class="codeblock-header">
69
69
70 <div class="file-filename">
70 <div class="file-filename">
71 <i class="icon-file"></i> ${entry['f_path'].split('/')[-1]}
71 <i class="icon-file"></i> ${entry['f_path'].split('/')[-1]}
72 </div>
72 </div>
73
73
74 <div class="file-stats">
74 <div class="file-stats">
75 <div class="stats-info">
75 <div class="stats-info">
76 <span class="stats-first-item">
76 <span class="stats-first-item">
77 ${entry.get('lines', 0.)} ${_ungettext('line', 'lines', entry.get('lines', 0.))}
77 ${entry.get('lines', 0.)} ${_ungettext('line', 'lines', entry.get('lines', 0.))}
78 (${len(matching_lines)} ${_ungettext('matched', 'matched', len(matching_lines))})
78 (${len(matching_lines)} ${_ungettext('matched', 'matched', len(matching_lines))})
79 </span>
79 </span>
80 <span>
80 <span>
81 % if entry.get('size'):
81 % if entry.get('size'):
82 | ${h.format_byte_size_binary(entry['size'])}
82 | ${h.format_byte_size_binary(entry['size'])}
83 % endif
83 % endif
84 </span>
84 </span>
85 <span>
85 <span>
86 % if entry.get('mimetype'):
86 % if entry.get('mimetype'):
87 | ${entry.get('mimetype', "unknown mimetype")}
87 | ${entry.get('mimetype', "unknown mimetype")}
88 % endif
88 % endif
89 </span>
89 </span>
90 </div>
90 </div>
91 </div>
91 </div>
92 </div>
92 </div>
93
93
94 <div class="path clear-fix">
94 <div class="path clear-fix">
95 <div class="pull-left">
95 <div class="pull-left">
96 ${h.files_breadcrumbs(entry['repository'], entry.get('commit_id', 'tip'), entry['f_path'], linkify_last_item=True)}
96 ${h.files_breadcrumbs(entry['repository'], repo_type, entry.get('commit_id', 'tip'), entry['f_path'], linkify_last_item=True)}
97 </div>
97 </div>
98
98
99 <div class="pull-right stats">
99 <div class="pull-right stats">
100 ## <a id="file_history_overview_full" href="${h.route_path('repo_commits_file',repo_name=entry.get('repository',''),commit_id=entry.get('commit_id', 'tip'),f_path=entry.get('f_path',''))}">
100 ## <a id="file_history_overview_full" href="${h.route_path('repo_commits_file',repo_name=entry.get('repository',''),commit_id=entry.get('commit_id', 'tip'),f_path=entry.get('f_path',''))}">
101 ## ${_('Show Full History')}
101 ## ${_('Show Full History')}
102 ## </a>
102 ## </a>
103 ## | ${h.link_to(_('Annotation'), h.route_path('repo_files:annotated', repo_name=entry.get('repository',''),commit_id=entry.get('commit_id', 'tip'),f_path=entry.get('f_path','')))}
103 ## | ${h.link_to(_('Annotation'), h.route_path('repo_files:annotated', repo_name=entry.get('repository',''),commit_id=entry.get('commit_id', 'tip'),f_path=entry.get('f_path','')))}
104 ## | ${h.link_to(_('Raw'), h.route_path('repo_file_raw', repo_name=entry.get('repository',''),commit_id=entry.get('commit_id', 'tip'),f_path=entry.get('f_path','')))}
104 ## | ${h.link_to(_('Raw'), h.route_path('repo_file_raw', repo_name=entry.get('repository',''),commit_id=entry.get('commit_id', 'tip'),f_path=entry.get('f_path','')))}
105 <div class="search-tags">
105 <div class="search-tags">
106
106
107 <% repo_group = entry.get('repository_group')%>
107 <% repo_group = entry.get('repository_group')%>
108 ## hiden if in repo group view
108 ## hiden if in repo group view
109 % if repo_group and not c.repo_group_name:
109 % if repo_group and not c.repo_group_name:
110 <span class="tag tag8">
110 <span class="tag tag8">
111 ${search.repo_group_icon()}
111 ${search.repo_group_icon()}
112 <a href="${h.route_path('search_repo_group', repo_group_name=repo_group, _query={'q': c.cur_query})}">${_('Narrow to this repository group')}</a>
112 <a href="${h.route_path('search_repo_group', repo_group_name=repo_group, _query={'q': c.cur_query})}">${_('Narrow to this repository group')}</a>
113 </span>
113 </span>
114 % endif
114 % endif
115 ## hiden if in repo view
115 ## hiden if in repo view
116 % if not c.repo_name:
116 % if not c.repo_name:
117 <span class="tag tag8">
117 <span class="tag tag8">
118 ${search.repo_icon(repo_type)}
118 ${search.repo_icon(repo_type)}
119 <a href="${h.route_path('search_repo', repo_name=entry.get('repo_name'), _query={'q': c.cur_query})}">${_('Narrow to this repository')}</a>
119 <a href="${h.route_path('search_repo', repo_name=entry.get('repo_name'), _query={'q': c.cur_query})}">${_('Narrow to this repository')}</a>
120 </span>
120 </span>
121 % endif
121 % endif
122 </div>
122 </div>
123
123
124 </div>
124 </div>
125 <div class="clear-fix"></div>
125 <div class="clear-fix"></div>
126 </div>
126 </div>
127
127
128
128
129 <div class="code-body search-code-body clear-fix">
129 <div class="code-body search-code-body clear-fix">
130 ${highlight_text_file(
130 ${highlight_text_file(
131 has_matched_content=has_matched_content,
131 has_matched_content=has_matched_content,
132 file_content=file_content,
132 file_content=file_content,
133 lexer=lexer,
133 lexer=lexer,
134 html_formatter=html_formatter,
134 html_formatter=html_formatter,
135 matching_lines=matching_lines,
135 matching_lines=matching_lines,
136 shown_matching_lines=shown_matching_lines,
136 shown_matching_lines=shown_matching_lines,
137 url=match_file_url,
137 url=match_file_url,
138 use_hl_filter=c.searcher.is_es_6
138 use_hl_filter=c.searcher.is_es_6
139 )}
139 )}
140 </div>
140 </div>
141
141
142 </div>
142 </div>
143 % endif
143 % endif
144 %endfor
144 %endfor
145 </div>
145 </div>
146 %if c.cur_query and c.formatted_results:
146 %if c.cur_query and c.formatted_results:
147 <div class="pagination-wh pagination-left" >
147 <div class="pagination-wh pagination-left" >
148 ${c.formatted_results.render()}
148 ${c.formatted_results.render()}
149 </div>
149 </div>
150 %endif
150 %endif
151
151
152 %if c.cur_query:
152 %if c.cur_query:
153 <script type="text/javascript">
153 <script type="text/javascript">
154 $(function(){
154 $(function(){
155 $(".search-code-body").mark(
155 $(".search-code-body").mark(
156 "${query_mark}",
156 "${query_mark}",
157 {
157 {
158 "className": 'match',
158 "className": 'match',
159 "accuracy": "complementary",
159 "accuracy": "complementary",
160 "ignorePunctuation": ":._(){}[]!'+=".split("")
160 "ignorePunctuation": ":._(){}[]!'+=".split("")
161 }
161 }
162 );
162 );
163 })
163 })
164 </script>
164 </script>
165 %endif
165 %endif
General Comments 0
You need to be logged in to leave comments. Login now