##// END OF EJS Templates
files: render readme files found in repository file browser....
marcink -
r3924:0de274d4 default
parent child Browse files
Show More
@@ -1,739 +1,800 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2019 RhodeCode GmbH
3 # Copyright (C) 2016-2019 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import 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
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
32 from rhodecode.lib.vcs.backends.base import EmptyCommit
31 from rhodecode.lib.vcs.exceptions import RepositoryRequirementError
33 from rhodecode.lib.vcs.exceptions import RepositoryRequirementError
32 from rhodecode.model import repo
34 from rhodecode.model import repo
33 from rhodecode.model import repo_group
35 from rhodecode.model import repo_group
34 from rhodecode.model import user_group
36 from rhodecode.model import user_group
35 from rhodecode.model import user
37 from rhodecode.model import user
36 from rhodecode.model.db import User
38 from rhodecode.model.db import User
37 from rhodecode.model.scm import ScmModel
39 from rhodecode.model.scm import ScmModel
38 from rhodecode.model.settings import VcsSettingsModel
40 from rhodecode.model.settings import VcsSettingsModel
41 from rhodecode.model.repo import ReadmeFinder
39
42
40 log = logging.getLogger(__name__)
43 log = logging.getLogger(__name__)
41
44
42
45
43 ADMIN_PREFIX = '/_admin'
46 ADMIN_PREFIX = '/_admin'
44 STATIC_FILE_PREFIX = '/_static'
47 STATIC_FILE_PREFIX = '/_static'
45
48
46 URL_NAME_REQUIREMENTS = {
49 URL_NAME_REQUIREMENTS = {
47 # 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
48 'group_name': r'.*?[^/]',
51 'group_name': r'.*?[^/]',
49 'repo_group_name': r'.*?[^/]',
52 'repo_group_name': r'.*?[^/]',
50 # 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
51 'repo_name': r'.*?[^/]',
54 'repo_name': r'.*?[^/]',
52 # file path eats up everything at the end
55 # file path eats up everything at the end
53 'f_path': r'.*',
56 'f_path': r'.*',
54 # reference types
57 # reference types
55 'source_ref_type': '(branch|book|tag|rev|\%\(source_ref_type\)s)',
58 'source_ref_type': '(branch|book|tag|rev|\%\(source_ref_type\)s)',
56 'target_ref_type': '(branch|book|tag|rev|\%\(target_ref_type\)s)',
59 'target_ref_type': '(branch|book|tag|rev|\%\(target_ref_type\)s)',
57 }
60 }
58
61
59
62
60 def add_route_with_slash(config,name, pattern, **kw):
63 def add_route_with_slash(config,name, pattern, **kw):
61 config.add_route(name, pattern, **kw)
64 config.add_route(name, pattern, **kw)
62 if not pattern.endswith('/'):
65 if not pattern.endswith('/'):
63 config.add_route(name + '_slash', pattern + '/', **kw)
66 config.add_route(name + '_slash', pattern + '/', **kw)
64
67
65
68
66 def add_route_requirements(route_path, requirements=None):
69 def add_route_requirements(route_path, requirements=None):
67 """
70 """
68 Adds regex requirements to pyramid routes using a mapping dict
71 Adds regex requirements to pyramid routes using a mapping dict
69 e.g::
72 e.g::
70 add_route_requirements('{repo_name}/settings')
73 add_route_requirements('{repo_name}/settings')
71 """
74 """
72 requirements = requirements or URL_NAME_REQUIREMENTS
75 requirements = requirements or URL_NAME_REQUIREMENTS
73 for key, regex in requirements.items():
76 for key, regex in requirements.items():
74 route_path = route_path.replace('{%s}' % key, '{%s:%s}' % (key, regex))
77 route_path = route_path.replace('{%s}' % key, '{%s:%s}' % (key, regex))
75 return route_path
78 return route_path
76
79
77
80
78 def get_format_ref_id(repo):
81 def get_format_ref_id(repo):
79 """Returns a `repo` specific reference formatter function"""
82 """Returns a `repo` specific reference formatter function"""
80 if h.is_svn(repo):
83 if h.is_svn(repo):
81 return _format_ref_id_svn
84 return _format_ref_id_svn
82 else:
85 else:
83 return _format_ref_id
86 return _format_ref_id
84
87
85
88
86 def _format_ref_id(name, raw_id):
89 def _format_ref_id(name, raw_id):
87 """Default formatting of a given reference `name`"""
90 """Default formatting of a given reference `name`"""
88 return name
91 return name
89
92
90
93
91 def _format_ref_id_svn(name, raw_id):
94 def _format_ref_id_svn(name, raw_id):
92 """Special way of formatting a reference for Subversion including path"""
95 """Special way of formatting a reference for Subversion including path"""
93 return '%s@%s' % (name, raw_id)
96 return '%s@%s' % (name, raw_id)
94
97
95
98
96 class TemplateArgs(StrictAttributeDict):
99 class TemplateArgs(StrictAttributeDict):
97 pass
100 pass
98
101
99
102
100 class BaseAppView(object):
103 class BaseAppView(object):
101
104
102 def __init__(self, context, request):
105 def __init__(self, context, request):
103 self.request = request
106 self.request = request
104 self.context = context
107 self.context = context
105 self.session = request.session
108 self.session = request.session
106 if not hasattr(request, 'user'):
109 if not hasattr(request, 'user'):
107 # NOTE(marcink): edge case, we ended up in matched route
110 # NOTE(marcink): edge case, we ended up in matched route
108 # 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
109 if hasattr(request, 'vcs_call') or hasattr(request, 'rpc_method'):
112 if hasattr(request, 'vcs_call') or hasattr(request, 'rpc_method'):
110 log.warning('Unable to process request `%s` in this scope', request)
113 log.warning('Unable to process request `%s` in this scope', request)
111 raise HTTPBadRequest()
114 raise HTTPBadRequest()
112
115
113 self._rhodecode_user = request.user # auth user
116 self._rhodecode_user = request.user # auth user
114 self._rhodecode_db_user = self._rhodecode_user.get_instance()
117 self._rhodecode_db_user = self._rhodecode_user.get_instance()
115 self._maybe_needs_password_change(
118 self._maybe_needs_password_change(
116 request.matched_route.name, self._rhodecode_db_user)
119 request.matched_route.name, self._rhodecode_db_user)
117
120
118 def _maybe_needs_password_change(self, view_name, user_obj):
121 def _maybe_needs_password_change(self, view_name, user_obj):
119 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',
120 user_obj, view_name)
123 user_obj, view_name)
121 skip_user_views = [
124 skip_user_views = [
122 'logout', 'login',
125 'logout', 'login',
123 'my_account_password', 'my_account_password_update'
126 'my_account_password', 'my_account_password_update'
124 ]
127 ]
125
128
126 if not user_obj:
129 if not user_obj:
127 return
130 return
128
131
129 if user_obj.username == User.DEFAULT_USER:
132 if user_obj.username == User.DEFAULT_USER:
130 return
133 return
131
134
132 now = time.time()
135 now = time.time()
133 should_change = user_obj.user_data.get('force_password_change')
136 should_change = user_obj.user_data.get('force_password_change')
134 change_after = safe_int(should_change) or 0
137 change_after = safe_int(should_change) or 0
135 if should_change and now > change_after:
138 if should_change and now > change_after:
136 log.debug('User %s requires password change', user_obj)
139 log.debug('User %s requires password change', user_obj)
137 h.flash('You are required to change your password', 'warning',
140 h.flash('You are required to change your password', 'warning',
138 ignore_duplicate=True)
141 ignore_duplicate=True)
139
142
140 if view_name not in skip_user_views:
143 if view_name not in skip_user_views:
141 raise HTTPFound(
144 raise HTTPFound(
142 self.request.route_path('my_account_password'))
145 self.request.route_path('my_account_password'))
143
146
144 def _log_creation_exception(self, e, repo_name):
147 def _log_creation_exception(self, e, repo_name):
145 _ = self.request.translate
148 _ = self.request.translate
146 reason = None
149 reason = None
147 if len(e.args) == 2:
150 if len(e.args) == 2:
148 reason = e.args[1]
151 reason = e.args[1]
149
152
150 if reason == 'INVALID_CERTIFICATE':
153 if reason == 'INVALID_CERTIFICATE':
151 log.exception(
154 log.exception(
152 'Exception creating a repository: invalid certificate')
155 'Exception creating a repository: invalid certificate')
153 msg = (_('Error creating repository %s: invalid certificate')
156 msg = (_('Error creating repository %s: invalid certificate')
154 % repo_name)
157 % repo_name)
155 else:
158 else:
156 log.exception("Exception creating a repository")
159 log.exception("Exception creating a repository")
157 msg = (_('Error creating repository %s')
160 msg = (_('Error creating repository %s')
158 % repo_name)
161 % repo_name)
159 return msg
162 return msg
160
163
161 def _get_local_tmpl_context(self, include_app_defaults=True):
164 def _get_local_tmpl_context(self, include_app_defaults=True):
162 c = TemplateArgs()
165 c = TemplateArgs()
163 c.auth_user = self.request.user
166 c.auth_user = self.request.user
164 # 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
165 c.rhodecode_user = self.request.user
168 c.rhodecode_user = self.request.user
166
169
167 if include_app_defaults:
170 if include_app_defaults:
168 from rhodecode.lib.base import attach_context_attributes
171 from rhodecode.lib.base import attach_context_attributes
169 attach_context_attributes(c, self.request, self.request.user.user_id)
172 attach_context_attributes(c, self.request, self.request.user.user_id)
170
173
171 c.is_super_admin = c.auth_user.is_admin
174 c.is_super_admin = c.auth_user.is_admin
172
175
173 c.can_create_repo = c.is_super_admin
176 c.can_create_repo = c.is_super_admin
174 c.can_create_repo_group = c.is_super_admin
177 c.can_create_repo_group = c.is_super_admin
175 c.can_create_user_group = c.is_super_admin
178 c.can_create_user_group = c.is_super_admin
176
179
177 c.is_delegated_admin = False
180 c.is_delegated_admin = False
178
181
179 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:
180 c.can_create_repo = h.HasPermissionAny('hg.create.repository')(
183 c.can_create_repo = h.HasPermissionAny('hg.create.repository')(
181 user=self.request.user)
184 user=self.request.user)
182 repositories = c.auth_user.repositories_admin or c.can_create_repo
185 repositories = c.auth_user.repositories_admin or c.can_create_repo
183
186
184 c.can_create_repo_group = h.HasPermissionAny('hg.repogroup.create.true')(
187 c.can_create_repo_group = h.HasPermissionAny('hg.repogroup.create.true')(
185 user=self.request.user)
188 user=self.request.user)
186 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
187
190
188 c.can_create_user_group = h.HasPermissionAny('hg.usergroup.create.true')(
191 c.can_create_user_group = h.HasPermissionAny('hg.usergroup.create.true')(
189 user=self.request.user)
192 user=self.request.user)
190 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
191 # delegated admin can create, or manage some objects
194 # delegated admin can create, or manage some objects
192 c.is_delegated_admin = repositories or repository_groups or user_groups
195 c.is_delegated_admin = repositories or repository_groups or user_groups
193 return c
196 return c
194
197
195 def _get_template_context(self, tmpl_args, **kwargs):
198 def _get_template_context(self, tmpl_args, **kwargs):
196
199
197 local_tmpl_args = {
200 local_tmpl_args = {
198 'defaults': {},
201 'defaults': {},
199 'errors': {},
202 'errors': {},
200 'c': tmpl_args
203 'c': tmpl_args
201 }
204 }
202 local_tmpl_args.update(kwargs)
205 local_tmpl_args.update(kwargs)
203 return local_tmpl_args
206 return local_tmpl_args
204
207
205 def load_default_context(self):
208 def load_default_context(self):
206 """
209 """
207 example:
210 example:
208
211
209 def load_default_context(self):
212 def load_default_context(self):
210 c = self._get_local_tmpl_context()
213 c = self._get_local_tmpl_context()
211 c.custom_var = 'foobar'
214 c.custom_var = 'foobar'
212
215
213 return c
216 return c
214 """
217 """
215 raise NotImplementedError('Needs implementation in view class')
218 raise NotImplementedError('Needs implementation in view class')
216
219
217
220
218 class RepoAppView(BaseAppView):
221 class RepoAppView(BaseAppView):
219
222
220 def __init__(self, context, request):
223 def __init__(self, context, request):
221 super(RepoAppView, self).__init__(context, request)
224 super(RepoAppView, self).__init__(context, request)
222 self.db_repo = request.db_repo
225 self.db_repo = request.db_repo
223 self.db_repo_name = self.db_repo.repo_name
226 self.db_repo_name = self.db_repo.repo_name
224 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)
225
228
226 def _handle_missing_requirements(self, error):
229 def _handle_missing_requirements(self, error):
227 log.error(
230 log.error(
228 'Requirements are missing for repository %s: %s',
231 'Requirements are missing for repository %s: %s',
229 self.db_repo_name, safe_unicode(error))
232 self.db_repo_name, safe_unicode(error))
230
233
231 def _get_local_tmpl_context(self, include_app_defaults=True):
234 def _get_local_tmpl_context(self, include_app_defaults=True):
232 _ = self.request.translate
235 _ = self.request.translate
233 c = super(RepoAppView, self)._get_local_tmpl_context(
236 c = super(RepoAppView, self)._get_local_tmpl_context(
234 include_app_defaults=include_app_defaults)
237 include_app_defaults=include_app_defaults)
235
238
236 # register common vars for this type of view
239 # register common vars for this type of view
237 c.rhodecode_db_repo = self.db_repo
240 c.rhodecode_db_repo = self.db_repo
238 c.repo_name = self.db_repo_name
241 c.repo_name = self.db_repo_name
239 c.repository_pull_requests = self.db_repo_pull_requests
242 c.repository_pull_requests = self.db_repo_pull_requests
240 c.repository_is_user_following = ScmModel().is_following_repo(
243 c.repository_is_user_following = ScmModel().is_following_repo(
241 self.db_repo_name, self._rhodecode_user.user_id)
244 self.db_repo_name, self._rhodecode_user.user_id)
242 self.path_filter = PathFilter(None)
245 self.path_filter = PathFilter(None)
243
246
244 c.repository_requirements_missing = {}
247 c.repository_requirements_missing = {}
245 try:
248 try:
246 self.rhodecode_vcs_repo = self.db_repo.scm_instance()
249 self.rhodecode_vcs_repo = self.db_repo.scm_instance()
247 # NOTE(marcink):
250 # NOTE(marcink):
248 # comparison to None since if it's an object __bool__ is expensive to
251 # comparison to None since if it's an object __bool__ is expensive to
249 # calculate
252 # calculate
250 if self.rhodecode_vcs_repo is not None:
253 if self.rhodecode_vcs_repo is not None:
251 path_perms = self.rhodecode_vcs_repo.get_path_permissions(
254 path_perms = self.rhodecode_vcs_repo.get_path_permissions(
252 c.auth_user.username)
255 c.auth_user.username)
253 self.path_filter = PathFilter(path_perms)
256 self.path_filter = PathFilter(path_perms)
254 except RepositoryRequirementError as e:
257 except RepositoryRequirementError as e:
255 c.repository_requirements_missing = {'error': str(e)}
258 c.repository_requirements_missing = {'error': str(e)}
256 self._handle_missing_requirements(e)
259 self._handle_missing_requirements(e)
257 self.rhodecode_vcs_repo = None
260 self.rhodecode_vcs_repo = None
258
261
259 c.path_filter = self.path_filter # used by atom_feed_entry.mako
262 c.path_filter = self.path_filter # used by atom_feed_entry.mako
260
263
261 if self.rhodecode_vcs_repo is None:
264 if self.rhodecode_vcs_repo is None:
262 # unable to fetch this repo as vcs instance, report back to user
265 # unable to fetch this repo as vcs instance, report back to user
263 h.flash(_(
266 h.flash(_(
264 "The repository `%(repo_name)s` cannot be loaded in filesystem. "
267 "The repository `%(repo_name)s` cannot be loaded in filesystem. "
265 "Please check if it exist, or is not damaged.") %
268 "Please check if it exist, or is not damaged.") %
266 {'repo_name': c.repo_name},
269 {'repo_name': c.repo_name},
267 category='error', ignore_duplicate=True)
270 category='error', ignore_duplicate=True)
268 if c.repository_requirements_missing:
271 if c.repository_requirements_missing:
269 route = self.request.matched_route.name
272 route = self.request.matched_route.name
270 if route.startswith(('edit_repo', 'repo_summary')):
273 if route.startswith(('edit_repo', 'repo_summary')):
271 # allow summary and edit repo on missing requirements
274 # allow summary and edit repo on missing requirements
272 return c
275 return c
273
276
274 raise HTTPFound(
277 raise HTTPFound(
275 h.route_path('repo_summary', repo_name=self.db_repo_name))
278 h.route_path('repo_summary', repo_name=self.db_repo_name))
276
279
277 else: # redirect if we don't show missing requirements
280 else: # redirect if we don't show missing requirements
278 raise HTTPFound(h.route_path('home'))
281 raise HTTPFound(h.route_path('home'))
279
282
280 c.has_origin_repo_read_perm = False
283 c.has_origin_repo_read_perm = False
281 if self.db_repo.fork:
284 if self.db_repo.fork:
282 c.has_origin_repo_read_perm = h.HasRepoPermissionAny(
285 c.has_origin_repo_read_perm = h.HasRepoPermissionAny(
283 'repository.write', 'repository.read', 'repository.admin')(
286 'repository.write', 'repository.read', 'repository.admin')(
284 self.db_repo.fork.repo_name, 'summary fork link')
287 self.db_repo.fork.repo_name, 'summary fork link')
285
288
286 return c
289 return c
287
290
288 def _get_f_path_unchecked(self, matchdict, default=None):
291 def _get_f_path_unchecked(self, matchdict, default=None):
289 """
292 """
290 Should only be used by redirects, everything else should call _get_f_path
293 Should only be used by redirects, everything else should call _get_f_path
291 """
294 """
292 f_path = matchdict.get('f_path')
295 f_path = matchdict.get('f_path')
293 if f_path:
296 if f_path:
294 # fix for multiple initial slashes that causes errors for GIT
297 # fix for multiple initial slashes that causes errors for GIT
295 return f_path.lstrip('/')
298 return f_path.lstrip('/')
296
299
297 return default
300 return default
298
301
299 def _get_f_path(self, matchdict, default=None):
302 def _get_f_path(self, matchdict, default=None):
300 f_path_match = self._get_f_path_unchecked(matchdict, default)
303 f_path_match = self._get_f_path_unchecked(matchdict, default)
301 return self.path_filter.assert_path_permissions(f_path_match)
304 return self.path_filter.assert_path_permissions(f_path_match)
302
305
303 def _get_general_setting(self, target_repo, settings_key, default=False):
306 def _get_general_setting(self, target_repo, settings_key, default=False):
304 settings_model = VcsSettingsModel(repo=target_repo)
307 settings_model = VcsSettingsModel(repo=target_repo)
305 settings = settings_model.get_general_settings()
308 settings = settings_model.get_general_settings()
306 return settings.get(settings_key, default)
309 return settings.get(settings_key, default)
307
310
308 def _get_repo_setting(self, target_repo, settings_key, default=False):
311 def _get_repo_setting(self, target_repo, settings_key, default=False):
309 settings_model = VcsSettingsModel(repo=target_repo)
312 settings_model = VcsSettingsModel(repo=target_repo)
310 settings = settings_model.get_repo_settings_inherited()
313 settings = settings_model.get_repo_settings_inherited()
311 return settings.get(settings_key, default)
314 return settings.get(settings_key, default)
312
315
316 def _get_readme_data(self, db_repo, renderer_type, commit_id=None, path='/'):
317 log.debug('Looking for README file at path %s', path)
318 if commit_id:
319 landing_commit_id = commit_id
320 else:
321 landing_commit = db_repo.get_landing_commit()
322 if isinstance(landing_commit, EmptyCommit):
323 return None, None
324 landing_commit_id = landing_commit.raw_id
325
326 cache_namespace_uid = 'cache_repo.{}'.format(db_repo.repo_id)
327 region = rc_cache.get_or_create_region('cache_repo', cache_namespace_uid)
328 start = time.time()
329
330 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid)
331 def generate_repo_readme(repo_id, _commit_id, _repo_name, _readme_search_path, _renderer_type):
332 readme_data = None
333 readme_filename = None
334
335 commit = db_repo.get_commit(_commit_id)
336 log.debug("Searching for a README file at commit %s.", _commit_id)
337 readme_node = ReadmeFinder(_renderer_type).search(commit, path=_readme_search_path)
338
339 if readme_node:
340 log.debug('Found README node: %s', readme_node)
341 relative_urls = {
342 'raw': h.route_path(
343 'repo_file_raw', repo_name=_repo_name,
344 commit_id=commit.raw_id, f_path=readme_node.path),
345 'standard': h.route_path(
346 'repo_files', repo_name=_repo_name,
347 commit_id=commit.raw_id, f_path=readme_node.path),
348 }
349 readme_data = self._render_readme_or_none(commit, readme_node, relative_urls)
350 readme_filename = readme_node.unicode_path
351
352 return readme_data, readme_filename
353
354 readme_data, readme_filename = generate_repo_readme(
355 db_repo.repo_id, landing_commit_id, db_repo.repo_name, path, renderer_type,)
356 compute_time = time.time() - start
357 log.debug('Repo README for path %s generated and computed in %.4fs',
358 path, compute_time)
359 return readme_data, readme_filename
360
361 def _render_readme_or_none(self, commit, readme_node, relative_urls):
362 log.debug('Found README file `%s` rendering...', readme_node.path)
363 renderer = MarkupRenderer()
364 try:
365 html_source = renderer.render(
366 readme_node.content, filename=readme_node.path)
367 if relative_urls:
368 return relative_links(html_source, relative_urls)
369 return html_source
370 except Exception:
371 log.exception(
372 "Exception while trying to render the README")
373
313 def get_recache_flag(self):
374 def get_recache_flag(self):
314 for flag_name in ['force_recache', 'force-recache', 'no-cache']:
375 for flag_name in ['force_recache', 'force-recache', 'no-cache']:
315 flag_val = self.request.GET.get(flag_name)
376 flag_val = self.request.GET.get(flag_name)
316 if str2bool(flag_val):
377 if str2bool(flag_val):
317 return True
378 return True
318 return False
379 return False
319
380
320
381
321 class PathFilter(object):
382 class PathFilter(object):
322
383
323 # Expects and instance of BasePathPermissionChecker or None
384 # Expects and instance of BasePathPermissionChecker or None
324 def __init__(self, permission_checker):
385 def __init__(self, permission_checker):
325 self.permission_checker = permission_checker
386 self.permission_checker = permission_checker
326
387
327 def assert_path_permissions(self, path):
388 def assert_path_permissions(self, path):
328 if self.path_access_allowed(path):
389 if self.path_access_allowed(path):
329 return path
390 return path
330 raise HTTPForbidden()
391 raise HTTPForbidden()
331
392
332 def path_access_allowed(self, path):
393 def path_access_allowed(self, path):
333 log.debug('Checking ACL permissions for PathFilter for `%s`', path)
394 log.debug('Checking ACL permissions for PathFilter for `%s`', path)
334 if self.permission_checker:
395 if self.permission_checker:
335 return path and self.permission_checker.has_access(path)
396 return path and self.permission_checker.has_access(path)
336 return True
397 return True
337
398
338 def filter_patchset(self, patchset):
399 def filter_patchset(self, patchset):
339 if not self.permission_checker or not patchset:
400 if not self.permission_checker or not patchset:
340 return patchset, False
401 return patchset, False
341 had_filtered = False
402 had_filtered = False
342 filtered_patchset = []
403 filtered_patchset = []
343 for patch in patchset:
404 for patch in patchset:
344 filename = patch.get('filename', None)
405 filename = patch.get('filename', None)
345 if not filename or self.permission_checker.has_access(filename):
406 if not filename or self.permission_checker.has_access(filename):
346 filtered_patchset.append(patch)
407 filtered_patchset.append(patch)
347 else:
408 else:
348 had_filtered = True
409 had_filtered = True
349 if had_filtered:
410 if had_filtered:
350 if isinstance(patchset, diffs.LimitedDiffContainer):
411 if isinstance(patchset, diffs.LimitedDiffContainer):
351 filtered_patchset = diffs.LimitedDiffContainer(patchset.diff_limit, patchset.cur_diff_size, filtered_patchset)
412 filtered_patchset = diffs.LimitedDiffContainer(patchset.diff_limit, patchset.cur_diff_size, filtered_patchset)
352 return filtered_patchset, True
413 return filtered_patchset, True
353 else:
414 else:
354 return patchset, False
415 return patchset, False
355
416
356 def render_patchset_filtered(self, diffset, patchset, source_ref=None, target_ref=None):
417 def render_patchset_filtered(self, diffset, patchset, source_ref=None, target_ref=None):
357 filtered_patchset, has_hidden_changes = self.filter_patchset(patchset)
418 filtered_patchset, has_hidden_changes = self.filter_patchset(patchset)
358 result = diffset.render_patchset(
419 result = diffset.render_patchset(
359 filtered_patchset, source_ref=source_ref, target_ref=target_ref)
420 filtered_patchset, source_ref=source_ref, target_ref=target_ref)
360 result.has_hidden_changes = has_hidden_changes
421 result.has_hidden_changes = has_hidden_changes
361 return result
422 return result
362
423
363 def get_raw_patch(self, diff_processor):
424 def get_raw_patch(self, diff_processor):
364 if self.permission_checker is None:
425 if self.permission_checker is None:
365 return diff_processor.as_raw()
426 return diff_processor.as_raw()
366 elif self.permission_checker.has_full_access:
427 elif self.permission_checker.has_full_access:
367 return diff_processor.as_raw()
428 return diff_processor.as_raw()
368 else:
429 else:
369 return '# Repository has user-specific filters, raw patch generation is disabled.'
430 return '# Repository has user-specific filters, raw patch generation is disabled.'
370
431
371 @property
432 @property
372 def is_enabled(self):
433 def is_enabled(self):
373 return self.permission_checker is not None
434 return self.permission_checker is not None
374
435
375
436
376 class RepoGroupAppView(BaseAppView):
437 class RepoGroupAppView(BaseAppView):
377 def __init__(self, context, request):
438 def __init__(self, context, request):
378 super(RepoGroupAppView, self).__init__(context, request)
439 super(RepoGroupAppView, self).__init__(context, request)
379 self.db_repo_group = request.db_repo_group
440 self.db_repo_group = request.db_repo_group
380 self.db_repo_group_name = self.db_repo_group.group_name
441 self.db_repo_group_name = self.db_repo_group.group_name
381
442
382 def _get_local_tmpl_context(self, include_app_defaults=True):
443 def _get_local_tmpl_context(self, include_app_defaults=True):
383 _ = self.request.translate
444 _ = self.request.translate
384 c = super(RepoGroupAppView, self)._get_local_tmpl_context(
445 c = super(RepoGroupAppView, self)._get_local_tmpl_context(
385 include_app_defaults=include_app_defaults)
446 include_app_defaults=include_app_defaults)
386 c.repo_group = self.db_repo_group
447 c.repo_group = self.db_repo_group
387 return c
448 return c
388
449
389 def _revoke_perms_on_yourself(self, form_result):
450 def _revoke_perms_on_yourself(self, form_result):
390 _updates = filter(lambda u: self._rhodecode_user.user_id == int(u[0]),
451 _updates = filter(lambda u: self._rhodecode_user.user_id == int(u[0]),
391 form_result['perm_updates'])
452 form_result['perm_updates'])
392 _additions = filter(lambda u: self._rhodecode_user.user_id == int(u[0]),
453 _additions = filter(lambda u: self._rhodecode_user.user_id == int(u[0]),
393 form_result['perm_additions'])
454 form_result['perm_additions'])
394 _deletions = filter(lambda u: self._rhodecode_user.user_id == int(u[0]),
455 _deletions = filter(lambda u: self._rhodecode_user.user_id == int(u[0]),
395 form_result['perm_deletions'])
456 form_result['perm_deletions'])
396 admin_perm = 'group.admin'
457 admin_perm = 'group.admin'
397 if _updates and _updates[0][1] != admin_perm or \
458 if _updates and _updates[0][1] != admin_perm or \
398 _additions and _additions[0][1] != admin_perm or \
459 _additions and _additions[0][1] != admin_perm or \
399 _deletions and _deletions[0][1] != admin_perm:
460 _deletions and _deletions[0][1] != admin_perm:
400 return True
461 return True
401 return False
462 return False
402
463
403
464
404 class UserGroupAppView(BaseAppView):
465 class UserGroupAppView(BaseAppView):
405 def __init__(self, context, request):
466 def __init__(self, context, request):
406 super(UserGroupAppView, self).__init__(context, request)
467 super(UserGroupAppView, self).__init__(context, request)
407 self.db_user_group = request.db_user_group
468 self.db_user_group = request.db_user_group
408 self.db_user_group_name = self.db_user_group.users_group_name
469 self.db_user_group_name = self.db_user_group.users_group_name
409
470
410
471
411 class UserAppView(BaseAppView):
472 class UserAppView(BaseAppView):
412 def __init__(self, context, request):
473 def __init__(self, context, request):
413 super(UserAppView, self).__init__(context, request)
474 super(UserAppView, self).__init__(context, request)
414 self.db_user = request.db_user
475 self.db_user = request.db_user
415 self.db_user_id = self.db_user.user_id
476 self.db_user_id = self.db_user.user_id
416
477
417 _ = self.request.translate
478 _ = self.request.translate
418 if not request.db_user_supports_default:
479 if not request.db_user_supports_default:
419 if self.db_user.username == User.DEFAULT_USER:
480 if self.db_user.username == User.DEFAULT_USER:
420 h.flash(_("Editing user `{}` is disabled.".format(
481 h.flash(_("Editing user `{}` is disabled.".format(
421 User.DEFAULT_USER)), category='warning')
482 User.DEFAULT_USER)), category='warning')
422 raise HTTPFound(h.route_path('users'))
483 raise HTTPFound(h.route_path('users'))
423
484
424
485
425 class DataGridAppView(object):
486 class DataGridAppView(object):
426 """
487 """
427 Common class to have re-usable grid rendering components
488 Common class to have re-usable grid rendering components
428 """
489 """
429
490
430 def _extract_ordering(self, request, column_map=None):
491 def _extract_ordering(self, request, column_map=None):
431 column_map = column_map or {}
492 column_map = column_map or {}
432 column_index = safe_int(request.GET.get('order[0][column]'))
493 column_index = safe_int(request.GET.get('order[0][column]'))
433 order_dir = request.GET.get(
494 order_dir = request.GET.get(
434 'order[0][dir]', 'desc')
495 'order[0][dir]', 'desc')
435 order_by = request.GET.get(
496 order_by = request.GET.get(
436 'columns[%s][data][sort]' % column_index, 'name_raw')
497 'columns[%s][data][sort]' % column_index, 'name_raw')
437
498
438 # translate datatable to DB columns
499 # translate datatable to DB columns
439 order_by = column_map.get(order_by) or order_by
500 order_by = column_map.get(order_by) or order_by
440
501
441 search_q = request.GET.get('search[value]')
502 search_q = request.GET.get('search[value]')
442 return search_q, order_by, order_dir
503 return search_q, order_by, order_dir
443
504
444 def _extract_chunk(self, request):
505 def _extract_chunk(self, request):
445 start = safe_int(request.GET.get('start'), 0)
506 start = safe_int(request.GET.get('start'), 0)
446 length = safe_int(request.GET.get('length'), 25)
507 length = safe_int(request.GET.get('length'), 25)
447 draw = safe_int(request.GET.get('draw'))
508 draw = safe_int(request.GET.get('draw'))
448 return draw, start, length
509 return draw, start, length
449
510
450 def _get_order_col(self, order_by, model):
511 def _get_order_col(self, order_by, model):
451 if isinstance(order_by, compat.string_types):
512 if isinstance(order_by, compat.string_types):
452 try:
513 try:
453 return operator.attrgetter(order_by)(model)
514 return operator.attrgetter(order_by)(model)
454 except AttributeError:
515 except AttributeError:
455 return None
516 return None
456 else:
517 else:
457 return order_by
518 return order_by
458
519
459
520
460 class BaseReferencesView(RepoAppView):
521 class BaseReferencesView(RepoAppView):
461 """
522 """
462 Base for reference view for branches, tags and bookmarks.
523 Base for reference view for branches, tags and bookmarks.
463 """
524 """
464 def load_default_context(self):
525 def load_default_context(self):
465 c = self._get_local_tmpl_context()
526 c = self._get_local_tmpl_context()
466
527
467
528
468 return c
529 return c
469
530
470 def load_refs_context(self, ref_items, partials_template):
531 def load_refs_context(self, ref_items, partials_template):
471 _render = self.request.get_partial_renderer(partials_template)
532 _render = self.request.get_partial_renderer(partials_template)
472 pre_load = ["author", "date", "message", "parents"]
533 pre_load = ["author", "date", "message", "parents"]
473
534
474 is_svn = h.is_svn(self.rhodecode_vcs_repo)
535 is_svn = h.is_svn(self.rhodecode_vcs_repo)
475 is_hg = h.is_hg(self.rhodecode_vcs_repo)
536 is_hg = h.is_hg(self.rhodecode_vcs_repo)
476
537
477 format_ref_id = get_format_ref_id(self.rhodecode_vcs_repo)
538 format_ref_id = get_format_ref_id(self.rhodecode_vcs_repo)
478
539
479 closed_refs = {}
540 closed_refs = {}
480 if is_hg:
541 if is_hg:
481 closed_refs = self.rhodecode_vcs_repo.branches_closed
542 closed_refs = self.rhodecode_vcs_repo.branches_closed
482
543
483 data = []
544 data = []
484 for ref_name, commit_id in ref_items:
545 for ref_name, commit_id in ref_items:
485 commit = self.rhodecode_vcs_repo.get_commit(
546 commit = self.rhodecode_vcs_repo.get_commit(
486 commit_id=commit_id, pre_load=pre_load)
547 commit_id=commit_id, pre_load=pre_load)
487 closed = ref_name in closed_refs
548 closed = ref_name in closed_refs
488
549
489 # TODO: johbo: Unify generation of reference links
550 # TODO: johbo: Unify generation of reference links
490 use_commit_id = '/' in ref_name or is_svn
551 use_commit_id = '/' in ref_name or is_svn
491
552
492 if use_commit_id:
553 if use_commit_id:
493 files_url = h.route_path(
554 files_url = h.route_path(
494 'repo_files',
555 'repo_files',
495 repo_name=self.db_repo_name,
556 repo_name=self.db_repo_name,
496 f_path=ref_name if is_svn else '',
557 f_path=ref_name if is_svn else '',
497 commit_id=commit_id)
558 commit_id=commit_id)
498
559
499 else:
560 else:
500 files_url = h.route_path(
561 files_url = h.route_path(
501 'repo_files',
562 'repo_files',
502 repo_name=self.db_repo_name,
563 repo_name=self.db_repo_name,
503 f_path=ref_name if is_svn else '',
564 f_path=ref_name if is_svn else '',
504 commit_id=ref_name,
565 commit_id=ref_name,
505 _query=dict(at=ref_name))
566 _query=dict(at=ref_name))
506
567
507 data.append({
568 data.append({
508 "name": _render('name', ref_name, files_url, closed),
569 "name": _render('name', ref_name, files_url, closed),
509 "name_raw": ref_name,
570 "name_raw": ref_name,
510 "date": _render('date', commit.date),
571 "date": _render('date', commit.date),
511 "date_raw": datetime_to_time(commit.date),
572 "date_raw": datetime_to_time(commit.date),
512 "author": _render('author', commit.author),
573 "author": _render('author', commit.author),
513 "commit": _render(
574 "commit": _render(
514 'commit', commit.message, commit.raw_id, commit.idx),
575 'commit', commit.message, commit.raw_id, commit.idx),
515 "commit_raw": commit.idx,
576 "commit_raw": commit.idx,
516 "compare": _render(
577 "compare": _render(
517 'compare', format_ref_id(ref_name, commit.raw_id)),
578 'compare', format_ref_id(ref_name, commit.raw_id)),
518 })
579 })
519
580
520 return data
581 return data
521
582
522
583
523 class RepoRoutePredicate(object):
584 class RepoRoutePredicate(object):
524 def __init__(self, val, config):
585 def __init__(self, val, config):
525 self.val = val
586 self.val = val
526
587
527 def text(self):
588 def text(self):
528 return 'repo_route = %s' % self.val
589 return 'repo_route = %s' % self.val
529
590
530 phash = text
591 phash = text
531
592
532 def __call__(self, info, request):
593 def __call__(self, info, request):
533 if hasattr(request, 'vcs_call'):
594 if hasattr(request, 'vcs_call'):
534 # skip vcs calls
595 # skip vcs calls
535 return
596 return
536
597
537 repo_name = info['match']['repo_name']
598 repo_name = info['match']['repo_name']
538 repo_model = repo.RepoModel()
599 repo_model = repo.RepoModel()
539
600
540 by_name_match = repo_model.get_by_repo_name(repo_name, cache=False)
601 by_name_match = repo_model.get_by_repo_name(repo_name, cache=False)
541
602
542 def redirect_if_creating(route_info, db_repo):
603 def redirect_if_creating(route_info, db_repo):
543 skip_views = ['edit_repo_advanced_delete']
604 skip_views = ['edit_repo_advanced_delete']
544 route = route_info['route']
605 route = route_info['route']
545 # we should skip delete view so we can actually "remove" repositories
606 # we should skip delete view so we can actually "remove" repositories
546 # if they get stuck in creating state.
607 # if they get stuck in creating state.
547 if route.name in skip_views:
608 if route.name in skip_views:
548 return
609 return
549
610
550 if db_repo.repo_state in [repo.Repository.STATE_PENDING]:
611 if db_repo.repo_state in [repo.Repository.STATE_PENDING]:
551 repo_creating_url = request.route_path(
612 repo_creating_url = request.route_path(
552 'repo_creating', repo_name=db_repo.repo_name)
613 'repo_creating', repo_name=db_repo.repo_name)
553 raise HTTPFound(repo_creating_url)
614 raise HTTPFound(repo_creating_url)
554
615
555 if by_name_match:
616 if by_name_match:
556 # register this as request object we can re-use later
617 # register this as request object we can re-use later
557 request.db_repo = by_name_match
618 request.db_repo = by_name_match
558 redirect_if_creating(info, by_name_match)
619 redirect_if_creating(info, by_name_match)
559 return True
620 return True
560
621
561 by_id_match = repo_model.get_repo_by_id(repo_name)
622 by_id_match = repo_model.get_repo_by_id(repo_name)
562 if by_id_match:
623 if by_id_match:
563 request.db_repo = by_id_match
624 request.db_repo = by_id_match
564 redirect_if_creating(info, by_id_match)
625 redirect_if_creating(info, by_id_match)
565 return True
626 return True
566
627
567 return False
628 return False
568
629
569
630
570 class RepoForbidArchivedRoutePredicate(object):
631 class RepoForbidArchivedRoutePredicate(object):
571 def __init__(self, val, config):
632 def __init__(self, val, config):
572 self.val = val
633 self.val = val
573
634
574 def text(self):
635 def text(self):
575 return 'repo_forbid_archived = %s' % self.val
636 return 'repo_forbid_archived = %s' % self.val
576
637
577 phash = text
638 phash = text
578
639
579 def __call__(self, info, request):
640 def __call__(self, info, request):
580 _ = request.translate
641 _ = request.translate
581 rhodecode_db_repo = request.db_repo
642 rhodecode_db_repo = request.db_repo
582
643
583 log.debug(
644 log.debug(
584 '%s checking if archived flag for repo for %s',
645 '%s checking if archived flag for repo for %s',
585 self.__class__.__name__, rhodecode_db_repo.repo_name)
646 self.__class__.__name__, rhodecode_db_repo.repo_name)
586
647
587 if rhodecode_db_repo.archived:
648 if rhodecode_db_repo.archived:
588 log.warning('Current view is not supported for archived repo:%s',
649 log.warning('Current view is not supported for archived repo:%s',
589 rhodecode_db_repo.repo_name)
650 rhodecode_db_repo.repo_name)
590
651
591 h.flash(
652 h.flash(
592 h.literal(_('Action not supported for archived repository.')),
653 h.literal(_('Action not supported for archived repository.')),
593 category='warning')
654 category='warning')
594 summary_url = request.route_path(
655 summary_url = request.route_path(
595 'repo_summary', repo_name=rhodecode_db_repo.repo_name)
656 'repo_summary', repo_name=rhodecode_db_repo.repo_name)
596 raise HTTPFound(summary_url)
657 raise HTTPFound(summary_url)
597 return True
658 return True
598
659
599
660
600 class RepoTypeRoutePredicate(object):
661 class RepoTypeRoutePredicate(object):
601 def __init__(self, val, config):
662 def __init__(self, val, config):
602 self.val = val or ['hg', 'git', 'svn']
663 self.val = val or ['hg', 'git', 'svn']
603
664
604 def text(self):
665 def text(self):
605 return 'repo_accepted_type = %s' % self.val
666 return 'repo_accepted_type = %s' % self.val
606
667
607 phash = text
668 phash = text
608
669
609 def __call__(self, info, request):
670 def __call__(self, info, request):
610 if hasattr(request, 'vcs_call'):
671 if hasattr(request, 'vcs_call'):
611 # skip vcs calls
672 # skip vcs calls
612 return
673 return
613
674
614 rhodecode_db_repo = request.db_repo
675 rhodecode_db_repo = request.db_repo
615
676
616 log.debug(
677 log.debug(
617 '%s checking repo type for %s in %s',
678 '%s checking repo type for %s in %s',
618 self.__class__.__name__, rhodecode_db_repo.repo_type, self.val)
679 self.__class__.__name__, rhodecode_db_repo.repo_type, self.val)
619
680
620 if rhodecode_db_repo.repo_type in self.val:
681 if rhodecode_db_repo.repo_type in self.val:
621 return True
682 return True
622 else:
683 else:
623 log.warning('Current view is not supported for repo type:%s',
684 log.warning('Current view is not supported for repo type:%s',
624 rhodecode_db_repo.repo_type)
685 rhodecode_db_repo.repo_type)
625 return False
686 return False
626
687
627
688
628 class RepoGroupRoutePredicate(object):
689 class RepoGroupRoutePredicate(object):
629 def __init__(self, val, config):
690 def __init__(self, val, config):
630 self.val = val
691 self.val = val
631
692
632 def text(self):
693 def text(self):
633 return 'repo_group_route = %s' % self.val
694 return 'repo_group_route = %s' % self.val
634
695
635 phash = text
696 phash = text
636
697
637 def __call__(self, info, request):
698 def __call__(self, info, request):
638 if hasattr(request, 'vcs_call'):
699 if hasattr(request, 'vcs_call'):
639 # skip vcs calls
700 # skip vcs calls
640 return
701 return
641
702
642 repo_group_name = info['match']['repo_group_name']
703 repo_group_name = info['match']['repo_group_name']
643 repo_group_model = repo_group.RepoGroupModel()
704 repo_group_model = repo_group.RepoGroupModel()
644 by_name_match = repo_group_model.get_by_group_name(repo_group_name, cache=False)
705 by_name_match = repo_group_model.get_by_group_name(repo_group_name, cache=False)
645
706
646 if by_name_match:
707 if by_name_match:
647 # register this as request object we can re-use later
708 # register this as request object we can re-use later
648 request.db_repo_group = by_name_match
709 request.db_repo_group = by_name_match
649 return True
710 return True
650
711
651 return False
712 return False
652
713
653
714
654 class UserGroupRoutePredicate(object):
715 class UserGroupRoutePredicate(object):
655 def __init__(self, val, config):
716 def __init__(self, val, config):
656 self.val = val
717 self.val = val
657
718
658 def text(self):
719 def text(self):
659 return 'user_group_route = %s' % self.val
720 return 'user_group_route = %s' % self.val
660
721
661 phash = text
722 phash = text
662
723
663 def __call__(self, info, request):
724 def __call__(self, info, request):
664 if hasattr(request, 'vcs_call'):
725 if hasattr(request, 'vcs_call'):
665 # skip vcs calls
726 # skip vcs calls
666 return
727 return
667
728
668 user_group_id = info['match']['user_group_id']
729 user_group_id = info['match']['user_group_id']
669 user_group_model = user_group.UserGroup()
730 user_group_model = user_group.UserGroup()
670 by_id_match = user_group_model.get(user_group_id, cache=False)
731 by_id_match = user_group_model.get(user_group_id, cache=False)
671
732
672 if by_id_match:
733 if by_id_match:
673 # register this as request object we can re-use later
734 # register this as request object we can re-use later
674 request.db_user_group = by_id_match
735 request.db_user_group = by_id_match
675 return True
736 return True
676
737
677 return False
738 return False
678
739
679
740
680 class UserRoutePredicateBase(object):
741 class UserRoutePredicateBase(object):
681 supports_default = None
742 supports_default = None
682
743
683 def __init__(self, val, config):
744 def __init__(self, val, config):
684 self.val = val
745 self.val = val
685
746
686 def text(self):
747 def text(self):
687 raise NotImplementedError()
748 raise NotImplementedError()
688
749
689 def __call__(self, info, request):
750 def __call__(self, info, request):
690 if hasattr(request, 'vcs_call'):
751 if hasattr(request, 'vcs_call'):
691 # skip vcs calls
752 # skip vcs calls
692 return
753 return
693
754
694 user_id = info['match']['user_id']
755 user_id = info['match']['user_id']
695 user_model = user.User()
756 user_model = user.User()
696 by_id_match = user_model.get(user_id, cache=False)
757 by_id_match = user_model.get(user_id, cache=False)
697
758
698 if by_id_match:
759 if by_id_match:
699 # register this as request object we can re-use later
760 # register this as request object we can re-use later
700 request.db_user = by_id_match
761 request.db_user = by_id_match
701 request.db_user_supports_default = self.supports_default
762 request.db_user_supports_default = self.supports_default
702 return True
763 return True
703
764
704 return False
765 return False
705
766
706
767
707 class UserRoutePredicate(UserRoutePredicateBase):
768 class UserRoutePredicate(UserRoutePredicateBase):
708 supports_default = False
769 supports_default = False
709
770
710 def text(self):
771 def text(self):
711 return 'user_route = %s' % self.val
772 return 'user_route = %s' % self.val
712
773
713 phash = text
774 phash = text
714
775
715
776
716 class UserRouteWithDefaultPredicate(UserRoutePredicateBase):
777 class UserRouteWithDefaultPredicate(UserRoutePredicateBase):
717 supports_default = True
778 supports_default = True
718
779
719 def text(self):
780 def text(self):
720 return 'user_with_default_route = %s' % self.val
781 return 'user_with_default_route = %s' % self.val
721
782
722 phash = text
783 phash = text
723
784
724
785
725 def includeme(config):
786 def includeme(config):
726 config.add_route_predicate(
787 config.add_route_predicate(
727 'repo_route', RepoRoutePredicate)
788 'repo_route', RepoRoutePredicate)
728 config.add_route_predicate(
789 config.add_route_predicate(
729 'repo_accepted_types', RepoTypeRoutePredicate)
790 'repo_accepted_types', RepoTypeRoutePredicate)
730 config.add_route_predicate(
791 config.add_route_predicate(
731 'repo_forbid_when_archived', RepoForbidArchivedRoutePredicate)
792 'repo_forbid_when_archived', RepoForbidArchivedRoutePredicate)
732 config.add_route_predicate(
793 config.add_route_predicate(
733 'repo_group_route', RepoGroupRoutePredicate)
794 'repo_group_route', RepoGroupRoutePredicate)
734 config.add_route_predicate(
795 config.add_route_predicate(
735 'user_group_route', UserGroupRoutePredicate)
796 'user_group_route', UserGroupRoutePredicate)
736 config.add_route_predicate(
797 config.add_route_predicate(
737 'user_route_with_default', UserRouteWithDefaultPredicate)
798 'user_route_with_default', UserRouteWithDefaultPredicate)
738 config.add_route_predicate(
799 config.add_route_predicate(
739 'user_route', UserRoutePredicate)
800 'user_route', UserRoutePredicate)
@@ -1,1548 +1,1552 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2019 RhodeCode GmbH
3 # Copyright (C) 2011-2019 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import itertools
21 import itertools
22 import logging
22 import logging
23 import os
23 import os
24 import shutil
24 import shutil
25 import tempfile
25 import tempfile
26 import collections
26 import collections
27 import urllib
27 import urllib
28 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_rev[1]
140 default_commit_id = self.db_repo.landing_rev[1]
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):
184 except (CommitDoesNotExistError, LookupError):
185 msg = _('No such commit exists for this repository')
185 msg = _('No such commit exists for this repository')
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):
248 def _get_tree_at_commit(self, c, commit_id, f_path, full_load=False):
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,
264 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid,
265 condition=cache_on)
265 condition=cache_on)
266 def compute_file_tree(ver, repo_id, commit_id, f_path, full_load):
266 def compute_file_tree(ver, repo_id, commit_id, f_path, full_load):
267 log.debug('Generating cached file tree at ver:%s for repo_id: %s, %s, %s',
267 log.debug('Generating cached file tree at ver:%s for repo_id: %s, %s, %s',
268 ver, repo_id, commit_id, f_path)
268 ver, repo_id, commit_id, f_path)
269
269
270 c.full_load = full_load
270 c.full_load = full_load
271 return render(
271 return render(
272 'rhodecode:templates/files/files_browser_tree.mako',
272 'rhodecode:templates/files/files_browser_tree.mako',
273 self._get_template_context(c), self.request)
273 self._get_template_context(c), self.request)
274
274
275 return compute_file_tree('v1', self.db_repo.repo_id, commit_id, f_path, full_load)
275 return compute_file_tree('v1', self.db_repo.repo_id, commit_id, f_path, full_load)
276
276
277 def _get_archive_spec(self, fname):
277 def _get_archive_spec(self, fname):
278 log.debug('Detecting archive spec for: `%s`', fname)
278 log.debug('Detecting archive spec for: `%s`', fname)
279
279
280 fileformat = None
280 fileformat = None
281 ext = None
281 ext = None
282 content_type = None
282 content_type = None
283 for a_type, content_type, extension in settings.ARCHIVE_SPECS:
283 for a_type, content_type, extension in settings.ARCHIVE_SPECS:
284
284
285 if fname.endswith(extension):
285 if fname.endswith(extension):
286 fileformat = a_type
286 fileformat = a_type
287 log.debug('archive is of type: %s', fileformat)
287 log.debug('archive is of type: %s', fileformat)
288 ext = extension
288 ext = extension
289 break
289 break
290
290
291 if not fileformat:
291 if not fileformat:
292 raise ValueError()
292 raise ValueError()
293
293
294 # left over part of whole fname is the commit
294 # left over part of whole fname is the commit
295 commit_id = fname[:-len(ext)]
295 commit_id = fname[:-len(ext)]
296
296
297 return commit_id, ext, fileformat, content_type
297 return commit_id, ext, fileformat, content_type
298
298
299 def create_pure_path(self, *parts):
299 def create_pure_path(self, *parts):
300 # Split paths and sanitize them, removing any ../ etc
300 # Split paths and sanitize them, removing any ../ etc
301 sanitized_path = [
301 sanitized_path = [
302 x for x in pathlib2.PurePath(*parts).parts
302 x for x in pathlib2.PurePath(*parts).parts
303 if x not in ['.', '..']]
303 if x not in ['.', '..']]
304
304
305 pure_path = pathlib2.PurePath(*sanitized_path)
305 pure_path = pathlib2.PurePath(*sanitized_path)
306 return pure_path
306 return pure_path
307
307
308 def _is_lf_enabled(self, target_repo):
308 def _is_lf_enabled(self, target_repo):
309 lf_enabled = False
309 lf_enabled = False
310
310
311 lf_key_for_vcs_map = {
311 lf_key_for_vcs_map = {
312 'hg': 'extensions_largefiles',
312 'hg': 'extensions_largefiles',
313 'git': 'vcs_git_lfs_enabled'
313 'git': 'vcs_git_lfs_enabled'
314 }
314 }
315
315
316 lf_key_for_vcs = lf_key_for_vcs_map.get(target_repo.repo_type)
316 lf_key_for_vcs = lf_key_for_vcs_map.get(target_repo.repo_type)
317
317
318 if lf_key_for_vcs:
318 if lf_key_for_vcs:
319 lf_enabled = self._get_repo_setting(target_repo, lf_key_for_vcs)
319 lf_enabled = self._get_repo_setting(target_repo, lf_key_for_vcs)
320
320
321 return lf_enabled
321 return lf_enabled
322
322
323 @LoginRequired()
323 @LoginRequired()
324 @HasRepoPermissionAnyDecorator(
324 @HasRepoPermissionAnyDecorator(
325 'repository.read', 'repository.write', 'repository.admin')
325 'repository.read', 'repository.write', 'repository.admin')
326 @view_config(
326 @view_config(
327 route_name='repo_archivefile', request_method='GET',
327 route_name='repo_archivefile', request_method='GET',
328 renderer=None)
328 renderer=None)
329 def repo_archivefile(self):
329 def repo_archivefile(self):
330 # archive cache config
330 # archive cache config
331 from rhodecode import CONFIG
331 from rhodecode import CONFIG
332 _ = self.request.translate
332 _ = self.request.translate
333 self.load_default_context()
333 self.load_default_context()
334 default_at_path = '/'
334 default_at_path = '/'
335 fname = self.request.matchdict['fname']
335 fname = self.request.matchdict['fname']
336 subrepos = self.request.GET.get('subrepos') == 'true'
336 subrepos = self.request.GET.get('subrepos') == 'true'
337 at_path = self.request.GET.get('at_path') or default_at_path
337 at_path = self.request.GET.get('at_path') or default_at_path
338
338
339 if not self.db_repo.enable_downloads:
339 if not self.db_repo.enable_downloads:
340 return Response(_('Downloads disabled'))
340 return Response(_('Downloads disabled'))
341
341
342 try:
342 try:
343 commit_id, ext, fileformat, content_type = \
343 commit_id, ext, fileformat, content_type = \
344 self._get_archive_spec(fname)
344 self._get_archive_spec(fname)
345 except ValueError:
345 except ValueError:
346 return Response(_('Unknown archive type for: `{}`').format(
346 return Response(_('Unknown archive type for: `{}`').format(
347 h.escape(fname)))
347 h.escape(fname)))
348
348
349 try:
349 try:
350 commit = self.rhodecode_vcs_repo.get_commit(commit_id)
350 commit = self.rhodecode_vcs_repo.get_commit(commit_id)
351 except CommitDoesNotExistError:
351 except CommitDoesNotExistError:
352 return Response(_('Unknown commit_id {}').format(
352 return Response(_('Unknown commit_id {}').format(
353 h.escape(commit_id)))
353 h.escape(commit_id)))
354 except EmptyRepositoryError:
354 except EmptyRepositoryError:
355 return Response(_('Empty repository'))
355 return Response(_('Empty repository'))
356
356
357 try:
357 try:
358 at_path = commit.get_node(at_path).path or default_at_path
358 at_path = commit.get_node(at_path).path or default_at_path
359 except Exception:
359 except Exception:
360 return Response(_('No node at path {} for this repository').format(at_path))
360 return Response(_('No node at path {} for this repository').format(at_path))
361
361
362 path_sha = sha1(at_path)[:8]
362 path_sha = sha1(at_path)[:8]
363
363
364 # original backward compat name of archive
364 # original backward compat name of archive
365 clean_name = safe_str(self.db_repo_name.replace('/', '_'))
365 clean_name = safe_str(self.db_repo_name.replace('/', '_'))
366 short_sha = safe_str(commit.short_id)
366 short_sha = safe_str(commit.short_id)
367
367
368 if at_path == default_at_path:
368 if at_path == default_at_path:
369 archive_name = '{}-{}{}{}'.format(
369 archive_name = '{}-{}{}{}'.format(
370 clean_name,
370 clean_name,
371 '-sub' if subrepos else '',
371 '-sub' if subrepos else '',
372 short_sha,
372 short_sha,
373 ext)
373 ext)
374 # custom path and new name
374 # custom path and new name
375 else:
375 else:
376 archive_name = '{}-{}{}-{}{}'.format(
376 archive_name = '{}-{}{}-{}{}'.format(
377 clean_name,
377 clean_name,
378 '-sub' if subrepos else '',
378 '-sub' if subrepos else '',
379 short_sha,
379 short_sha,
380 path_sha,
380 path_sha,
381 ext)
381 ext)
382
382
383 use_cached_archive = False
383 use_cached_archive = False
384 archive_cache_enabled = CONFIG.get(
384 archive_cache_enabled = CONFIG.get(
385 'archive_cache_dir') and not self.request.GET.get('no_cache')
385 'archive_cache_dir') and not self.request.GET.get('no_cache')
386 cached_archive_path = None
386 cached_archive_path = None
387
387
388 if archive_cache_enabled:
388 if archive_cache_enabled:
389 # check if we it's ok to write
389 # check if we it's ok to write
390 if not os.path.isdir(CONFIG['archive_cache_dir']):
390 if not os.path.isdir(CONFIG['archive_cache_dir']):
391 os.makedirs(CONFIG['archive_cache_dir'])
391 os.makedirs(CONFIG['archive_cache_dir'])
392 cached_archive_path = os.path.join(
392 cached_archive_path = os.path.join(
393 CONFIG['archive_cache_dir'], archive_name)
393 CONFIG['archive_cache_dir'], archive_name)
394 if os.path.isfile(cached_archive_path):
394 if os.path.isfile(cached_archive_path):
395 log.debug('Found cached archive in %s', cached_archive_path)
395 log.debug('Found cached archive in %s', cached_archive_path)
396 fd, archive = None, cached_archive_path
396 fd, archive = None, cached_archive_path
397 use_cached_archive = True
397 use_cached_archive = True
398 else:
398 else:
399 log.debug('Archive %s is not yet cached', archive_name)
399 log.debug('Archive %s is not yet cached', archive_name)
400
400
401 if not use_cached_archive:
401 if not use_cached_archive:
402 # generate new archive
402 # generate new archive
403 fd, archive = tempfile.mkstemp()
403 fd, archive = tempfile.mkstemp()
404 log.debug('Creating new temp archive in %s', archive)
404 log.debug('Creating new temp archive in %s', archive)
405 try:
405 try:
406 commit.archive_repo(archive, kind=fileformat, subrepos=subrepos,
406 commit.archive_repo(archive, kind=fileformat, subrepos=subrepos,
407 archive_at_path=at_path)
407 archive_at_path=at_path)
408 except ImproperArchiveTypeError:
408 except ImproperArchiveTypeError:
409 return _('Unknown archive type')
409 return _('Unknown archive type')
410 if archive_cache_enabled:
410 if archive_cache_enabled:
411 # if we generated the archive and we have cache enabled
411 # if we generated the archive and we have cache enabled
412 # let's use this for future
412 # let's use this for future
413 log.debug('Storing new archive in %s', cached_archive_path)
413 log.debug('Storing new archive in %s', cached_archive_path)
414 shutil.move(archive, cached_archive_path)
414 shutil.move(archive, cached_archive_path)
415 archive = cached_archive_path
415 archive = cached_archive_path
416
416
417 # store download action
417 # store download action
418 audit_logger.store_web(
418 audit_logger.store_web(
419 'repo.archive.download', action_data={
419 'repo.archive.download', action_data={
420 'user_agent': self.request.user_agent,
420 'user_agent': self.request.user_agent,
421 'archive_name': archive_name,
421 'archive_name': archive_name,
422 'archive_spec': fname,
422 'archive_spec': fname,
423 'archive_cached': use_cached_archive},
423 'archive_cached': use_cached_archive},
424 user=self._rhodecode_user,
424 user=self._rhodecode_user,
425 repo=self.db_repo,
425 repo=self.db_repo,
426 commit=True
426 commit=True
427 )
427 )
428
428
429 def get_chunked_archive(archive_path):
429 def get_chunked_archive(archive_path):
430 with open(archive_path, 'rb') as stream:
430 with open(archive_path, 'rb') as stream:
431 while True:
431 while True:
432 data = stream.read(16 * 1024)
432 data = stream.read(16 * 1024)
433 if not data:
433 if not data:
434 if fd: # fd means we used temporary file
434 if fd: # fd means we used temporary file
435 os.close(fd)
435 os.close(fd)
436 if not archive_cache_enabled:
436 if not archive_cache_enabled:
437 log.debug('Destroying temp archive %s', archive_path)
437 log.debug('Destroying temp archive %s', archive_path)
438 os.remove(archive_path)
438 os.remove(archive_path)
439 break
439 break
440 yield data
440 yield data
441
441
442 response = Response(app_iter=get_chunked_archive(archive))
442 response = Response(app_iter=get_chunked_archive(archive))
443 response.content_disposition = str(
443 response.content_disposition = str(
444 'attachment; filename=%s' % archive_name)
444 'attachment; filename=%s' % archive_name)
445 response.content_type = str(content_type)
445 response.content_type = str(content_type)
446
446
447 return response
447 return response
448
448
449 def _get_file_node(self, commit_id, f_path):
449 def _get_file_node(self, commit_id, f_path):
450 if commit_id not in ['', None, 'None', '0' * 12, '0' * 40]:
450 if commit_id not in ['', None, 'None', '0' * 12, '0' * 40]:
451 commit = self.rhodecode_vcs_repo.get_commit(commit_id=commit_id)
451 commit = self.rhodecode_vcs_repo.get_commit(commit_id=commit_id)
452 try:
452 try:
453 node = commit.get_node(f_path)
453 node = commit.get_node(f_path)
454 if node.is_dir():
454 if node.is_dir():
455 raise NodeError('%s path is a %s not a file'
455 raise NodeError('%s path is a %s not a file'
456 % (node, type(node)))
456 % (node, type(node)))
457 except NodeDoesNotExistError:
457 except NodeDoesNotExistError:
458 commit = EmptyCommit(
458 commit = EmptyCommit(
459 commit_id=commit_id,
459 commit_id=commit_id,
460 idx=commit.idx,
460 idx=commit.idx,
461 repo=commit.repository,
461 repo=commit.repository,
462 alias=commit.repository.alias,
462 alias=commit.repository.alias,
463 message=commit.message,
463 message=commit.message,
464 author=commit.author,
464 author=commit.author,
465 date=commit.date)
465 date=commit.date)
466 node = FileNode(f_path, '', commit=commit)
466 node = FileNode(f_path, '', commit=commit)
467 else:
467 else:
468 commit = EmptyCommit(
468 commit = EmptyCommit(
469 repo=self.rhodecode_vcs_repo,
469 repo=self.rhodecode_vcs_repo,
470 alias=self.rhodecode_vcs_repo.alias)
470 alias=self.rhodecode_vcs_repo.alias)
471 node = FileNode(f_path, '', commit=commit)
471 node = FileNode(f_path, '', commit=commit)
472 return node
472 return node
473
473
474 @LoginRequired()
474 @LoginRequired()
475 @HasRepoPermissionAnyDecorator(
475 @HasRepoPermissionAnyDecorator(
476 'repository.read', 'repository.write', 'repository.admin')
476 'repository.read', 'repository.write', 'repository.admin')
477 @view_config(
477 @view_config(
478 route_name='repo_files_diff', request_method='GET',
478 route_name='repo_files_diff', request_method='GET',
479 renderer=None)
479 renderer=None)
480 def repo_files_diff(self):
480 def repo_files_diff(self):
481 c = self.load_default_context()
481 c = self.load_default_context()
482 f_path = self._get_f_path(self.request.matchdict)
482 f_path = self._get_f_path(self.request.matchdict)
483 diff1 = self.request.GET.get('diff1', '')
483 diff1 = self.request.GET.get('diff1', '')
484 diff2 = self.request.GET.get('diff2', '')
484 diff2 = self.request.GET.get('diff2', '')
485
485
486 path1, diff1 = parse_path_ref(diff1, default_path=f_path)
486 path1, diff1 = parse_path_ref(diff1, default_path=f_path)
487
487
488 ignore_whitespace = str2bool(self.request.GET.get('ignorews'))
488 ignore_whitespace = str2bool(self.request.GET.get('ignorews'))
489 line_context = self.request.GET.get('context', 3)
489 line_context = self.request.GET.get('context', 3)
490
490
491 if not any((diff1, diff2)):
491 if not any((diff1, diff2)):
492 h.flash(
492 h.flash(
493 'Need query parameter "diff1" or "diff2" to generate a diff.',
493 'Need query parameter "diff1" or "diff2" to generate a diff.',
494 category='error')
494 category='error')
495 raise HTTPBadRequest()
495 raise HTTPBadRequest()
496
496
497 c.action = self.request.GET.get('diff')
497 c.action = self.request.GET.get('diff')
498 if c.action not in ['download', 'raw']:
498 if c.action not in ['download', 'raw']:
499 compare_url = h.route_path(
499 compare_url = h.route_path(
500 'repo_compare',
500 'repo_compare',
501 repo_name=self.db_repo_name,
501 repo_name=self.db_repo_name,
502 source_ref_type='rev',
502 source_ref_type='rev',
503 source_ref=diff1,
503 source_ref=diff1,
504 target_repo=self.db_repo_name,
504 target_repo=self.db_repo_name,
505 target_ref_type='rev',
505 target_ref_type='rev',
506 target_ref=diff2,
506 target_ref=diff2,
507 _query=dict(f_path=f_path))
507 _query=dict(f_path=f_path))
508 # redirect to new view if we render diff
508 # redirect to new view if we render diff
509 raise HTTPFound(compare_url)
509 raise HTTPFound(compare_url)
510
510
511 try:
511 try:
512 node1 = self._get_file_node(diff1, path1)
512 node1 = self._get_file_node(diff1, path1)
513 node2 = self._get_file_node(diff2, f_path)
513 node2 = self._get_file_node(diff2, f_path)
514 except (RepositoryError, NodeError):
514 except (RepositoryError, NodeError):
515 log.exception("Exception while trying to get node from repository")
515 log.exception("Exception while trying to get node from repository")
516 raise HTTPFound(
516 raise HTTPFound(
517 h.route_path('repo_files', repo_name=self.db_repo_name,
517 h.route_path('repo_files', repo_name=self.db_repo_name,
518 commit_id='tip', f_path=f_path))
518 commit_id='tip', f_path=f_path))
519
519
520 if all(isinstance(node.commit, EmptyCommit)
520 if all(isinstance(node.commit, EmptyCommit)
521 for node in (node1, node2)):
521 for node in (node1, node2)):
522 raise HTTPNotFound()
522 raise HTTPNotFound()
523
523
524 c.commit_1 = node1.commit
524 c.commit_1 = node1.commit
525 c.commit_2 = node2.commit
525 c.commit_2 = node2.commit
526
526
527 if c.action == 'download':
527 if c.action == 'download':
528 _diff = diffs.get_gitdiff(node1, node2,
528 _diff = diffs.get_gitdiff(node1, node2,
529 ignore_whitespace=ignore_whitespace,
529 ignore_whitespace=ignore_whitespace,
530 context=line_context)
530 context=line_context)
531 diff = diffs.DiffProcessor(_diff, format='gitdiff')
531 diff = diffs.DiffProcessor(_diff, format='gitdiff')
532
532
533 response = Response(self.path_filter.get_raw_patch(diff))
533 response = Response(self.path_filter.get_raw_patch(diff))
534 response.content_type = 'text/plain'
534 response.content_type = 'text/plain'
535 response.content_disposition = (
535 response.content_disposition = (
536 'attachment; filename=%s_%s_vs_%s.diff' % (f_path, diff1, diff2)
536 'attachment; filename=%s_%s_vs_%s.diff' % (f_path, diff1, diff2)
537 )
537 )
538 charset = self._get_default_encoding(c)
538 charset = self._get_default_encoding(c)
539 if charset:
539 if charset:
540 response.charset = charset
540 response.charset = charset
541 return response
541 return response
542
542
543 elif c.action == 'raw':
543 elif c.action == 'raw':
544 _diff = diffs.get_gitdiff(node1, node2,
544 _diff = diffs.get_gitdiff(node1, node2,
545 ignore_whitespace=ignore_whitespace,
545 ignore_whitespace=ignore_whitespace,
546 context=line_context)
546 context=line_context)
547 diff = diffs.DiffProcessor(_diff, format='gitdiff')
547 diff = diffs.DiffProcessor(_diff, format='gitdiff')
548
548
549 response = Response(self.path_filter.get_raw_patch(diff))
549 response = Response(self.path_filter.get_raw_patch(diff))
550 response.content_type = 'text/plain'
550 response.content_type = 'text/plain'
551 charset = self._get_default_encoding(c)
551 charset = self._get_default_encoding(c)
552 if charset:
552 if charset:
553 response.charset = charset
553 response.charset = charset
554 return response
554 return response
555
555
556 # in case we ever end up here
556 # in case we ever end up here
557 raise HTTPNotFound()
557 raise HTTPNotFound()
558
558
559 @LoginRequired()
559 @LoginRequired()
560 @HasRepoPermissionAnyDecorator(
560 @HasRepoPermissionAnyDecorator(
561 'repository.read', 'repository.write', 'repository.admin')
561 'repository.read', 'repository.write', 'repository.admin')
562 @view_config(
562 @view_config(
563 route_name='repo_files_diff_2way_redirect', request_method='GET',
563 route_name='repo_files_diff_2way_redirect', request_method='GET',
564 renderer=None)
564 renderer=None)
565 def repo_files_diff_2way_redirect(self):
565 def repo_files_diff_2way_redirect(self):
566 """
566 """
567 Kept only to make OLD links work
567 Kept only to make OLD links work
568 """
568 """
569 f_path = self._get_f_path_unchecked(self.request.matchdict)
569 f_path = self._get_f_path_unchecked(self.request.matchdict)
570 diff1 = self.request.GET.get('diff1', '')
570 diff1 = self.request.GET.get('diff1', '')
571 diff2 = self.request.GET.get('diff2', '')
571 diff2 = self.request.GET.get('diff2', '')
572
572
573 if not any((diff1, diff2)):
573 if not any((diff1, diff2)):
574 h.flash(
574 h.flash(
575 'Need query parameter "diff1" or "diff2" to generate a diff.',
575 'Need query parameter "diff1" or "diff2" to generate a diff.',
576 category='error')
576 category='error')
577 raise HTTPBadRequest()
577 raise HTTPBadRequest()
578
578
579 compare_url = h.route_path(
579 compare_url = h.route_path(
580 'repo_compare',
580 'repo_compare',
581 repo_name=self.db_repo_name,
581 repo_name=self.db_repo_name,
582 source_ref_type='rev',
582 source_ref_type='rev',
583 source_ref=diff1,
583 source_ref=diff1,
584 target_ref_type='rev',
584 target_ref_type='rev',
585 target_ref=diff2,
585 target_ref=diff2,
586 _query=dict(f_path=f_path, diffmode='sideside',
586 _query=dict(f_path=f_path, diffmode='sideside',
587 target_repo=self.db_repo_name,))
587 target_repo=self.db_repo_name,))
588 raise HTTPFound(compare_url)
588 raise HTTPFound(compare_url)
589
589
590 @LoginRequired()
590 @LoginRequired()
591 @HasRepoPermissionAnyDecorator(
591 @HasRepoPermissionAnyDecorator(
592 'repository.read', 'repository.write', 'repository.admin')
592 'repository.read', 'repository.write', 'repository.admin')
593 @view_config(
593 @view_config(
594 route_name='repo_files', request_method='GET',
594 route_name='repo_files', request_method='GET',
595 renderer=None)
595 renderer=None)
596 @view_config(
596 @view_config(
597 route_name='repo_files:default_path', request_method='GET',
597 route_name='repo_files:default_path', request_method='GET',
598 renderer=None)
598 renderer=None)
599 @view_config(
599 @view_config(
600 route_name='repo_files:default_commit', request_method='GET',
600 route_name='repo_files:default_commit', request_method='GET',
601 renderer=None)
601 renderer=None)
602 @view_config(
602 @view_config(
603 route_name='repo_files:rendered', request_method='GET',
603 route_name='repo_files:rendered', request_method='GET',
604 renderer=None)
604 renderer=None)
605 @view_config(
605 @view_config(
606 route_name='repo_files:annotated', request_method='GET',
606 route_name='repo_files:annotated', request_method='GET',
607 renderer=None)
607 renderer=None)
608 def repo_files(self):
608 def repo_files(self):
609 c = self.load_default_context()
609 c = self.load_default_context()
610
610
611 view_name = getattr(self.request.matched_route, 'name', None)
611 view_name = getattr(self.request.matched_route, 'name', None)
612
612
613 c.annotate = view_name == 'repo_files:annotated'
613 c.annotate = view_name == 'repo_files:annotated'
614 # default is false, but .rst/.md files later are auto rendered, we can
614 # default is false, but .rst/.md files later are auto rendered, we can
615 # overwrite auto rendering by setting this GET flag
615 # overwrite auto rendering by setting this GET flag
616 c.renderer = view_name == 'repo_files:rendered' or \
616 c.renderer = view_name == 'repo_files:rendered' or \
617 not self.request.GET.get('no-render', False)
617 not self.request.GET.get('no-render', False)
618
618
619 # redirect to given commit_id from form if given
619 # redirect to given commit_id from form if given
620 get_commit_id = self.request.GET.get('at_rev', None)
620 get_commit_id = self.request.GET.get('at_rev', None)
621 if get_commit_id:
621 if get_commit_id:
622 self._get_commit_or_redirect(get_commit_id)
622 self._get_commit_or_redirect(get_commit_id)
623
623
624 commit_id, f_path = self._get_commit_and_path()
624 commit_id, f_path = self._get_commit_and_path()
625 c.commit = self._get_commit_or_redirect(commit_id)
625 c.commit = self._get_commit_or_redirect(commit_id)
626 c.branch = self.request.GET.get('branch', None)
626 c.branch = self.request.GET.get('branch', None)
627 c.f_path = f_path
627 c.f_path = f_path
628
628
629 # prev link
629 # prev link
630 try:
630 try:
631 prev_commit = c.commit.prev(c.branch)
631 prev_commit = c.commit.prev(c.branch)
632 c.prev_commit = prev_commit
632 c.prev_commit = prev_commit
633 c.url_prev = h.route_path(
633 c.url_prev = h.route_path(
634 'repo_files', repo_name=self.db_repo_name,
634 'repo_files', repo_name=self.db_repo_name,
635 commit_id=prev_commit.raw_id, f_path=f_path)
635 commit_id=prev_commit.raw_id, f_path=f_path)
636 if c.branch:
636 if c.branch:
637 c.url_prev += '?branch=%s' % c.branch
637 c.url_prev += '?branch=%s' % c.branch
638 except (CommitDoesNotExistError, VCSError):
638 except (CommitDoesNotExistError, VCSError):
639 c.url_prev = '#'
639 c.url_prev = '#'
640 c.prev_commit = EmptyCommit()
640 c.prev_commit = EmptyCommit()
641
641
642 # next link
642 # next link
643 try:
643 try:
644 next_commit = c.commit.next(c.branch)
644 next_commit = c.commit.next(c.branch)
645 c.next_commit = next_commit
645 c.next_commit = next_commit
646 c.url_next = h.route_path(
646 c.url_next = h.route_path(
647 'repo_files', repo_name=self.db_repo_name,
647 'repo_files', repo_name=self.db_repo_name,
648 commit_id=next_commit.raw_id, f_path=f_path)
648 commit_id=next_commit.raw_id, f_path=f_path)
649 if c.branch:
649 if c.branch:
650 c.url_next += '?branch=%s' % c.branch
650 c.url_next += '?branch=%s' % c.branch
651 except (CommitDoesNotExistError, VCSError):
651 except (CommitDoesNotExistError, VCSError):
652 c.url_next = '#'
652 c.url_next = '#'
653 c.next_commit = EmptyCommit()
653 c.next_commit = EmptyCommit()
654
654
655 # files or dirs
655 # files or dirs
656 try:
656 try:
657 c.file = c.commit.get_node(f_path)
657 c.file = c.commit.get_node(f_path)
658 c.file_author = True
658 c.file_author = True
659 c.file_tree = ''
659 c.file_tree = ''
660
660
661 # load file content
661 # load file content
662 if c.file.is_file():
662 if c.file.is_file():
663 c.lf_node = {}
663 c.lf_node = {}
664
664
665 has_lf_enabled = self._is_lf_enabled(self.db_repo)
665 has_lf_enabled = self._is_lf_enabled(self.db_repo)
666 if has_lf_enabled:
666 if has_lf_enabled:
667 c.lf_node = c.file.get_largefile_node()
667 c.lf_node = c.file.get_largefile_node()
668
668
669 c.file_source_page = 'true'
669 c.file_source_page = 'true'
670 c.file_last_commit = c.file.last_commit
670 c.file_last_commit = c.file.last_commit
671
671
672 c.file_size_too_big = c.file.size > c.visual.cut_off_limit_file
672 c.file_size_too_big = c.file.size > c.visual.cut_off_limit_file
673
673
674 if not (c.file_size_too_big or c.file.is_binary):
674 if not (c.file_size_too_big or c.file.is_binary):
675 if c.annotate: # annotation has precedence over renderer
675 if c.annotate: # annotation has precedence over renderer
676 c.annotated_lines = filenode_as_annotated_lines_tokens(
676 c.annotated_lines = filenode_as_annotated_lines_tokens(
677 c.file
677 c.file
678 )
678 )
679 else:
679 else:
680 c.renderer = (
680 c.renderer = (
681 c.renderer and h.renderer_from_filename(c.file.path)
681 c.renderer and h.renderer_from_filename(c.file.path)
682 )
682 )
683 if not c.renderer:
683 if not c.renderer:
684 c.lines = filenode_as_lines_tokens(c.file)
684 c.lines = filenode_as_lines_tokens(c.file)
685
685
686 _branch_name, _sha_commit_id, is_head = self._is_valid_head(
686 _branch_name, _sha_commit_id, is_head = self._is_valid_head(
687 commit_id, self.rhodecode_vcs_repo)
687 commit_id, self.rhodecode_vcs_repo)
688 c.on_branch_head = is_head
688 c.on_branch_head = is_head
689
689
690 branch = c.commit.branch if (
690 branch = c.commit.branch if (
691 c.commit.branch and '/' not in c.commit.branch) else None
691 c.commit.branch and '/' not in c.commit.branch) else None
692 c.branch_or_raw_id = branch or c.commit.raw_id
692 c.branch_or_raw_id = branch or c.commit.raw_id
693 c.branch_name = c.commit.branch or h.short_id(c.commit.raw_id)
693 c.branch_name = c.commit.branch or h.short_id(c.commit.raw_id)
694
694
695 author = c.file_last_commit.author
695 author = c.file_last_commit.author
696 c.authors = [[
696 c.authors = [[
697 h.email(author),
697 h.email(author),
698 h.person(author, 'username_or_name_or_email'),
698 h.person(author, 'username_or_name_or_email'),
699 1
699 1
700 ]]
700 ]]
701
701
702 else: # load tree content at path
702 else: # load tree content at path
703 c.file_source_page = 'false'
703 c.file_source_page = 'false'
704 c.authors = []
704 c.authors = []
705 # this loads a simple tree without metadata to speed things up
705 # this loads a simple tree without metadata to speed things up
706 # later via ajax we call repo_nodetree_full and fetch whole
706 # later via ajax we call repo_nodetree_full and fetch whole
707 c.file_tree = self._get_tree_at_commit(c, c.commit.raw_id, f_path)
707 c.file_tree = self._get_tree_at_commit(c, c.commit.raw_id, f_path)
708
708
709 c.readme_data, c.readme_file = \
710 self._get_readme_data(self.db_repo, c.visual.default_renderer,
711 c.commit.raw_id, f_path)
712
709 except RepositoryError as e:
713 except RepositoryError as e:
710 h.flash(safe_str(h.escape(e)), category='error')
714 h.flash(safe_str(h.escape(e)), category='error')
711 raise HTTPNotFound()
715 raise HTTPNotFound()
712
716
713 if self.request.environ.get('HTTP_X_PJAX'):
717 if self.request.environ.get('HTTP_X_PJAX'):
714 html = render('rhodecode:templates/files/files_pjax.mako',
718 html = render('rhodecode:templates/files/files_pjax.mako',
715 self._get_template_context(c), self.request)
719 self._get_template_context(c), self.request)
716 else:
720 else:
717 html = render('rhodecode:templates/files/files.mako',
721 html = render('rhodecode:templates/files/files.mako',
718 self._get_template_context(c), self.request)
722 self._get_template_context(c), self.request)
719 return Response(html)
723 return Response(html)
720
724
721 @HasRepoPermissionAnyDecorator(
725 @HasRepoPermissionAnyDecorator(
722 'repository.read', 'repository.write', 'repository.admin')
726 'repository.read', 'repository.write', 'repository.admin')
723 @view_config(
727 @view_config(
724 route_name='repo_files:annotated_previous', request_method='GET',
728 route_name='repo_files:annotated_previous', request_method='GET',
725 renderer=None)
729 renderer=None)
726 def repo_files_annotated_previous(self):
730 def repo_files_annotated_previous(self):
727 self.load_default_context()
731 self.load_default_context()
728
732
729 commit_id, f_path = self._get_commit_and_path()
733 commit_id, f_path = self._get_commit_and_path()
730 commit = self._get_commit_or_redirect(commit_id)
734 commit = self._get_commit_or_redirect(commit_id)
731 prev_commit_id = commit.raw_id
735 prev_commit_id = commit.raw_id
732 line_anchor = self.request.GET.get('line_anchor')
736 line_anchor = self.request.GET.get('line_anchor')
733 is_file = False
737 is_file = False
734 try:
738 try:
735 _file = commit.get_node(f_path)
739 _file = commit.get_node(f_path)
736 is_file = _file.is_file()
740 is_file = _file.is_file()
737 except (NodeDoesNotExistError, CommitDoesNotExistError, VCSError):
741 except (NodeDoesNotExistError, CommitDoesNotExistError, VCSError):
738 pass
742 pass
739
743
740 if is_file:
744 if is_file:
741 history = commit.get_path_history(f_path)
745 history = commit.get_path_history(f_path)
742 prev_commit_id = history[1].raw_id \
746 prev_commit_id = history[1].raw_id \
743 if len(history) > 1 else prev_commit_id
747 if len(history) > 1 else prev_commit_id
744 prev_url = h.route_path(
748 prev_url = h.route_path(
745 'repo_files:annotated', repo_name=self.db_repo_name,
749 'repo_files:annotated', repo_name=self.db_repo_name,
746 commit_id=prev_commit_id, f_path=f_path,
750 commit_id=prev_commit_id, f_path=f_path,
747 _anchor='L{}'.format(line_anchor))
751 _anchor='L{}'.format(line_anchor))
748
752
749 raise HTTPFound(prev_url)
753 raise HTTPFound(prev_url)
750
754
751 @LoginRequired()
755 @LoginRequired()
752 @HasRepoPermissionAnyDecorator(
756 @HasRepoPermissionAnyDecorator(
753 'repository.read', 'repository.write', 'repository.admin')
757 'repository.read', 'repository.write', 'repository.admin')
754 @view_config(
758 @view_config(
755 route_name='repo_nodetree_full', request_method='GET',
759 route_name='repo_nodetree_full', request_method='GET',
756 renderer=None, xhr=True)
760 renderer=None, xhr=True)
757 @view_config(
761 @view_config(
758 route_name='repo_nodetree_full:default_path', request_method='GET',
762 route_name='repo_nodetree_full:default_path', request_method='GET',
759 renderer=None, xhr=True)
763 renderer=None, xhr=True)
760 def repo_nodetree_full(self):
764 def repo_nodetree_full(self):
761 """
765 """
762 Returns rendered html of file tree that contains commit date,
766 Returns rendered html of file tree that contains commit date,
763 author, commit_id for the specified combination of
767 author, commit_id for the specified combination of
764 repo, commit_id and file path
768 repo, commit_id and file path
765 """
769 """
766 c = self.load_default_context()
770 c = self.load_default_context()
767
771
768 commit_id, f_path = self._get_commit_and_path()
772 commit_id, f_path = self._get_commit_and_path()
769 commit = self._get_commit_or_redirect(commit_id)
773 commit = self._get_commit_or_redirect(commit_id)
770 try:
774 try:
771 dir_node = commit.get_node(f_path)
775 dir_node = commit.get_node(f_path)
772 except RepositoryError as e:
776 except RepositoryError as e:
773 return Response('error: {}'.format(h.escape(safe_str(e))))
777 return Response('error: {}'.format(h.escape(safe_str(e))))
774
778
775 if dir_node.is_file():
779 if dir_node.is_file():
776 return Response('')
780 return Response('')
777
781
778 c.file = dir_node
782 c.file = dir_node
779 c.commit = commit
783 c.commit = commit
780
784
781 html = self._get_tree_at_commit(
785 html = self._get_tree_at_commit(
782 c, commit.raw_id, dir_node.path, full_load=True)
786 c, commit.raw_id, dir_node.path, full_load=True)
783
787
784 return Response(html)
788 return Response(html)
785
789
786 def _get_attachement_headers(self, f_path):
790 def _get_attachement_headers(self, f_path):
787 f_name = safe_str(f_path.split(Repository.NAME_SEP)[-1])
791 f_name = safe_str(f_path.split(Repository.NAME_SEP)[-1])
788 safe_path = f_name.replace('"', '\\"')
792 safe_path = f_name.replace('"', '\\"')
789 encoded_path = urllib.quote(f_name)
793 encoded_path = urllib.quote(f_name)
790
794
791 return "attachment; " \
795 return "attachment; " \
792 "filename=\"{}\"; " \
796 "filename=\"{}\"; " \
793 "filename*=UTF-8\'\'{}".format(safe_path, encoded_path)
797 "filename*=UTF-8\'\'{}".format(safe_path, encoded_path)
794
798
795 @LoginRequired()
799 @LoginRequired()
796 @HasRepoPermissionAnyDecorator(
800 @HasRepoPermissionAnyDecorator(
797 'repository.read', 'repository.write', 'repository.admin')
801 'repository.read', 'repository.write', 'repository.admin')
798 @view_config(
802 @view_config(
799 route_name='repo_file_raw', request_method='GET',
803 route_name='repo_file_raw', request_method='GET',
800 renderer=None)
804 renderer=None)
801 def repo_file_raw(self):
805 def repo_file_raw(self):
802 """
806 """
803 Action for show as raw, some mimetypes are "rendered",
807 Action for show as raw, some mimetypes are "rendered",
804 those include images, icons.
808 those include images, icons.
805 """
809 """
806 c = self.load_default_context()
810 c = self.load_default_context()
807
811
808 commit_id, f_path = self._get_commit_and_path()
812 commit_id, f_path = self._get_commit_and_path()
809 commit = self._get_commit_or_redirect(commit_id)
813 commit = self._get_commit_or_redirect(commit_id)
810 file_node = self._get_filenode_or_redirect(commit, f_path)
814 file_node = self._get_filenode_or_redirect(commit, f_path)
811
815
812 raw_mimetype_mapping = {
816 raw_mimetype_mapping = {
813 # map original mimetype to a mimetype used for "show as raw"
817 # map original mimetype to a mimetype used for "show as raw"
814 # you can also provide a content-disposition to override the
818 # you can also provide a content-disposition to override the
815 # default "attachment" disposition.
819 # default "attachment" disposition.
816 # orig_type: (new_type, new_dispo)
820 # orig_type: (new_type, new_dispo)
817
821
818 # show images inline:
822 # show images inline:
819 # Do not re-add SVG: it is unsafe and permits XSS attacks. One can
823 # Do not re-add SVG: it is unsafe and permits XSS attacks. One can
820 # for example render an SVG with javascript inside or even render
824 # for example render an SVG with javascript inside or even render
821 # HTML.
825 # HTML.
822 'image/x-icon': ('image/x-icon', 'inline'),
826 'image/x-icon': ('image/x-icon', 'inline'),
823 'image/png': ('image/png', 'inline'),
827 'image/png': ('image/png', 'inline'),
824 'image/gif': ('image/gif', 'inline'),
828 'image/gif': ('image/gif', 'inline'),
825 'image/jpeg': ('image/jpeg', 'inline'),
829 'image/jpeg': ('image/jpeg', 'inline'),
826 'application/pdf': ('application/pdf', 'inline'),
830 'application/pdf': ('application/pdf', 'inline'),
827 }
831 }
828
832
829 mimetype = file_node.mimetype
833 mimetype = file_node.mimetype
830 try:
834 try:
831 mimetype, disposition = raw_mimetype_mapping[mimetype]
835 mimetype, disposition = raw_mimetype_mapping[mimetype]
832 except KeyError:
836 except KeyError:
833 # we don't know anything special about this, handle it safely
837 # we don't know anything special about this, handle it safely
834 if file_node.is_binary:
838 if file_node.is_binary:
835 # do same as download raw for binary files
839 # do same as download raw for binary files
836 mimetype, disposition = 'application/octet-stream', 'attachment'
840 mimetype, disposition = 'application/octet-stream', 'attachment'
837 else:
841 else:
838 # do not just use the original mimetype, but force text/plain,
842 # do not just use the original mimetype, but force text/plain,
839 # otherwise it would serve text/html and that might be unsafe.
843 # otherwise it would serve text/html and that might be unsafe.
840 # Note: underlying vcs library fakes text/plain mimetype if the
844 # Note: underlying vcs library fakes text/plain mimetype if the
841 # mimetype can not be determined and it thinks it is not
845 # mimetype can not be determined and it thinks it is not
842 # binary.This might lead to erroneous text display in some
846 # binary.This might lead to erroneous text display in some
843 # cases, but helps in other cases, like with text files
847 # cases, but helps in other cases, like with text files
844 # without extension.
848 # without extension.
845 mimetype, disposition = 'text/plain', 'inline'
849 mimetype, disposition = 'text/plain', 'inline'
846
850
847 if disposition == 'attachment':
851 if disposition == 'attachment':
848 disposition = self._get_attachement_headers(f_path)
852 disposition = self._get_attachement_headers(f_path)
849
853
850 stream_content = file_node.stream_bytes()
854 stream_content = file_node.stream_bytes()
851
855
852 response = Response(app_iter=stream_content)
856 response = Response(app_iter=stream_content)
853 response.content_disposition = disposition
857 response.content_disposition = disposition
854 response.content_type = mimetype
858 response.content_type = mimetype
855
859
856 charset = self._get_default_encoding(c)
860 charset = self._get_default_encoding(c)
857 if charset:
861 if charset:
858 response.charset = charset
862 response.charset = charset
859
863
860 return response
864 return response
861
865
862 @LoginRequired()
866 @LoginRequired()
863 @HasRepoPermissionAnyDecorator(
867 @HasRepoPermissionAnyDecorator(
864 'repository.read', 'repository.write', 'repository.admin')
868 'repository.read', 'repository.write', 'repository.admin')
865 @view_config(
869 @view_config(
866 route_name='repo_file_download', request_method='GET',
870 route_name='repo_file_download', request_method='GET',
867 renderer=None)
871 renderer=None)
868 @view_config(
872 @view_config(
869 route_name='repo_file_download:legacy', request_method='GET',
873 route_name='repo_file_download:legacy', request_method='GET',
870 renderer=None)
874 renderer=None)
871 def repo_file_download(self):
875 def repo_file_download(self):
872 c = self.load_default_context()
876 c = self.load_default_context()
873
877
874 commit_id, f_path = self._get_commit_and_path()
878 commit_id, f_path = self._get_commit_and_path()
875 commit = self._get_commit_or_redirect(commit_id)
879 commit = self._get_commit_or_redirect(commit_id)
876 file_node = self._get_filenode_or_redirect(commit, f_path)
880 file_node = self._get_filenode_or_redirect(commit, f_path)
877
881
878 if self.request.GET.get('lf'):
882 if self.request.GET.get('lf'):
879 # only if lf get flag is passed, we download this file
883 # only if lf get flag is passed, we download this file
880 # as LFS/Largefile
884 # as LFS/Largefile
881 lf_node = file_node.get_largefile_node()
885 lf_node = file_node.get_largefile_node()
882 if lf_node:
886 if lf_node:
883 # overwrite our pointer with the REAL large-file
887 # overwrite our pointer with the REAL large-file
884 file_node = lf_node
888 file_node = lf_node
885
889
886 disposition = self._get_attachement_headers(f_path)
890 disposition = self._get_attachement_headers(f_path)
887
891
888 stream_content = file_node.stream_bytes()
892 stream_content = file_node.stream_bytes()
889
893
890 response = Response(app_iter=stream_content)
894 response = Response(app_iter=stream_content)
891 response.content_disposition = disposition
895 response.content_disposition = disposition
892 response.content_type = file_node.mimetype
896 response.content_type = file_node.mimetype
893
897
894 charset = self._get_default_encoding(c)
898 charset = self._get_default_encoding(c)
895 if charset:
899 if charset:
896 response.charset = charset
900 response.charset = charset
897
901
898 return response
902 return response
899
903
900 def _get_nodelist_at_commit(self, repo_name, repo_id, commit_id, f_path):
904 def _get_nodelist_at_commit(self, repo_name, repo_id, commit_id, f_path):
901
905
902 cache_seconds = safe_int(
906 cache_seconds = safe_int(
903 rhodecode.CONFIG.get('rc_cache.cache_repo.expiration_time'))
907 rhodecode.CONFIG.get('rc_cache.cache_repo.expiration_time'))
904 cache_on = cache_seconds > 0
908 cache_on = cache_seconds > 0
905 log.debug(
909 log.debug(
906 'Computing FILE SEARCH for repo_id %s commit_id `%s` and path `%s`'
910 'Computing FILE SEARCH for repo_id %s commit_id `%s` and path `%s`'
907 'with caching: %s[TTL: %ss]' % (
911 'with caching: %s[TTL: %ss]' % (
908 repo_id, commit_id, f_path, cache_on, cache_seconds or 0))
912 repo_id, commit_id, f_path, cache_on, cache_seconds or 0))
909
913
910 cache_namespace_uid = 'cache_repo.{}'.format(repo_id)
914 cache_namespace_uid = 'cache_repo.{}'.format(repo_id)
911 region = rc_cache.get_or_create_region('cache_repo', cache_namespace_uid)
915 region = rc_cache.get_or_create_region('cache_repo', cache_namespace_uid)
912
916
913 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid,
917 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid,
914 condition=cache_on)
918 condition=cache_on)
915 def compute_file_search(repo_id, commit_id, f_path):
919 def compute_file_search(repo_id, commit_id, f_path):
916 log.debug('Generating cached nodelist for repo_id:%s, %s, %s',
920 log.debug('Generating cached nodelist for repo_id:%s, %s, %s',
917 repo_id, commit_id, f_path)
921 repo_id, commit_id, f_path)
918 try:
922 try:
919 _d, _f = ScmModel().get_nodes(
923 _d, _f = ScmModel().get_nodes(
920 repo_name, commit_id, f_path, flat=False)
924 repo_name, commit_id, f_path, flat=False)
921 except (RepositoryError, CommitDoesNotExistError, Exception) as e:
925 except (RepositoryError, CommitDoesNotExistError, Exception) as e:
922 log.exception(safe_str(e))
926 log.exception(safe_str(e))
923 h.flash(safe_str(h.escape(e)), category='error')
927 h.flash(safe_str(h.escape(e)), category='error')
924 raise HTTPFound(h.route_path(
928 raise HTTPFound(h.route_path(
925 'repo_files', repo_name=self.db_repo_name,
929 'repo_files', repo_name=self.db_repo_name,
926 commit_id='tip', f_path='/'))
930 commit_id='tip', f_path='/'))
927
931
928 return _d + _f
932 return _d + _f
929
933
930 result = compute_file_search(self.db_repo.repo_id, commit_id, f_path)
934 result = compute_file_search(self.db_repo.repo_id, commit_id, f_path)
931 return filter(lambda n: self.path_filter.path_access_allowed(n['name']), result)
935 return filter(lambda n: self.path_filter.path_access_allowed(n['name']), result)
932
936
933 @LoginRequired()
937 @LoginRequired()
934 @HasRepoPermissionAnyDecorator(
938 @HasRepoPermissionAnyDecorator(
935 'repository.read', 'repository.write', 'repository.admin')
939 'repository.read', 'repository.write', 'repository.admin')
936 @view_config(
940 @view_config(
937 route_name='repo_files_nodelist', request_method='GET',
941 route_name='repo_files_nodelist', request_method='GET',
938 renderer='json_ext', xhr=True)
942 renderer='json_ext', xhr=True)
939 def repo_nodelist(self):
943 def repo_nodelist(self):
940 self.load_default_context()
944 self.load_default_context()
941
945
942 commit_id, f_path = self._get_commit_and_path()
946 commit_id, f_path = self._get_commit_and_path()
943 commit = self._get_commit_or_redirect(commit_id)
947 commit = self._get_commit_or_redirect(commit_id)
944
948
945 metadata = self._get_nodelist_at_commit(
949 metadata = self._get_nodelist_at_commit(
946 self.db_repo_name, self.db_repo.repo_id, commit.raw_id, f_path)
950 self.db_repo_name, self.db_repo.repo_id, commit.raw_id, f_path)
947 return {'nodes': metadata}
951 return {'nodes': metadata}
948
952
949 def _create_references(self, branches_or_tags, symbolic_reference, f_path, ref_type):
953 def _create_references(self, branches_or_tags, symbolic_reference, f_path, ref_type):
950 items = []
954 items = []
951 for name, commit_id in branches_or_tags.items():
955 for name, commit_id in branches_or_tags.items():
952 sym_ref = symbolic_reference(commit_id, name, f_path, ref_type)
956 sym_ref = symbolic_reference(commit_id, name, f_path, ref_type)
953 items.append((sym_ref, name, ref_type))
957 items.append((sym_ref, name, ref_type))
954 return items
958 return items
955
959
956 def _symbolic_reference(self, commit_id, name, f_path, ref_type):
960 def _symbolic_reference(self, commit_id, name, f_path, ref_type):
957 return commit_id
961 return commit_id
958
962
959 def _symbolic_reference_svn(self, commit_id, name, f_path, ref_type):
963 def _symbolic_reference_svn(self, commit_id, name, f_path, ref_type):
960 new_f_path = vcspath.join(name, f_path)
964 new_f_path = vcspath.join(name, f_path)
961 return u'%s@%s' % (new_f_path, commit_id)
965 return u'%s@%s' % (new_f_path, commit_id)
962
966
963 def _get_node_history(self, commit_obj, f_path, commits=None):
967 def _get_node_history(self, commit_obj, f_path, commits=None):
964 """
968 """
965 get commit history for given node
969 get commit history for given node
966
970
967 :param commit_obj: commit to calculate history
971 :param commit_obj: commit to calculate history
968 :param f_path: path for node to calculate history for
972 :param f_path: path for node to calculate history for
969 :param commits: if passed don't calculate history and take
973 :param commits: if passed don't calculate history and take
970 commits defined in this list
974 commits defined in this list
971 """
975 """
972 _ = self.request.translate
976 _ = self.request.translate
973
977
974 # calculate history based on tip
978 # calculate history based on tip
975 tip = self.rhodecode_vcs_repo.get_commit()
979 tip = self.rhodecode_vcs_repo.get_commit()
976 if commits is None:
980 if commits is None:
977 pre_load = ["author", "branch"]
981 pre_load = ["author", "branch"]
978 try:
982 try:
979 commits = tip.get_path_history(f_path, pre_load=pre_load)
983 commits = tip.get_path_history(f_path, pre_load=pre_load)
980 except (NodeDoesNotExistError, CommitError):
984 except (NodeDoesNotExistError, CommitError):
981 # this node is not present at tip!
985 # this node is not present at tip!
982 commits = commit_obj.get_path_history(f_path, pre_load=pre_load)
986 commits = commit_obj.get_path_history(f_path, pre_load=pre_load)
983
987
984 history = []
988 history = []
985 commits_group = ([], _("Changesets"))
989 commits_group = ([], _("Changesets"))
986 for commit in commits:
990 for commit in commits:
987 branch = ' (%s)' % commit.branch if commit.branch else ''
991 branch = ' (%s)' % commit.branch if commit.branch else ''
988 n_desc = 'r%s:%s%s' % (commit.idx, commit.short_id, branch)
992 n_desc = 'r%s:%s%s' % (commit.idx, commit.short_id, branch)
989 commits_group[0].append((commit.raw_id, n_desc, 'sha'))
993 commits_group[0].append((commit.raw_id, n_desc, 'sha'))
990 history.append(commits_group)
994 history.append(commits_group)
991
995
992 symbolic_reference = self._symbolic_reference
996 symbolic_reference = self._symbolic_reference
993
997
994 if self.rhodecode_vcs_repo.alias == 'svn':
998 if self.rhodecode_vcs_repo.alias == 'svn':
995 adjusted_f_path = RepoFilesView.adjust_file_path_for_svn(
999 adjusted_f_path = RepoFilesView.adjust_file_path_for_svn(
996 f_path, self.rhodecode_vcs_repo)
1000 f_path, self.rhodecode_vcs_repo)
997 if adjusted_f_path != f_path:
1001 if adjusted_f_path != f_path:
998 log.debug(
1002 log.debug(
999 'Recognized svn tag or branch in file "%s", using svn '
1003 'Recognized svn tag or branch in file "%s", using svn '
1000 'specific symbolic references', f_path)
1004 'specific symbolic references', f_path)
1001 f_path = adjusted_f_path
1005 f_path = adjusted_f_path
1002 symbolic_reference = self._symbolic_reference_svn
1006 symbolic_reference = self._symbolic_reference_svn
1003
1007
1004 branches = self._create_references(
1008 branches = self._create_references(
1005 self.rhodecode_vcs_repo.branches, symbolic_reference, f_path, 'branch')
1009 self.rhodecode_vcs_repo.branches, symbolic_reference, f_path, 'branch')
1006 branches_group = (branches, _("Branches"))
1010 branches_group = (branches, _("Branches"))
1007
1011
1008 tags = self._create_references(
1012 tags = self._create_references(
1009 self.rhodecode_vcs_repo.tags, symbolic_reference, f_path, 'tag')
1013 self.rhodecode_vcs_repo.tags, symbolic_reference, f_path, 'tag')
1010 tags_group = (tags, _("Tags"))
1014 tags_group = (tags, _("Tags"))
1011
1015
1012 history.append(branches_group)
1016 history.append(branches_group)
1013 history.append(tags_group)
1017 history.append(tags_group)
1014
1018
1015 return history, commits
1019 return history, commits
1016
1020
1017 @LoginRequired()
1021 @LoginRequired()
1018 @HasRepoPermissionAnyDecorator(
1022 @HasRepoPermissionAnyDecorator(
1019 'repository.read', 'repository.write', 'repository.admin')
1023 'repository.read', 'repository.write', 'repository.admin')
1020 @view_config(
1024 @view_config(
1021 route_name='repo_file_history', request_method='GET',
1025 route_name='repo_file_history', request_method='GET',
1022 renderer='json_ext')
1026 renderer='json_ext')
1023 def repo_file_history(self):
1027 def repo_file_history(self):
1024 self.load_default_context()
1028 self.load_default_context()
1025
1029
1026 commit_id, f_path = self._get_commit_and_path()
1030 commit_id, f_path = self._get_commit_and_path()
1027 commit = self._get_commit_or_redirect(commit_id)
1031 commit = self._get_commit_or_redirect(commit_id)
1028 file_node = self._get_filenode_or_redirect(commit, f_path)
1032 file_node = self._get_filenode_or_redirect(commit, f_path)
1029
1033
1030 if file_node.is_file():
1034 if file_node.is_file():
1031 file_history, _hist = self._get_node_history(commit, f_path)
1035 file_history, _hist = self._get_node_history(commit, f_path)
1032
1036
1033 res = []
1037 res = []
1034 for obj in file_history:
1038 for obj in file_history:
1035 res.append({
1039 res.append({
1036 'text': obj[1],
1040 'text': obj[1],
1037 'children': [{'id': o[0], 'text': o[1], 'type': o[2]} for o in obj[0]]
1041 'children': [{'id': o[0], 'text': o[1], 'type': o[2]} for o in obj[0]]
1038 })
1042 })
1039
1043
1040 data = {
1044 data = {
1041 'more': False,
1045 'more': False,
1042 'results': res
1046 'results': res
1043 }
1047 }
1044 return data
1048 return data
1045
1049
1046 log.warning('Cannot fetch history for directory')
1050 log.warning('Cannot fetch history for directory')
1047 raise HTTPBadRequest()
1051 raise HTTPBadRequest()
1048
1052
1049 @LoginRequired()
1053 @LoginRequired()
1050 @HasRepoPermissionAnyDecorator(
1054 @HasRepoPermissionAnyDecorator(
1051 'repository.read', 'repository.write', 'repository.admin')
1055 'repository.read', 'repository.write', 'repository.admin')
1052 @view_config(
1056 @view_config(
1053 route_name='repo_file_authors', request_method='GET',
1057 route_name='repo_file_authors', request_method='GET',
1054 renderer='rhodecode:templates/files/file_authors_box.mako')
1058 renderer='rhodecode:templates/files/file_authors_box.mako')
1055 def repo_file_authors(self):
1059 def repo_file_authors(self):
1056 c = self.load_default_context()
1060 c = self.load_default_context()
1057
1061
1058 commit_id, f_path = self._get_commit_and_path()
1062 commit_id, f_path = self._get_commit_and_path()
1059 commit = self._get_commit_or_redirect(commit_id)
1063 commit = self._get_commit_or_redirect(commit_id)
1060 file_node = self._get_filenode_or_redirect(commit, f_path)
1064 file_node = self._get_filenode_or_redirect(commit, f_path)
1061
1065
1062 if not file_node.is_file():
1066 if not file_node.is_file():
1063 raise HTTPBadRequest()
1067 raise HTTPBadRequest()
1064
1068
1065 c.file_last_commit = file_node.last_commit
1069 c.file_last_commit = file_node.last_commit
1066 if self.request.GET.get('annotate') == '1':
1070 if self.request.GET.get('annotate') == '1':
1067 # use _hist from annotation if annotation mode is on
1071 # use _hist from annotation if annotation mode is on
1068 commit_ids = set(x[1] for x in file_node.annotate)
1072 commit_ids = set(x[1] for x in file_node.annotate)
1069 _hist = (
1073 _hist = (
1070 self.rhodecode_vcs_repo.get_commit(commit_id)
1074 self.rhodecode_vcs_repo.get_commit(commit_id)
1071 for commit_id in commit_ids)
1075 for commit_id in commit_ids)
1072 else:
1076 else:
1073 _f_history, _hist = self._get_node_history(commit, f_path)
1077 _f_history, _hist = self._get_node_history(commit, f_path)
1074 c.file_author = False
1078 c.file_author = False
1075
1079
1076 unique = collections.OrderedDict()
1080 unique = collections.OrderedDict()
1077 for commit in _hist:
1081 for commit in _hist:
1078 author = commit.author
1082 author = commit.author
1079 if author not in unique:
1083 if author not in unique:
1080 unique[commit.author] = [
1084 unique[commit.author] = [
1081 h.email(author),
1085 h.email(author),
1082 h.person(author, 'username_or_name_or_email'),
1086 h.person(author, 'username_or_name_or_email'),
1083 1 # counter
1087 1 # counter
1084 ]
1088 ]
1085
1089
1086 else:
1090 else:
1087 # increase counter
1091 # increase counter
1088 unique[commit.author][2] += 1
1092 unique[commit.author][2] += 1
1089
1093
1090 c.authors = [val for val in unique.values()]
1094 c.authors = [val for val in unique.values()]
1091
1095
1092 return self._get_template_context(c)
1096 return self._get_template_context(c)
1093
1097
1094 @LoginRequired()
1098 @LoginRequired()
1095 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1099 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1096 @view_config(
1100 @view_config(
1097 route_name='repo_files_remove_file', request_method='GET',
1101 route_name='repo_files_remove_file', request_method='GET',
1098 renderer='rhodecode:templates/files/files_delete.mako')
1102 renderer='rhodecode:templates/files/files_delete.mako')
1099 def repo_files_remove_file(self):
1103 def repo_files_remove_file(self):
1100 _ = self.request.translate
1104 _ = self.request.translate
1101 c = self.load_default_context()
1105 c = self.load_default_context()
1102 commit_id, f_path = self._get_commit_and_path()
1106 commit_id, f_path = self._get_commit_and_path()
1103
1107
1104 self._ensure_not_locked()
1108 self._ensure_not_locked()
1105 _branch_name, _sha_commit_id, is_head = \
1109 _branch_name, _sha_commit_id, is_head = \
1106 self._is_valid_head(commit_id, self.rhodecode_vcs_repo)
1110 self._is_valid_head(commit_id, self.rhodecode_vcs_repo)
1107
1111
1108 self.forbid_non_head(is_head, f_path)
1112 self.forbid_non_head(is_head, f_path)
1109 self.check_branch_permission(_branch_name)
1113 self.check_branch_permission(_branch_name)
1110
1114
1111 c.commit = self._get_commit_or_redirect(commit_id)
1115 c.commit = self._get_commit_or_redirect(commit_id)
1112 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1116 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1113
1117
1114 c.default_message = _(
1118 c.default_message = _(
1115 'Deleted file {} via RhodeCode Enterprise').format(f_path)
1119 'Deleted file {} via RhodeCode Enterprise').format(f_path)
1116 c.f_path = f_path
1120 c.f_path = f_path
1117
1121
1118 return self._get_template_context(c)
1122 return self._get_template_context(c)
1119
1123
1120 @LoginRequired()
1124 @LoginRequired()
1121 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1125 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1122 @CSRFRequired()
1126 @CSRFRequired()
1123 @view_config(
1127 @view_config(
1124 route_name='repo_files_delete_file', request_method='POST',
1128 route_name='repo_files_delete_file', request_method='POST',
1125 renderer=None)
1129 renderer=None)
1126 def repo_files_delete_file(self):
1130 def repo_files_delete_file(self):
1127 _ = self.request.translate
1131 _ = self.request.translate
1128
1132
1129 c = self.load_default_context()
1133 c = self.load_default_context()
1130 commit_id, f_path = self._get_commit_and_path()
1134 commit_id, f_path = self._get_commit_and_path()
1131
1135
1132 self._ensure_not_locked()
1136 self._ensure_not_locked()
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 self.forbid_non_head(is_head, f_path)
1140 self.forbid_non_head(is_head, f_path)
1137 self.check_branch_permission(_branch_name)
1141 self.check_branch_permission(_branch_name)
1138
1142
1139 c.commit = self._get_commit_or_redirect(commit_id)
1143 c.commit = self._get_commit_or_redirect(commit_id)
1140 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1144 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1141
1145
1142 c.default_message = _(
1146 c.default_message = _(
1143 'Deleted file {} via RhodeCode Enterprise').format(f_path)
1147 'Deleted file {} via RhodeCode Enterprise').format(f_path)
1144 c.f_path = f_path
1148 c.f_path = f_path
1145 node_path = f_path
1149 node_path = f_path
1146 author = self._rhodecode_db_user.full_contact
1150 author = self._rhodecode_db_user.full_contact
1147 message = self.request.POST.get('message') or c.default_message
1151 message = self.request.POST.get('message') or c.default_message
1148 try:
1152 try:
1149 nodes = {
1153 nodes = {
1150 node_path: {
1154 node_path: {
1151 'content': ''
1155 'content': ''
1152 }
1156 }
1153 }
1157 }
1154 ScmModel().delete_nodes(
1158 ScmModel().delete_nodes(
1155 user=self._rhodecode_db_user.user_id, repo=self.db_repo,
1159 user=self._rhodecode_db_user.user_id, repo=self.db_repo,
1156 message=message,
1160 message=message,
1157 nodes=nodes,
1161 nodes=nodes,
1158 parent_commit=c.commit,
1162 parent_commit=c.commit,
1159 author=author,
1163 author=author,
1160 )
1164 )
1161
1165
1162 h.flash(
1166 h.flash(
1163 _('Successfully deleted file `{}`').format(
1167 _('Successfully deleted file `{}`').format(
1164 h.escape(f_path)), category='success')
1168 h.escape(f_path)), category='success')
1165 except Exception:
1169 except Exception:
1166 log.exception('Error during commit operation')
1170 log.exception('Error during commit operation')
1167 h.flash(_('Error occurred during commit'), category='error')
1171 h.flash(_('Error occurred during commit'), category='error')
1168 raise HTTPFound(
1172 raise HTTPFound(
1169 h.route_path('repo_commit', repo_name=self.db_repo_name,
1173 h.route_path('repo_commit', repo_name=self.db_repo_name,
1170 commit_id='tip'))
1174 commit_id='tip'))
1171
1175
1172 @LoginRequired()
1176 @LoginRequired()
1173 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1177 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1174 @view_config(
1178 @view_config(
1175 route_name='repo_files_edit_file', request_method='GET',
1179 route_name='repo_files_edit_file', request_method='GET',
1176 renderer='rhodecode:templates/files/files_edit.mako')
1180 renderer='rhodecode:templates/files/files_edit.mako')
1177 def repo_files_edit_file(self):
1181 def repo_files_edit_file(self):
1178 _ = self.request.translate
1182 _ = self.request.translate
1179 c = self.load_default_context()
1183 c = self.load_default_context()
1180 commit_id, f_path = self._get_commit_and_path()
1184 commit_id, f_path = self._get_commit_and_path()
1181
1185
1182 self._ensure_not_locked()
1186 self._ensure_not_locked()
1183 _branch_name, _sha_commit_id, is_head = \
1187 _branch_name, _sha_commit_id, is_head = \
1184 self._is_valid_head(commit_id, self.rhodecode_vcs_repo)
1188 self._is_valid_head(commit_id, self.rhodecode_vcs_repo)
1185
1189
1186 self.forbid_non_head(is_head, f_path, commit_id=commit_id)
1190 self.forbid_non_head(is_head, f_path, commit_id=commit_id)
1187 self.check_branch_permission(_branch_name, commit_id=commit_id)
1191 self.check_branch_permission(_branch_name, commit_id=commit_id)
1188
1192
1189 c.commit = self._get_commit_or_redirect(commit_id)
1193 c.commit = self._get_commit_or_redirect(commit_id)
1190 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1194 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1191
1195
1192 if c.file.is_binary:
1196 if c.file.is_binary:
1193 files_url = h.route_path(
1197 files_url = h.route_path(
1194 'repo_files',
1198 'repo_files',
1195 repo_name=self.db_repo_name,
1199 repo_name=self.db_repo_name,
1196 commit_id=c.commit.raw_id, f_path=f_path)
1200 commit_id=c.commit.raw_id, f_path=f_path)
1197 raise HTTPFound(files_url)
1201 raise HTTPFound(files_url)
1198
1202
1199 c.default_message = _('Edited file {} via RhodeCode Enterprise').format(f_path)
1203 c.default_message = _('Edited file {} via RhodeCode Enterprise').format(f_path)
1200 c.f_path = f_path
1204 c.f_path = f_path
1201
1205
1202 return self._get_template_context(c)
1206 return self._get_template_context(c)
1203
1207
1204 @LoginRequired()
1208 @LoginRequired()
1205 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1209 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1206 @CSRFRequired()
1210 @CSRFRequired()
1207 @view_config(
1211 @view_config(
1208 route_name='repo_files_update_file', request_method='POST',
1212 route_name='repo_files_update_file', request_method='POST',
1209 renderer=None)
1213 renderer=None)
1210 def repo_files_update_file(self):
1214 def repo_files_update_file(self):
1211 _ = self.request.translate
1215 _ = self.request.translate
1212 c = self.load_default_context()
1216 c = self.load_default_context()
1213 commit_id, f_path = self._get_commit_and_path()
1217 commit_id, f_path = self._get_commit_and_path()
1214
1218
1215 self._ensure_not_locked()
1219 self._ensure_not_locked()
1216
1220
1217 c.commit = self._get_commit_or_redirect(commit_id)
1221 c.commit = self._get_commit_or_redirect(commit_id)
1218 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1222 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1219
1223
1220 if c.file.is_binary:
1224 if c.file.is_binary:
1221 raise HTTPFound(h.route_path('repo_files', repo_name=self.db_repo_name,
1225 raise HTTPFound(h.route_path('repo_files', repo_name=self.db_repo_name,
1222 commit_id=c.commit.raw_id, f_path=f_path))
1226 commit_id=c.commit.raw_id, f_path=f_path))
1223
1227
1224 _branch_name, _sha_commit_id, is_head = \
1228 _branch_name, _sha_commit_id, is_head = \
1225 self._is_valid_head(commit_id, self.rhodecode_vcs_repo)
1229 self._is_valid_head(commit_id, self.rhodecode_vcs_repo)
1226
1230
1227 self.forbid_non_head(is_head, f_path, commit_id=commit_id)
1231 self.forbid_non_head(is_head, f_path, commit_id=commit_id)
1228 self.check_branch_permission(_branch_name, commit_id=commit_id)
1232 self.check_branch_permission(_branch_name, commit_id=commit_id)
1229
1233
1230 c.default_message = _('Edited file {} via RhodeCode Enterprise').format(f_path)
1234 c.default_message = _('Edited file {} via RhodeCode Enterprise').format(f_path)
1231 c.f_path = f_path
1235 c.f_path = f_path
1232
1236
1233 old_content = c.file.content
1237 old_content = c.file.content
1234 sl = old_content.splitlines(1)
1238 sl = old_content.splitlines(1)
1235 first_line = sl[0] if sl else ''
1239 first_line = sl[0] if sl else ''
1236
1240
1237 r_post = self.request.POST
1241 r_post = self.request.POST
1238 # line endings: 0 - Unix, 1 - Mac, 2 - DOS
1242 # line endings: 0 - Unix, 1 - Mac, 2 - DOS
1239 line_ending_mode = detect_mode(first_line, 0)
1243 line_ending_mode = detect_mode(first_line, 0)
1240 content = convert_line_endings(r_post.get('content', ''), line_ending_mode)
1244 content = convert_line_endings(r_post.get('content', ''), line_ending_mode)
1241
1245
1242 message = r_post.get('message') or c.default_message
1246 message = r_post.get('message') or c.default_message
1243 org_node_path = c.file.unicode_path
1247 org_node_path = c.file.unicode_path
1244 filename = r_post['filename']
1248 filename = r_post['filename']
1245
1249
1246 root_path = c.file.dir_path
1250 root_path = c.file.dir_path
1247 pure_path = self.create_pure_path(root_path, filename)
1251 pure_path = self.create_pure_path(root_path, filename)
1248 node_path = safe_unicode(bytes(pure_path))
1252 node_path = safe_unicode(bytes(pure_path))
1249
1253
1250 default_redirect_url = h.route_path('repo_commit', repo_name=self.db_repo_name,
1254 default_redirect_url = h.route_path('repo_commit', repo_name=self.db_repo_name,
1251 commit_id=commit_id)
1255 commit_id=commit_id)
1252 if content == old_content and node_path == org_node_path:
1256 if content == old_content and node_path == org_node_path:
1253 h.flash(_('No changes detected on {}').format(h.escape(org_node_path)),
1257 h.flash(_('No changes detected on {}').format(h.escape(org_node_path)),
1254 category='warning')
1258 category='warning')
1255 raise HTTPFound(default_redirect_url)
1259 raise HTTPFound(default_redirect_url)
1256
1260
1257 try:
1261 try:
1258 mapping = {
1262 mapping = {
1259 org_node_path: {
1263 org_node_path: {
1260 'org_filename': org_node_path,
1264 'org_filename': org_node_path,
1261 'filename': node_path,
1265 'filename': node_path,
1262 'content': content,
1266 'content': content,
1263 'lexer': '',
1267 'lexer': '',
1264 'op': 'mod',
1268 'op': 'mod',
1265 'mode': c.file.mode
1269 'mode': c.file.mode
1266 }
1270 }
1267 }
1271 }
1268
1272
1269 commit = ScmModel().update_nodes(
1273 commit = ScmModel().update_nodes(
1270 user=self._rhodecode_db_user.user_id,
1274 user=self._rhodecode_db_user.user_id,
1271 repo=self.db_repo,
1275 repo=self.db_repo,
1272 message=message,
1276 message=message,
1273 nodes=mapping,
1277 nodes=mapping,
1274 parent_commit=c.commit,
1278 parent_commit=c.commit,
1275 )
1279 )
1276
1280
1277 h.flash(_('Successfully committed changes to file `{}`').format(
1281 h.flash(_('Successfully committed changes to file `{}`').format(
1278 h.escape(f_path)), category='success')
1282 h.escape(f_path)), category='success')
1279 default_redirect_url = h.route_path(
1283 default_redirect_url = h.route_path(
1280 'repo_commit', repo_name=self.db_repo_name, commit_id=commit.raw_id)
1284 'repo_commit', repo_name=self.db_repo_name, commit_id=commit.raw_id)
1281
1285
1282 except Exception:
1286 except Exception:
1283 log.exception('Error occurred during commit')
1287 log.exception('Error occurred during commit')
1284 h.flash(_('Error occurred during commit'), category='error')
1288 h.flash(_('Error occurred during commit'), category='error')
1285
1289
1286 raise HTTPFound(default_redirect_url)
1290 raise HTTPFound(default_redirect_url)
1287
1291
1288 @LoginRequired()
1292 @LoginRequired()
1289 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1293 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1290 @view_config(
1294 @view_config(
1291 route_name='repo_files_add_file', request_method='GET',
1295 route_name='repo_files_add_file', request_method='GET',
1292 renderer='rhodecode:templates/files/files_add.mako')
1296 renderer='rhodecode:templates/files/files_add.mako')
1293 @view_config(
1297 @view_config(
1294 route_name='repo_files_upload_file', request_method='GET',
1298 route_name='repo_files_upload_file', request_method='GET',
1295 renderer='rhodecode:templates/files/files_upload.mako')
1299 renderer='rhodecode:templates/files/files_upload.mako')
1296 def repo_files_add_file(self):
1300 def repo_files_add_file(self):
1297 _ = self.request.translate
1301 _ = self.request.translate
1298 c = self.load_default_context()
1302 c = self.load_default_context()
1299 commit_id, f_path = self._get_commit_and_path()
1303 commit_id, f_path = self._get_commit_and_path()
1300
1304
1301 self._ensure_not_locked()
1305 self._ensure_not_locked()
1302
1306
1303 c.commit = self._get_commit_or_redirect(commit_id, redirect_after=False)
1307 c.commit = self._get_commit_or_redirect(commit_id, redirect_after=False)
1304 if c.commit is None:
1308 if c.commit is None:
1305 c.commit = EmptyCommit(alias=self.rhodecode_vcs_repo.alias)
1309 c.commit = EmptyCommit(alias=self.rhodecode_vcs_repo.alias)
1306
1310
1307 if self.rhodecode_vcs_repo.is_empty():
1311 if self.rhodecode_vcs_repo.is_empty():
1308 # for empty repository we cannot check for current branch, we rely on
1312 # for empty repository we cannot check for current branch, we rely on
1309 # c.commit.branch instead
1313 # c.commit.branch instead
1310 _branch_name, _sha_commit_id, is_head = c.commit.branch, '', True
1314 _branch_name, _sha_commit_id, is_head = c.commit.branch, '', True
1311 else:
1315 else:
1312 _branch_name, _sha_commit_id, is_head = \
1316 _branch_name, _sha_commit_id, is_head = \
1313 self._is_valid_head(commit_id, self.rhodecode_vcs_repo)
1317 self._is_valid_head(commit_id, self.rhodecode_vcs_repo)
1314
1318
1315 self.forbid_non_head(is_head, f_path, commit_id=commit_id)
1319 self.forbid_non_head(is_head, f_path, commit_id=commit_id)
1316 self.check_branch_permission(_branch_name, commit_id=commit_id)
1320 self.check_branch_permission(_branch_name, commit_id=commit_id)
1317
1321
1318 c.default_message = (_('Added file via RhodeCode Enterprise'))
1322 c.default_message = (_('Added file via RhodeCode Enterprise'))
1319 c.f_path = f_path.lstrip('/') # ensure not relative path
1323 c.f_path = f_path.lstrip('/') # ensure not relative path
1320
1324
1321 return self._get_template_context(c)
1325 return self._get_template_context(c)
1322
1326
1323 @LoginRequired()
1327 @LoginRequired()
1324 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1328 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1325 @CSRFRequired()
1329 @CSRFRequired()
1326 @view_config(
1330 @view_config(
1327 route_name='repo_files_create_file', request_method='POST',
1331 route_name='repo_files_create_file', request_method='POST',
1328 renderer=None)
1332 renderer=None)
1329 def repo_files_create_file(self):
1333 def repo_files_create_file(self):
1330 _ = self.request.translate
1334 _ = self.request.translate
1331 c = self.load_default_context()
1335 c = self.load_default_context()
1332 commit_id, f_path = self._get_commit_and_path()
1336 commit_id, f_path = self._get_commit_and_path()
1333
1337
1334 self._ensure_not_locked()
1338 self._ensure_not_locked()
1335
1339
1336 c.commit = self._get_commit_or_redirect(commit_id, redirect_after=False)
1340 c.commit = self._get_commit_or_redirect(commit_id, redirect_after=False)
1337 if c.commit is None:
1341 if c.commit is None:
1338 c.commit = EmptyCommit(alias=self.rhodecode_vcs_repo.alias)
1342 c.commit = EmptyCommit(alias=self.rhodecode_vcs_repo.alias)
1339
1343
1340 # calculate redirect URL
1344 # calculate redirect URL
1341 if self.rhodecode_vcs_repo.is_empty():
1345 if self.rhodecode_vcs_repo.is_empty():
1342 default_redirect_url = h.route_path(
1346 default_redirect_url = h.route_path(
1343 'repo_summary', repo_name=self.db_repo_name)
1347 'repo_summary', repo_name=self.db_repo_name)
1344 else:
1348 else:
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='tip')
1350 'repo_commit', repo_name=self.db_repo_name, commit_id='tip')
1347
1351
1348 if self.rhodecode_vcs_repo.is_empty():
1352 if self.rhodecode_vcs_repo.is_empty():
1349 # for empty repository we cannot check for current branch, we rely on
1353 # for empty repository we cannot check for current branch, we rely on
1350 # c.commit.branch instead
1354 # c.commit.branch instead
1351 _branch_name, _sha_commit_id, is_head = c.commit.branch, '', True
1355 _branch_name, _sha_commit_id, is_head = c.commit.branch, '', True
1352 else:
1356 else:
1353 _branch_name, _sha_commit_id, is_head = \
1357 _branch_name, _sha_commit_id, is_head = \
1354 self._is_valid_head(commit_id, self.rhodecode_vcs_repo)
1358 self._is_valid_head(commit_id, self.rhodecode_vcs_repo)
1355
1359
1356 self.forbid_non_head(is_head, f_path, commit_id=commit_id)
1360 self.forbid_non_head(is_head, f_path, commit_id=commit_id)
1357 self.check_branch_permission(_branch_name, commit_id=commit_id)
1361 self.check_branch_permission(_branch_name, commit_id=commit_id)
1358
1362
1359 c.default_message = (_('Added file via RhodeCode Enterprise'))
1363 c.default_message = (_('Added file via RhodeCode Enterprise'))
1360 c.f_path = f_path
1364 c.f_path = f_path
1361
1365
1362 r_post = self.request.POST
1366 r_post = self.request.POST
1363 message = r_post.get('message') or c.default_message
1367 message = r_post.get('message') or c.default_message
1364 filename = r_post.get('filename')
1368 filename = r_post.get('filename')
1365 unix_mode = 0
1369 unix_mode = 0
1366 content = convert_line_endings(r_post.get('content', ''), unix_mode)
1370 content = convert_line_endings(r_post.get('content', ''), unix_mode)
1367
1371
1368 if not filename:
1372 if not filename:
1369 # If there's no commit, redirect to repo summary
1373 # If there's no commit, redirect to repo summary
1370 if type(c.commit) is EmptyCommit:
1374 if type(c.commit) is EmptyCommit:
1371 redirect_url = h.route_path(
1375 redirect_url = h.route_path(
1372 'repo_summary', repo_name=self.db_repo_name)
1376 'repo_summary', repo_name=self.db_repo_name)
1373 else:
1377 else:
1374 redirect_url = default_redirect_url
1378 redirect_url = default_redirect_url
1375 h.flash(_('No filename specified'), category='warning')
1379 h.flash(_('No filename specified'), category='warning')
1376 raise HTTPFound(redirect_url)
1380 raise HTTPFound(redirect_url)
1377
1381
1378 root_path = f_path
1382 root_path = f_path
1379 pure_path = self.create_pure_path(root_path, filename)
1383 pure_path = self.create_pure_path(root_path, filename)
1380 node_path = safe_unicode(bytes(pure_path).lstrip('/'))
1384 node_path = safe_unicode(bytes(pure_path).lstrip('/'))
1381
1385
1382 author = self._rhodecode_db_user.full_contact
1386 author = self._rhodecode_db_user.full_contact
1383 nodes = {
1387 nodes = {
1384 node_path: {
1388 node_path: {
1385 'content': content
1389 'content': content
1386 }
1390 }
1387 }
1391 }
1388
1392
1389 try:
1393 try:
1390
1394
1391 commit = ScmModel().create_nodes(
1395 commit = ScmModel().create_nodes(
1392 user=self._rhodecode_db_user.user_id,
1396 user=self._rhodecode_db_user.user_id,
1393 repo=self.db_repo,
1397 repo=self.db_repo,
1394 message=message,
1398 message=message,
1395 nodes=nodes,
1399 nodes=nodes,
1396 parent_commit=c.commit,
1400 parent_commit=c.commit,
1397 author=author,
1401 author=author,
1398 )
1402 )
1399
1403
1400 h.flash(_('Successfully committed new file `{}`').format(
1404 h.flash(_('Successfully committed new file `{}`').format(
1401 h.escape(node_path)), category='success')
1405 h.escape(node_path)), category='success')
1402
1406
1403 default_redirect_url = h.route_path(
1407 default_redirect_url = h.route_path(
1404 'repo_commit', repo_name=self.db_repo_name, commit_id=commit.raw_id)
1408 'repo_commit', repo_name=self.db_repo_name, commit_id=commit.raw_id)
1405
1409
1406 except NonRelativePathError:
1410 except NonRelativePathError:
1407 log.exception('Non Relative path found')
1411 log.exception('Non Relative path found')
1408 h.flash(_('The location specified must be a relative path and must not '
1412 h.flash(_('The location specified must be a relative path and must not '
1409 'contain .. in the path'), category='warning')
1413 'contain .. in the path'), category='warning')
1410 raise HTTPFound(default_redirect_url)
1414 raise HTTPFound(default_redirect_url)
1411 except (NodeError, NodeAlreadyExistsError) as e:
1415 except (NodeError, NodeAlreadyExistsError) as e:
1412 h.flash(_(h.escape(e)), category='error')
1416 h.flash(_(h.escape(e)), category='error')
1413 except Exception:
1417 except Exception:
1414 log.exception('Error occurred during commit')
1418 log.exception('Error occurred during commit')
1415 h.flash(_('Error occurred during commit'), category='error')
1419 h.flash(_('Error occurred during commit'), category='error')
1416
1420
1417 raise HTTPFound(default_redirect_url)
1421 raise HTTPFound(default_redirect_url)
1418
1422
1419 @LoginRequired()
1423 @LoginRequired()
1420 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1424 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1421 @CSRFRequired()
1425 @CSRFRequired()
1422 @view_config(
1426 @view_config(
1423 route_name='repo_files_upload_file', request_method='POST',
1427 route_name='repo_files_upload_file', request_method='POST',
1424 renderer='json_ext')
1428 renderer='json_ext')
1425 def repo_files_upload_file(self):
1429 def repo_files_upload_file(self):
1426 _ = self.request.translate
1430 _ = self.request.translate
1427 c = self.load_default_context()
1431 c = self.load_default_context()
1428 commit_id, f_path = self._get_commit_and_path()
1432 commit_id, f_path = self._get_commit_and_path()
1429
1433
1430 self._ensure_not_locked()
1434 self._ensure_not_locked()
1431
1435
1432 c.commit = self._get_commit_or_redirect(commit_id, redirect_after=False)
1436 c.commit = self._get_commit_or_redirect(commit_id, redirect_after=False)
1433 if c.commit is None:
1437 if c.commit is None:
1434 c.commit = EmptyCommit(alias=self.rhodecode_vcs_repo.alias)
1438 c.commit = EmptyCommit(alias=self.rhodecode_vcs_repo.alias)
1435
1439
1436 # calculate redirect URL
1440 # calculate redirect URL
1437 if self.rhodecode_vcs_repo.is_empty():
1441 if self.rhodecode_vcs_repo.is_empty():
1438 default_redirect_url = h.route_path(
1442 default_redirect_url = h.route_path(
1439 'repo_summary', repo_name=self.db_repo_name)
1443 'repo_summary', repo_name=self.db_repo_name)
1440 else:
1444 else:
1441 default_redirect_url = h.route_path(
1445 default_redirect_url = h.route_path(
1442 'repo_commit', repo_name=self.db_repo_name, commit_id='tip')
1446 'repo_commit', repo_name=self.db_repo_name, commit_id='tip')
1443
1447
1444 if self.rhodecode_vcs_repo.is_empty():
1448 if self.rhodecode_vcs_repo.is_empty():
1445 # for empty repository we cannot check for current branch, we rely on
1449 # for empty repository we cannot check for current branch, we rely on
1446 # c.commit.branch instead
1450 # c.commit.branch instead
1447 _branch_name, _sha_commit_id, is_head = c.commit.branch, '', True
1451 _branch_name, _sha_commit_id, is_head = c.commit.branch, '', True
1448 else:
1452 else:
1449 _branch_name, _sha_commit_id, is_head = \
1453 _branch_name, _sha_commit_id, is_head = \
1450 self._is_valid_head(commit_id, self.rhodecode_vcs_repo)
1454 self._is_valid_head(commit_id, self.rhodecode_vcs_repo)
1451
1455
1452 error = self.forbid_non_head(is_head, f_path, json_mode=True)
1456 error = self.forbid_non_head(is_head, f_path, json_mode=True)
1453 if error:
1457 if error:
1454 return {
1458 return {
1455 'error': error,
1459 'error': error,
1456 'redirect_url': default_redirect_url
1460 'redirect_url': default_redirect_url
1457 }
1461 }
1458 error = self.check_branch_permission(_branch_name, json_mode=True)
1462 error = self.check_branch_permission(_branch_name, json_mode=True)
1459 if error:
1463 if error:
1460 return {
1464 return {
1461 'error': error,
1465 'error': error,
1462 'redirect_url': default_redirect_url
1466 'redirect_url': default_redirect_url
1463 }
1467 }
1464
1468
1465 c.default_message = (_('Uploaded file via RhodeCode Enterprise'))
1469 c.default_message = (_('Uploaded file via RhodeCode Enterprise'))
1466 c.f_path = f_path
1470 c.f_path = f_path
1467
1471
1468 r_post = self.request.POST
1472 r_post = self.request.POST
1469
1473
1470 message = c.default_message
1474 message = c.default_message
1471 user_message = r_post.getall('message')
1475 user_message = r_post.getall('message')
1472 if isinstance(user_message, list) and user_message:
1476 if isinstance(user_message, list) and user_message:
1473 # we take the first from duplicated results if it's not empty
1477 # we take the first from duplicated results if it's not empty
1474 message = user_message[0] if user_message[0] else message
1478 message = user_message[0] if user_message[0] else message
1475
1479
1476 nodes = {}
1480 nodes = {}
1477
1481
1478 for file_obj in r_post.getall('files_upload') or []:
1482 for file_obj in r_post.getall('files_upload') or []:
1479 content = file_obj.file
1483 content = file_obj.file
1480 filename = file_obj.filename
1484 filename = file_obj.filename
1481
1485
1482 root_path = f_path
1486 root_path = f_path
1483 pure_path = self.create_pure_path(root_path, filename)
1487 pure_path = self.create_pure_path(root_path, filename)
1484 node_path = safe_unicode(bytes(pure_path).lstrip('/'))
1488 node_path = safe_unicode(bytes(pure_path).lstrip('/'))
1485
1489
1486 nodes[node_path] = {
1490 nodes[node_path] = {
1487 'content': content
1491 'content': content
1488 }
1492 }
1489
1493
1490 if not nodes:
1494 if not nodes:
1491 error = 'missing files'
1495 error = 'missing files'
1492 return {
1496 return {
1493 'error': error,
1497 'error': error,
1494 'redirect_url': default_redirect_url
1498 'redirect_url': default_redirect_url
1495 }
1499 }
1496
1500
1497 author = self._rhodecode_db_user.full_contact
1501 author = self._rhodecode_db_user.full_contact
1498
1502
1499 try:
1503 try:
1500 commit = ScmModel().create_nodes(
1504 commit = ScmModel().create_nodes(
1501 user=self._rhodecode_db_user.user_id,
1505 user=self._rhodecode_db_user.user_id,
1502 repo=self.db_repo,
1506 repo=self.db_repo,
1503 message=message,
1507 message=message,
1504 nodes=nodes,
1508 nodes=nodes,
1505 parent_commit=c.commit,
1509 parent_commit=c.commit,
1506 author=author,
1510 author=author,
1507 )
1511 )
1508 if len(nodes) == 1:
1512 if len(nodes) == 1:
1509 flash_message = _('Successfully committed {} new files').format(len(nodes))
1513 flash_message = _('Successfully committed {} new files').format(len(nodes))
1510 else:
1514 else:
1511 flash_message = _('Successfully committed 1 new file')
1515 flash_message = _('Successfully committed 1 new file')
1512
1516
1513 h.flash(flash_message, category='success')
1517 h.flash(flash_message, category='success')
1514
1518
1515 default_redirect_url = h.route_path(
1519 default_redirect_url = h.route_path(
1516 'repo_commit', repo_name=self.db_repo_name, commit_id=commit.raw_id)
1520 'repo_commit', repo_name=self.db_repo_name, commit_id=commit.raw_id)
1517
1521
1518 except NonRelativePathError:
1522 except NonRelativePathError:
1519 log.exception('Non Relative path found')
1523 log.exception('Non Relative path found')
1520 error = _('The location specified must be a relative path and must not '
1524 error = _('The location specified must be a relative path and must not '
1521 'contain .. in the path')
1525 'contain .. in the path')
1522 h.flash(error, category='warning')
1526 h.flash(error, category='warning')
1523
1527
1524 return {
1528 return {
1525 'error': error,
1529 'error': error,
1526 'redirect_url': default_redirect_url
1530 'redirect_url': default_redirect_url
1527 }
1531 }
1528 except (NodeError, NodeAlreadyExistsError) as e:
1532 except (NodeError, NodeAlreadyExistsError) as e:
1529 error = h.escape(e)
1533 error = h.escape(e)
1530 h.flash(error, category='error')
1534 h.flash(error, category='error')
1531
1535
1532 return {
1536 return {
1533 'error': error,
1537 'error': error,
1534 'redirect_url': default_redirect_url
1538 'redirect_url': default_redirect_url
1535 }
1539 }
1536 except Exception:
1540 except Exception:
1537 log.exception('Error occurred during commit')
1541 log.exception('Error occurred during commit')
1538 error = _('Error occurred during commit')
1542 error = _('Error occurred during commit')
1539 h.flash(error, category='error')
1543 h.flash(error, category='error')
1540 return {
1544 return {
1541 'error': error,
1545 'error': error,
1542 'redirect_url': default_redirect_url
1546 'redirect_url': default_redirect_url
1543 }
1547 }
1544
1548
1545 return {
1549 return {
1546 'error': None,
1550 'error': None,
1547 'redirect_url': default_redirect_url
1551 'redirect_url': default_redirect_url
1548 }
1552 }
@@ -1,374 +1,319 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2019 RhodeCode GmbH
3 # Copyright (C) 2011-2019 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import logging
21 import logging
22 import string
22 import string
23 import time
23 import time
24
24
25 import rhodecode
25 import rhodecode
26
26
27 from pyramid.view import view_config
27 from pyramid.view import view_config
28
28
29 from rhodecode.lib.view_utils import get_format_ref_id
29 from rhodecode.lib.view_utils import get_format_ref_id
30 from rhodecode.apps._base import RepoAppView
30 from rhodecode.apps._base import RepoAppView
31 from rhodecode.config.conf import (LANGUAGES_EXTENSIONS_MAP)
31 from rhodecode.config.conf import (LANGUAGES_EXTENSIONS_MAP)
32 from rhodecode.lib import helpers as h, rc_cache
32 from rhodecode.lib import helpers as h, rc_cache
33 from rhodecode.lib.utils2 import safe_str, safe_int
33 from rhodecode.lib.utils2 import safe_str, safe_int
34 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
34 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
35 from rhodecode.lib.markup_renderer import MarkupRenderer, relative_links
36 from rhodecode.lib.ext_json import json
35 from rhodecode.lib.ext_json import json
37 from rhodecode.lib.vcs.backends.base import EmptyCommit
36 from rhodecode.lib.vcs.backends.base import EmptyCommit
38 from rhodecode.lib.vcs.exceptions import (
37 from rhodecode.lib.vcs.exceptions import (
39 CommitError, EmptyRepositoryError, CommitDoesNotExistError)
38 CommitError, EmptyRepositoryError, CommitDoesNotExistError)
40 from rhodecode.model.db import Statistics, CacheKey, User
39 from rhodecode.model.db import Statistics, CacheKey, User
41 from rhodecode.model.meta import Session
40 from rhodecode.model.meta import Session
42 from rhodecode.model.repo import ReadmeFinder
43 from rhodecode.model.scm import ScmModel
41 from rhodecode.model.scm import ScmModel
44
42
45 log = logging.getLogger(__name__)
43 log = logging.getLogger(__name__)
46
44
47
45
48 class RepoSummaryView(RepoAppView):
46 class RepoSummaryView(RepoAppView):
49
47
50 def load_default_context(self):
48 def load_default_context(self):
51 c = self._get_local_tmpl_context(include_app_defaults=True)
49 c = self._get_local_tmpl_context(include_app_defaults=True)
52 c.rhodecode_repo = None
50 c.rhodecode_repo = None
53 if not c.repository_requirements_missing:
51 if not c.repository_requirements_missing:
54 c.rhodecode_repo = self.rhodecode_vcs_repo
52 c.rhodecode_repo = self.rhodecode_vcs_repo
55 return c
53 return c
56
54
57 def _get_readme_data(self, db_repo, renderer_type):
58 log.debug('Looking for README file')
59 landing_commit = db_repo.get_landing_commit()
60 if isinstance(landing_commit, EmptyCommit):
61 return None, None
62
63 cache_namespace_uid = 'cache_repo.{}'.format(db_repo.repo_id)
64 region = rc_cache.get_or_create_region('cache_repo', cache_namespace_uid)
65 start = time.time()
66
67 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid)
68 def generate_repo_readme(repo_id, commit_id, _repo_name, _renderer_type):
69 readme_data = None
70 readme_filename = None
71
72 commit = db_repo.get_commit(commit_id)
73 log.debug("Searching for a README file at commit %s.", commit_id)
74 readme_node = ReadmeFinder(_renderer_type).search(commit)
75
76 if readme_node:
77 log.debug('Found README node: %s', readme_node)
78 relative_urls = {
79 'raw': h.route_path(
80 'repo_file_raw', repo_name=_repo_name,
81 commit_id=commit.raw_id, f_path=readme_node.path),
82 'standard': h.route_path(
83 'repo_files', repo_name=_repo_name,
84 commit_id=commit.raw_id, f_path=readme_node.path),
85 }
86 readme_data = self._render_readme_or_none(commit, readme_node, relative_urls)
87 readme_filename = readme_node.unicode_path
88
89 return readme_data, readme_filename
90
91 readme_data, readme_filename = generate_repo_readme(
92 db_repo.repo_id, landing_commit.raw_id, db_repo.repo_name, renderer_type,)
93 compute_time = time.time() - start
94 log.debug('Repo readme generated and computed in %.4fs', compute_time)
95 return readme_data, readme_filename
96
97 def _render_readme_or_none(self, commit, readme_node, relative_urls):
98 log.debug('Found README file `%s` rendering...', readme_node.path)
99 renderer = MarkupRenderer()
100 try:
101 html_source = renderer.render(
102 readme_node.content, filename=readme_node.path)
103 if relative_urls:
104 return relative_links(html_source, relative_urls)
105 return html_source
106 except Exception:
107 log.exception(
108 "Exception while trying to render the README")
109
110 def _load_commits_context(self, c):
55 def _load_commits_context(self, c):
111 p = safe_int(self.request.GET.get('page'), 1)
56 p = safe_int(self.request.GET.get('page'), 1)
112 size = safe_int(self.request.GET.get('size'), 10)
57 size = safe_int(self.request.GET.get('size'), 10)
113
58
114 def url_generator(**kw):
59 def url_generator(**kw):
115 query_params = {
60 query_params = {
116 'size': size
61 'size': size
117 }
62 }
118 query_params.update(kw)
63 query_params.update(kw)
119 return h.route_path(
64 return h.route_path(
120 'repo_summary_commits',
65 'repo_summary_commits',
121 repo_name=c.rhodecode_db_repo.repo_name, _query=query_params)
66 repo_name=c.rhodecode_db_repo.repo_name, _query=query_params)
122
67
123 pre_load = ['author', 'branch', 'date', 'message']
68 pre_load = ['author', 'branch', 'date', 'message']
124 try:
69 try:
125 collection = self.rhodecode_vcs_repo.get_commits(
70 collection = self.rhodecode_vcs_repo.get_commits(
126 pre_load=pre_load, translate_tags=False)
71 pre_load=pre_load, translate_tags=False)
127 except EmptyRepositoryError:
72 except EmptyRepositoryError:
128 collection = self.rhodecode_vcs_repo
73 collection = self.rhodecode_vcs_repo
129
74
130 c.repo_commits = h.RepoPage(
75 c.repo_commits = h.RepoPage(
131 collection, page=p, items_per_page=size, url=url_generator)
76 collection, page=p, items_per_page=size, url=url_generator)
132 page_ids = [x.raw_id for x in c.repo_commits]
77 page_ids = [x.raw_id for x in c.repo_commits]
133 c.comments = self.db_repo.get_comments(page_ids)
78 c.comments = self.db_repo.get_comments(page_ids)
134 c.statuses = self.db_repo.statuses(page_ids)
79 c.statuses = self.db_repo.statuses(page_ids)
135
80
136 def _prepare_and_set_clone_url(self, c):
81 def _prepare_and_set_clone_url(self, c):
137 username = ''
82 username = ''
138 if self._rhodecode_user.username != User.DEFAULT_USER:
83 if self._rhodecode_user.username != User.DEFAULT_USER:
139 username = safe_str(self._rhodecode_user.username)
84 username = safe_str(self._rhodecode_user.username)
140
85
141 _def_clone_uri = _def_clone_uri_id = c.clone_uri_tmpl
86 _def_clone_uri = _def_clone_uri_id = c.clone_uri_tmpl
142 _def_clone_uri_ssh = c.clone_uri_ssh_tmpl
87 _def_clone_uri_ssh = c.clone_uri_ssh_tmpl
143
88
144 if '{repo}' in _def_clone_uri:
89 if '{repo}' in _def_clone_uri:
145 _def_clone_uri_id = _def_clone_uri.replace('{repo}', '_{repoid}')
90 _def_clone_uri_id = _def_clone_uri.replace('{repo}', '_{repoid}')
146 elif '{repoid}' in _def_clone_uri:
91 elif '{repoid}' in _def_clone_uri:
147 _def_clone_uri_id = _def_clone_uri.replace('_{repoid}', '{repo}')
92 _def_clone_uri_id = _def_clone_uri.replace('_{repoid}', '{repo}')
148
93
149 c.clone_repo_url = self.db_repo.clone_url(
94 c.clone_repo_url = self.db_repo.clone_url(
150 user=username, uri_tmpl=_def_clone_uri)
95 user=username, uri_tmpl=_def_clone_uri)
151 c.clone_repo_url_id = self.db_repo.clone_url(
96 c.clone_repo_url_id = self.db_repo.clone_url(
152 user=username, uri_tmpl=_def_clone_uri_id)
97 user=username, uri_tmpl=_def_clone_uri_id)
153 c.clone_repo_url_ssh = self.db_repo.clone_url(
98 c.clone_repo_url_ssh = self.db_repo.clone_url(
154 uri_tmpl=_def_clone_uri_ssh, ssh=True)
99 uri_tmpl=_def_clone_uri_ssh, ssh=True)
155
100
156 @LoginRequired()
101 @LoginRequired()
157 @HasRepoPermissionAnyDecorator(
102 @HasRepoPermissionAnyDecorator(
158 'repository.read', 'repository.write', 'repository.admin')
103 'repository.read', 'repository.write', 'repository.admin')
159 @view_config(
104 @view_config(
160 route_name='repo_summary_commits', request_method='GET',
105 route_name='repo_summary_commits', request_method='GET',
161 renderer='rhodecode:templates/summary/summary_commits.mako')
106 renderer='rhodecode:templates/summary/summary_commits.mako')
162 def summary_commits(self):
107 def summary_commits(self):
163 c = self.load_default_context()
108 c = self.load_default_context()
164 self._prepare_and_set_clone_url(c)
109 self._prepare_and_set_clone_url(c)
165 self._load_commits_context(c)
110 self._load_commits_context(c)
166 return self._get_template_context(c)
111 return self._get_template_context(c)
167
112
168 @LoginRequired()
113 @LoginRequired()
169 @HasRepoPermissionAnyDecorator(
114 @HasRepoPermissionAnyDecorator(
170 'repository.read', 'repository.write', 'repository.admin')
115 'repository.read', 'repository.write', 'repository.admin')
171 @view_config(
116 @view_config(
172 route_name='repo_summary', request_method='GET',
117 route_name='repo_summary', request_method='GET',
173 renderer='rhodecode:templates/summary/summary.mako')
118 renderer='rhodecode:templates/summary/summary.mako')
174 @view_config(
119 @view_config(
175 route_name='repo_summary_slash', request_method='GET',
120 route_name='repo_summary_slash', request_method='GET',
176 renderer='rhodecode:templates/summary/summary.mako')
121 renderer='rhodecode:templates/summary/summary.mako')
177 @view_config(
122 @view_config(
178 route_name='repo_summary_explicit', request_method='GET',
123 route_name='repo_summary_explicit', request_method='GET',
179 renderer='rhodecode:templates/summary/summary.mako')
124 renderer='rhodecode:templates/summary/summary.mako')
180 def summary(self):
125 def summary(self):
181 c = self.load_default_context()
126 c = self.load_default_context()
182
127
183 # Prepare the clone URL
128 # Prepare the clone URL
184 self._prepare_and_set_clone_url(c)
129 self._prepare_and_set_clone_url(c)
185
130
186 # update every 5 min
131 # update every 5 min
187 if self.db_repo.last_commit_cache_update_diff > 60 * 5:
132 if self.db_repo.last_commit_cache_update_diff > 60 * 5:
188 self.db_repo.update_commit_cache()
133 self.db_repo.update_commit_cache()
189
134
190 # If enabled, get statistics data
135 # If enabled, get statistics data
191
136
192 c.show_stats = bool(self.db_repo.enable_statistics)
137 c.show_stats = bool(self.db_repo.enable_statistics)
193
138
194 stats = Session().query(Statistics) \
139 stats = Session().query(Statistics) \
195 .filter(Statistics.repository == self.db_repo) \
140 .filter(Statistics.repository == self.db_repo) \
196 .scalar()
141 .scalar()
197
142
198 c.stats_percentage = 0
143 c.stats_percentage = 0
199
144
200 if stats and stats.languages:
145 if stats and stats.languages:
201 c.no_data = False is self.db_repo.enable_statistics
146 c.no_data = False is self.db_repo.enable_statistics
202 lang_stats_d = json.loads(stats.languages)
147 lang_stats_d = json.loads(stats.languages)
203
148
204 # Sort first by decreasing count and second by the file extension,
149 # Sort first by decreasing count and second by the file extension,
205 # so we have a consistent output.
150 # so we have a consistent output.
206 lang_stats_items = sorted(lang_stats_d.iteritems(),
151 lang_stats_items = sorted(lang_stats_d.iteritems(),
207 key=lambda k: (-k[1], k[0]))[:10]
152 key=lambda k: (-k[1], k[0]))[:10]
208 lang_stats = [(x, {"count": y,
153 lang_stats = [(x, {"count": y,
209 "desc": LANGUAGES_EXTENSIONS_MAP.get(x)})
154 "desc": LANGUAGES_EXTENSIONS_MAP.get(x)})
210 for x, y in lang_stats_items]
155 for x, y in lang_stats_items]
211
156
212 c.trending_languages = json.dumps(lang_stats)
157 c.trending_languages = json.dumps(lang_stats)
213 else:
158 else:
214 c.no_data = True
159 c.no_data = True
215 c.trending_languages = json.dumps({})
160 c.trending_languages = json.dumps({})
216
161
217 scm_model = ScmModel()
162 scm_model = ScmModel()
218 c.enable_downloads = self.db_repo.enable_downloads
163 c.enable_downloads = self.db_repo.enable_downloads
219 c.repository_followers = scm_model.get_followers(self.db_repo)
164 c.repository_followers = scm_model.get_followers(self.db_repo)
220 c.repository_forks = scm_model.get_forks(self.db_repo)
165 c.repository_forks = scm_model.get_forks(self.db_repo)
221
166
222 # first interaction with the VCS instance after here...
167 # first interaction with the VCS instance after here...
223 if c.repository_requirements_missing:
168 if c.repository_requirements_missing:
224 self.request.override_renderer = \
169 self.request.override_renderer = \
225 'rhodecode:templates/summary/missing_requirements.mako'
170 'rhodecode:templates/summary/missing_requirements.mako'
226 return self._get_template_context(c)
171 return self._get_template_context(c)
227
172
228 c.readme_data, c.readme_file = \
173 c.readme_data, c.readme_file = \
229 self._get_readme_data(self.db_repo, c.visual.default_renderer)
174 self._get_readme_data(self.db_repo, c.visual.default_renderer)
230
175
231 # loads the summary commits template context
176 # loads the summary commits template context
232 self._load_commits_context(c)
177 self._load_commits_context(c)
233
178
234 return self._get_template_context(c)
179 return self._get_template_context(c)
235
180
236 @LoginRequired()
181 @LoginRequired()
237 @HasRepoPermissionAnyDecorator(
182 @HasRepoPermissionAnyDecorator(
238 'repository.read', 'repository.write', 'repository.admin')
183 'repository.read', 'repository.write', 'repository.admin')
239 @view_config(
184 @view_config(
240 route_name='repo_stats', request_method='GET',
185 route_name='repo_stats', request_method='GET',
241 renderer='json_ext')
186 renderer='json_ext')
242 def repo_stats(self):
187 def repo_stats(self):
243 show_stats = bool(self.db_repo.enable_statistics)
188 show_stats = bool(self.db_repo.enable_statistics)
244 repo_id = self.db_repo.repo_id
189 repo_id = self.db_repo.repo_id
245
190
246 landing_commit = self.db_repo.get_landing_commit()
191 landing_commit = self.db_repo.get_landing_commit()
247 if isinstance(landing_commit, EmptyCommit):
192 if isinstance(landing_commit, EmptyCommit):
248 return {'size': 0, 'code_stats': {}}
193 return {'size': 0, 'code_stats': {}}
249
194
250 cache_seconds = safe_int(rhodecode.CONFIG.get('rc_cache.cache_repo.expiration_time'))
195 cache_seconds = safe_int(rhodecode.CONFIG.get('rc_cache.cache_repo.expiration_time'))
251 cache_on = cache_seconds > 0
196 cache_on = cache_seconds > 0
252
197
253 log.debug(
198 log.debug(
254 'Computing REPO STATS for repo_id %s commit_id `%s` '
199 'Computing REPO STATS for repo_id %s commit_id `%s` '
255 'with caching: %s[TTL: %ss]' % (
200 'with caching: %s[TTL: %ss]' % (
256 repo_id, landing_commit, cache_on, cache_seconds or 0))
201 repo_id, landing_commit, cache_on, cache_seconds or 0))
257
202
258 cache_namespace_uid = 'cache_repo.{}'.format(repo_id)
203 cache_namespace_uid = 'cache_repo.{}'.format(repo_id)
259 region = rc_cache.get_or_create_region('cache_repo', cache_namespace_uid)
204 region = rc_cache.get_or_create_region('cache_repo', cache_namespace_uid)
260
205
261 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid,
206 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid,
262 condition=cache_on)
207 condition=cache_on)
263 def compute_stats(repo_id, commit_id, _show_stats):
208 def compute_stats(repo_id, commit_id, _show_stats):
264 code_stats = {}
209 code_stats = {}
265 size = 0
210 size = 0
266 try:
211 try:
267 commit = self.db_repo.get_commit(commit_id)
212 commit = self.db_repo.get_commit(commit_id)
268
213
269 for node in commit.get_filenodes_generator():
214 for node in commit.get_filenodes_generator():
270 size += node.size
215 size += node.size
271 if not _show_stats:
216 if not _show_stats:
272 continue
217 continue
273 ext = string.lower(node.extension)
218 ext = string.lower(node.extension)
274 ext_info = LANGUAGES_EXTENSIONS_MAP.get(ext)
219 ext_info = LANGUAGES_EXTENSIONS_MAP.get(ext)
275 if ext_info:
220 if ext_info:
276 if ext in code_stats:
221 if ext in code_stats:
277 code_stats[ext]['count'] += 1
222 code_stats[ext]['count'] += 1
278 else:
223 else:
279 code_stats[ext] = {"count": 1, "desc": ext_info}
224 code_stats[ext] = {"count": 1, "desc": ext_info}
280 except (EmptyRepositoryError, CommitDoesNotExistError):
225 except (EmptyRepositoryError, CommitDoesNotExistError):
281 pass
226 pass
282 return {'size': h.format_byte_size_binary(size),
227 return {'size': h.format_byte_size_binary(size),
283 'code_stats': code_stats}
228 'code_stats': code_stats}
284
229
285 stats = compute_stats(self.db_repo.repo_id, landing_commit.raw_id, show_stats)
230 stats = compute_stats(self.db_repo.repo_id, landing_commit.raw_id, show_stats)
286 return stats
231 return stats
287
232
288 @LoginRequired()
233 @LoginRequired()
289 @HasRepoPermissionAnyDecorator(
234 @HasRepoPermissionAnyDecorator(
290 'repository.read', 'repository.write', 'repository.admin')
235 'repository.read', 'repository.write', 'repository.admin')
291 @view_config(
236 @view_config(
292 route_name='repo_refs_data', request_method='GET',
237 route_name='repo_refs_data', request_method='GET',
293 renderer='json_ext')
238 renderer='json_ext')
294 def repo_refs_data(self):
239 def repo_refs_data(self):
295 _ = self.request.translate
240 _ = self.request.translate
296 self.load_default_context()
241 self.load_default_context()
297
242
298 repo = self.rhodecode_vcs_repo
243 repo = self.rhodecode_vcs_repo
299 refs_to_create = [
244 refs_to_create = [
300 (_("Branch"), repo.branches, 'branch'),
245 (_("Branch"), repo.branches, 'branch'),
301 (_("Tag"), repo.tags, 'tag'),
246 (_("Tag"), repo.tags, 'tag'),
302 (_("Bookmark"), repo.bookmarks, 'book'),
247 (_("Bookmark"), repo.bookmarks, 'book'),
303 ]
248 ]
304 res = self._create_reference_data(repo, self.db_repo_name, refs_to_create)
249 res = self._create_reference_data(repo, self.db_repo_name, refs_to_create)
305 data = {
250 data = {
306 'more': False,
251 'more': False,
307 'results': res
252 'results': res
308 }
253 }
309 return data
254 return data
310
255
311 @LoginRequired()
256 @LoginRequired()
312 @HasRepoPermissionAnyDecorator(
257 @HasRepoPermissionAnyDecorator(
313 'repository.read', 'repository.write', 'repository.admin')
258 'repository.read', 'repository.write', 'repository.admin')
314 @view_config(
259 @view_config(
315 route_name='repo_refs_changelog_data', request_method='GET',
260 route_name='repo_refs_changelog_data', request_method='GET',
316 renderer='json_ext')
261 renderer='json_ext')
317 def repo_refs_changelog_data(self):
262 def repo_refs_changelog_data(self):
318 _ = self.request.translate
263 _ = self.request.translate
319 self.load_default_context()
264 self.load_default_context()
320
265
321 repo = self.rhodecode_vcs_repo
266 repo = self.rhodecode_vcs_repo
322
267
323 refs_to_create = [
268 refs_to_create = [
324 (_("Branches"), repo.branches, 'branch'),
269 (_("Branches"), repo.branches, 'branch'),
325 (_("Closed branches"), repo.branches_closed, 'branch_closed'),
270 (_("Closed branches"), repo.branches_closed, 'branch_closed'),
326 # TODO: enable when vcs can handle bookmarks filters
271 # TODO: enable when vcs can handle bookmarks filters
327 # (_("Bookmarks"), repo.bookmarks, "book"),
272 # (_("Bookmarks"), repo.bookmarks, "book"),
328 ]
273 ]
329 res = self._create_reference_data(
274 res = self._create_reference_data(
330 repo, self.db_repo_name, refs_to_create)
275 repo, self.db_repo_name, refs_to_create)
331 data = {
276 data = {
332 'more': False,
277 'more': False,
333 'results': res
278 'results': res
334 }
279 }
335 return data
280 return data
336
281
337 def _create_reference_data(self, repo, full_repo_name, refs_to_create):
282 def _create_reference_data(self, repo, full_repo_name, refs_to_create):
338 format_ref_id = get_format_ref_id(repo)
283 format_ref_id = get_format_ref_id(repo)
339
284
340 result = []
285 result = []
341 for title, refs, ref_type in refs_to_create:
286 for title, refs, ref_type in refs_to_create:
342 if refs:
287 if refs:
343 result.append({
288 result.append({
344 'text': title,
289 'text': title,
345 'children': self._create_reference_items(
290 'children': self._create_reference_items(
346 repo, full_repo_name, refs, ref_type,
291 repo, full_repo_name, refs, ref_type,
347 format_ref_id),
292 format_ref_id),
348 })
293 })
349 return result
294 return result
350
295
351 def _create_reference_items(self, repo, full_repo_name, refs, ref_type, format_ref_id):
296 def _create_reference_items(self, repo, full_repo_name, refs, ref_type, format_ref_id):
352 result = []
297 result = []
353 is_svn = h.is_svn(repo)
298 is_svn = h.is_svn(repo)
354 for ref_name, raw_id in refs.iteritems():
299 for ref_name, raw_id in refs.iteritems():
355 files_url = self._create_files_url(
300 files_url = self._create_files_url(
356 repo, full_repo_name, ref_name, raw_id, is_svn)
301 repo, full_repo_name, ref_name, raw_id, is_svn)
357 result.append({
302 result.append({
358 'text': ref_name,
303 'text': ref_name,
359 'id': format_ref_id(ref_name, raw_id),
304 'id': format_ref_id(ref_name, raw_id),
360 'raw_id': raw_id,
305 'raw_id': raw_id,
361 'type': ref_type,
306 'type': ref_type,
362 'files_url': files_url,
307 'files_url': files_url,
363 'idx': 0,
308 'idx': 0,
364 })
309 })
365 return result
310 return result
366
311
367 def _create_files_url(self, repo, full_repo_name, ref_name, raw_id, is_svn):
312 def _create_files_url(self, repo, full_repo_name, ref_name, raw_id, is_svn):
368 use_commit_id = '/' in ref_name or is_svn
313 use_commit_id = '/' in ref_name or is_svn
369 return h.route_path(
314 return h.route_path(
370 'repo_files',
315 'repo_files',
371 repo_name=full_repo_name,
316 repo_name=full_repo_name,
372 f_path=ref_name if is_svn else '',
317 f_path=ref_name if is_svn else '',
373 commit_id=raw_id if use_commit_id else ref_name,
318 commit_id=raw_id if use_commit_id else ref_name,
374 _query=dict(at=ref_name))
319 _query=dict(at=ref_name))
@@ -1,64 +1,83 b''
1
1
2 <div id="codeblock" class="browserblock">
2 <div id="codeblock" class="browserblock">
3 <div class="browser-header">
3 <div class="browser-header">
4 <div class="browser-nav">
4 <div class="browser-nav">
5
5
6 <div class="info_box">
6 <div class="info_box">
7
7
8 <div class="info_box_elem previous">
8 <div class="info_box_elem previous">
9 <a id="prev_commit_link" data-commit-id="${c.prev_commit.raw_id}" class=" ${('disabled' if c.url_prev == '#' else '')}" href="${c.url_prev}" title="${_('Previous commit')}"><i class="icon-left"></i></a>
9 <a id="prev_commit_link" data-commit-id="${c.prev_commit.raw_id}" class=" ${('disabled' if c.url_prev == '#' else '')}" href="${c.url_prev}" title="${_('Previous commit')}"><i class="icon-left"></i></a>
10 </div>
10 </div>
11
11
12 ${h.hidden('refs_filter')}
12 ${h.hidden('refs_filter')}
13
13
14 <div class="info_box_elem next">
14 <div class="info_box_elem next">
15 <a id="next_commit_link" data-commit-id="${c.next_commit.raw_id}" class=" ${('disabled' if c.url_next == '#' else '')}" href="${c.url_next}" title="${_('Next commit')}"><i class="icon-right"></i></a>
15 <a id="next_commit_link" data-commit-id="${c.next_commit.raw_id}" class=" ${('disabled' if c.url_next == '#' else '')}" href="${c.url_next}" title="${_('Next commit')}"><i class="icon-right"></i></a>
16 </div>
16 </div>
17 </div>
17 </div>
18
18
19 % if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name):
19 % if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name):
20 <div>
20 <div>
21 <a class="btn btn-primary new-file" href="${h.route_path('repo_files_upload_file',repo_name=c.repo_name,commit_id=c.commit.raw_id,f_path=c.f_path)}">
21 <a class="btn btn-primary new-file" href="${h.route_path('repo_files_upload_file',repo_name=c.repo_name,commit_id=c.commit.raw_id,f_path=c.f_path)}">
22 ${_('Upload File')}
22 ${_('Upload File')}
23 </a>
23 </a>
24 <a class="btn btn-primary new-file" href="${h.route_path('repo_files_add_file',repo_name=c.repo_name,commit_id=c.commit.raw_id,f_path=c.f_path)}">
24 <a class="btn btn-primary new-file" href="${h.route_path('repo_files_add_file',repo_name=c.repo_name,commit_id=c.commit.raw_id,f_path=c.f_path)}">
25 ${_('Add File')}
25 ${_('Add File')}
26 </a>
26 </a>
27 </div>
27 </div>
28 % endif
28 % endif
29
29
30 % if c.enable_downloads:
30 % if c.enable_downloads:
31 <% at_path = '{}'.format(request.GET.get('at') or c.commit.raw_id[:6]) %>
31 <% at_path = '{}'.format(request.GET.get('at') or c.commit.raw_id[:6]) %>
32 <div class="btn btn-default new-file">
32 <div class="btn btn-default new-file">
33 % if c.f_path == '/':
33 % if c.f_path == '/':
34 <a href="${h.route_path('repo_archivefile',repo_name=c.repo_name, fname='{}.zip'.format(c.commit.raw_id))}">
34 <a href="${h.route_path('repo_archivefile',repo_name=c.repo_name, fname='{}.zip'.format(c.commit.raw_id))}">
35 ${_('Download full tree ZIP')}
35 ${_('Download full tree ZIP')}
36 </a>
36 </a>
37 % else:
37 % else:
38 <a href="${h.route_path('repo_archivefile',repo_name=c.repo_name, fname='{}.zip'.format(c.commit.raw_id), _query={'at_path':c.f_path})}">
38 <a href="${h.route_path('repo_archivefile',repo_name=c.repo_name, fname='{}.zip'.format(c.commit.raw_id), _query={'at_path':c.f_path})}">
39 ${_('Download this tree ZIP')}
39 ${_('Download this tree ZIP')}
40 </a>
40 </a>
41 % endif
41 % endif
42 </div>
42 </div>
43 % endif
43 % endif
44
44
45 <div class="files-quick-filter">
45 <div class="files-quick-filter">
46 <ul class="files-filter-box">
46 <ul class="files-filter-box">
47 <li class="files-filter-box-path">
47 <li class="files-filter-box-path">
48 <i class="icon-search"></i>
48 <i class="icon-search"></i>
49 </li>
49 </li>
50 <li class="files-filter-box-input">
50 <li class="files-filter-box-input">
51 <input onkeydown="NodeFilter.initFilter(event)" class="init" type="text" placeholder="Quick filter" name="filter" size="25" id="node_filter" autocomplete="off">
51 <input onkeydown="NodeFilter.initFilter(event)" class="init" type="text" placeholder="Quick filter" name="filter" size="25" id="node_filter" autocomplete="off">
52 </li>
52 </li>
53 </ul>
53 </ul>
54 </div>
54 </div>
55 </div>
55 </div>
56
56
57 </div>
57 </div>
58
58
59 ## file tree is computed from caches, and filled in
59 ## file tree is computed from caches, and filled in
60 <div id="file-tree">
60 <div id="file-tree">
61 ${c.file_tree |n}
61 ${c.file_tree |n}
62 </div>
62 </div>
63
63
64 %if c.readme_data:
65 <div id="readme" class="anchor">
66 <div class="box">
67 <div class="title" title="${h.tooltip(_('Readme file from commit %s:%s') % (c.rhodecode_db_repo.landing_rev[0], c.rhodecode_db_repo.landing_rev[1]))}">
68 <h3 class="breadcrumbs">
69 <a href="${h.route_path('repo_files',repo_name=c.repo_name,commit_id=c.rhodecode_db_repo.landing_rev[1],f_path=c.readme_file)}">
70 ${c.readme_file}
71 </a>
72 </h3>
73 </div>
74 <div class="readme codeblock">
75 <div class="readme_box">
76 ${c.readme_data|n}
77 </div>
78 </div>
79 </div>
80 </div>
81 %endif
82
64 </div>
83 </div>
General Comments 0
You need to be logged in to leave comments. Login now