##// END OF EJS Templates
path-permissions: Introduced a _get_f_path_unchecked method, which can be used by redirects, which don't have to create a template context
idlsoft -
r2620:807566df default
parent child Browse files
Show More
@@ -1,616 +1,622 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2018 RhodeCode GmbH
3 # Copyright (C) 2016-2018 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.httpexceptions import HTTPFound, HTTPForbidden
25 from pyramid.httpexceptions import HTTPFound, HTTPForbidden
26
26
27 from rhodecode.lib import helpers as h, diffs
27 from rhodecode.lib import helpers as h, diffs
28 from rhodecode.lib.utils2 import StrictAttributeDict, safe_int, datetime_to_time
28 from rhodecode.lib.utils2 import StrictAttributeDict, safe_int, datetime_to_time
29 from rhodecode.lib.vcs.exceptions import RepositoryRequirementError
29 from rhodecode.lib.vcs.exceptions import RepositoryRequirementError
30 from rhodecode.model import repo
30 from rhodecode.model import repo
31 from rhodecode.model import repo_group
31 from rhodecode.model import repo_group
32 from rhodecode.model import user_group
32 from rhodecode.model import user_group
33 from rhodecode.model import user
33 from rhodecode.model import user
34 from rhodecode.model.db import User
34 from rhodecode.model.db import User
35 from rhodecode.model.scm import ScmModel
35 from rhodecode.model.scm import ScmModel
36
36
37 log = logging.getLogger(__name__)
37 log = logging.getLogger(__name__)
38
38
39
39
40 ADMIN_PREFIX = '/_admin'
40 ADMIN_PREFIX = '/_admin'
41 STATIC_FILE_PREFIX = '/_static'
41 STATIC_FILE_PREFIX = '/_static'
42
42
43 URL_NAME_REQUIREMENTS = {
43 URL_NAME_REQUIREMENTS = {
44 # group name can have a slash in them, but they must not end with a slash
44 # group name can have a slash in them, but they must not end with a slash
45 'group_name': r'.*?[^/]',
45 'group_name': r'.*?[^/]',
46 'repo_group_name': r'.*?[^/]',
46 'repo_group_name': r'.*?[^/]',
47 # repo names can have a slash in them, but they must not end with a slash
47 # repo names can have a slash in them, but they must not end with a slash
48 'repo_name': r'.*?[^/]',
48 'repo_name': r'.*?[^/]',
49 # file path eats up everything at the end
49 # file path eats up everything at the end
50 'f_path': r'.*',
50 'f_path': r'.*',
51 # reference types
51 # reference types
52 'source_ref_type': '(branch|book|tag|rev|\%\(source_ref_type\)s)',
52 'source_ref_type': '(branch|book|tag|rev|\%\(source_ref_type\)s)',
53 'target_ref_type': '(branch|book|tag|rev|\%\(target_ref_type\)s)',
53 'target_ref_type': '(branch|book|tag|rev|\%\(target_ref_type\)s)',
54 }
54 }
55
55
56
56
57 def add_route_with_slash(config,name, pattern, **kw):
57 def add_route_with_slash(config,name, pattern, **kw):
58 config.add_route(name, pattern, **kw)
58 config.add_route(name, pattern, **kw)
59 if not pattern.endswith('/'):
59 if not pattern.endswith('/'):
60 config.add_route(name + '_slash', pattern + '/', **kw)
60 config.add_route(name + '_slash', pattern + '/', **kw)
61
61
62
62
63 def add_route_requirements(route_path, requirements=URL_NAME_REQUIREMENTS):
63 def add_route_requirements(route_path, requirements=URL_NAME_REQUIREMENTS):
64 """
64 """
65 Adds regex requirements to pyramid routes using a mapping dict
65 Adds regex requirements to pyramid routes using a mapping dict
66 e.g::
66 e.g::
67 add_route_requirements('{repo_name}/settings')
67 add_route_requirements('{repo_name}/settings')
68 """
68 """
69 for key, regex in requirements.items():
69 for key, regex in requirements.items():
70 route_path = route_path.replace('{%s}' % key, '{%s:%s}' % (key, regex))
70 route_path = route_path.replace('{%s}' % key, '{%s:%s}' % (key, regex))
71 return route_path
71 return route_path
72
72
73
73
74 def get_format_ref_id(repo):
74 def get_format_ref_id(repo):
75 """Returns a `repo` specific reference formatter function"""
75 """Returns a `repo` specific reference formatter function"""
76 if h.is_svn(repo):
76 if h.is_svn(repo):
77 return _format_ref_id_svn
77 return _format_ref_id_svn
78 else:
78 else:
79 return _format_ref_id
79 return _format_ref_id
80
80
81
81
82 def _format_ref_id(name, raw_id):
82 def _format_ref_id(name, raw_id):
83 """Default formatting of a given reference `name`"""
83 """Default formatting of a given reference `name`"""
84 return name
84 return name
85
85
86
86
87 def _format_ref_id_svn(name, raw_id):
87 def _format_ref_id_svn(name, raw_id):
88 """Special way of formatting a reference for Subversion including path"""
88 """Special way of formatting a reference for Subversion including path"""
89 return '%s@%s' % (name, raw_id)
89 return '%s@%s' % (name, raw_id)
90
90
91
91
92 class TemplateArgs(StrictAttributeDict):
92 class TemplateArgs(StrictAttributeDict):
93 pass
93 pass
94
94
95
95
96 class BaseAppView(object):
96 class BaseAppView(object):
97
97
98 def __init__(self, context, request):
98 def __init__(self, context, request):
99 self.request = request
99 self.request = request
100 self.context = context
100 self.context = context
101 self.session = request.session
101 self.session = request.session
102 self._rhodecode_user = request.user # auth user
102 self._rhodecode_user = request.user # auth user
103 self._rhodecode_db_user = self._rhodecode_user.get_instance()
103 self._rhodecode_db_user = self._rhodecode_user.get_instance()
104 self._maybe_needs_password_change(
104 self._maybe_needs_password_change(
105 request.matched_route.name, self._rhodecode_db_user)
105 request.matched_route.name, self._rhodecode_db_user)
106
106
107 def _maybe_needs_password_change(self, view_name, user_obj):
107 def _maybe_needs_password_change(self, view_name, user_obj):
108 log.debug('Checking if user %s needs password change on view %s',
108 log.debug('Checking if user %s needs password change on view %s',
109 user_obj, view_name)
109 user_obj, view_name)
110 skip_user_views = [
110 skip_user_views = [
111 'logout', 'login',
111 'logout', 'login',
112 'my_account_password', 'my_account_password_update'
112 'my_account_password', 'my_account_password_update'
113 ]
113 ]
114
114
115 if not user_obj:
115 if not user_obj:
116 return
116 return
117
117
118 if user_obj.username == User.DEFAULT_USER:
118 if user_obj.username == User.DEFAULT_USER:
119 return
119 return
120
120
121 now = time.time()
121 now = time.time()
122 should_change = user_obj.user_data.get('force_password_change')
122 should_change = user_obj.user_data.get('force_password_change')
123 change_after = safe_int(should_change) or 0
123 change_after = safe_int(should_change) or 0
124 if should_change and now > change_after:
124 if should_change and now > change_after:
125 log.debug('User %s requires password change', user_obj)
125 log.debug('User %s requires password change', user_obj)
126 h.flash('You are required to change your password', 'warning',
126 h.flash('You are required to change your password', 'warning',
127 ignore_duplicate=True)
127 ignore_duplicate=True)
128
128
129 if view_name not in skip_user_views:
129 if view_name not in skip_user_views:
130 raise HTTPFound(
130 raise HTTPFound(
131 self.request.route_path('my_account_password'))
131 self.request.route_path('my_account_password'))
132
132
133 def _log_creation_exception(self, e, repo_name):
133 def _log_creation_exception(self, e, repo_name):
134 _ = self.request.translate
134 _ = self.request.translate
135 reason = None
135 reason = None
136 if len(e.args) == 2:
136 if len(e.args) == 2:
137 reason = e.args[1]
137 reason = e.args[1]
138
138
139 if reason == 'INVALID_CERTIFICATE':
139 if reason == 'INVALID_CERTIFICATE':
140 log.exception(
140 log.exception(
141 'Exception creating a repository: invalid certificate')
141 'Exception creating a repository: invalid certificate')
142 msg = (_('Error creating repository %s: invalid certificate')
142 msg = (_('Error creating repository %s: invalid certificate')
143 % repo_name)
143 % repo_name)
144 else:
144 else:
145 log.exception("Exception creating a repository")
145 log.exception("Exception creating a repository")
146 msg = (_('Error creating repository %s')
146 msg = (_('Error creating repository %s')
147 % repo_name)
147 % repo_name)
148 return msg
148 return msg
149
149
150 def _get_local_tmpl_context(self, include_app_defaults=True):
150 def _get_local_tmpl_context(self, include_app_defaults=True):
151 c = TemplateArgs()
151 c = TemplateArgs()
152 c.auth_user = self.request.user
152 c.auth_user = self.request.user
153 # TODO(marcink): migrate the usage of c.rhodecode_user to c.auth_user
153 # TODO(marcink): migrate the usage of c.rhodecode_user to c.auth_user
154 c.rhodecode_user = self.request.user
154 c.rhodecode_user = self.request.user
155
155
156 if include_app_defaults:
156 if include_app_defaults:
157 from rhodecode.lib.base import attach_context_attributes
157 from rhodecode.lib.base import attach_context_attributes
158 attach_context_attributes(c, self.request, self.request.user.user_id)
158 attach_context_attributes(c, self.request, self.request.user.user_id)
159
159
160 return c
160 return c
161
161
162 def _get_template_context(self, tmpl_args, **kwargs):
162 def _get_template_context(self, tmpl_args, **kwargs):
163
163
164 local_tmpl_args = {
164 local_tmpl_args = {
165 'defaults': {},
165 'defaults': {},
166 'errors': {},
166 'errors': {},
167 'c': tmpl_args
167 'c': tmpl_args
168 }
168 }
169 local_tmpl_args.update(kwargs)
169 local_tmpl_args.update(kwargs)
170 return local_tmpl_args
170 return local_tmpl_args
171
171
172 def load_default_context(self):
172 def load_default_context(self):
173 """
173 """
174 example:
174 example:
175
175
176 def load_default_context(self):
176 def load_default_context(self):
177 c = self._get_local_tmpl_context()
177 c = self._get_local_tmpl_context()
178 c.custom_var = 'foobar'
178 c.custom_var = 'foobar'
179
179
180 return c
180 return c
181 """
181 """
182 raise NotImplementedError('Needs implementation in view class')
182 raise NotImplementedError('Needs implementation in view class')
183
183
184
184
185 class RepoAppView(BaseAppView):
185 class RepoAppView(BaseAppView):
186
186
187 def __init__(self, context, request):
187 def __init__(self, context, request):
188 super(RepoAppView, self).__init__(context, request)
188 super(RepoAppView, self).__init__(context, request)
189 self.db_repo = request.db_repo
189 self.db_repo = request.db_repo
190 self.db_repo_name = self.db_repo.repo_name
190 self.db_repo_name = self.db_repo.repo_name
191 self.db_repo_pull_requests = ScmModel().get_pull_requests(self.db_repo)
191 self.db_repo_pull_requests = ScmModel().get_pull_requests(self.db_repo)
192
192
193 def _handle_missing_requirements(self, error):
193 def _handle_missing_requirements(self, error):
194 log.error(
194 log.error(
195 'Requirements are missing for repository %s: %s',
195 'Requirements are missing for repository %s: %s',
196 self.db_repo_name, error.message)
196 self.db_repo_name, error.message)
197
197
198 def _get_local_tmpl_context(self, include_app_defaults=True):
198 def _get_local_tmpl_context(self, include_app_defaults=True):
199 _ = self.request.translate
199 _ = self.request.translate
200 c = super(RepoAppView, self)._get_local_tmpl_context(
200 c = super(RepoAppView, self)._get_local_tmpl_context(
201 include_app_defaults=include_app_defaults)
201 include_app_defaults=include_app_defaults)
202
202
203 # register common vars for this type of view
203 # register common vars for this type of view
204 c.rhodecode_db_repo = self.db_repo
204 c.rhodecode_db_repo = self.db_repo
205 c.repo_name = self.db_repo_name
205 c.repo_name = self.db_repo_name
206 c.repository_pull_requests = self.db_repo_pull_requests
206 c.repository_pull_requests = self.db_repo_pull_requests
207
207
208 c.repository_requirements_missing = False
208 c.repository_requirements_missing = False
209 try:
209 try:
210 self.rhodecode_vcs_repo = self.db_repo.scm_instance()
210 self.rhodecode_vcs_repo = self.db_repo.scm_instance()
211 self.path_filter = PathFilter(self.rhodecode_vcs_repo.get_path_permissions(c.auth_user.username))
211 self.path_filter = PathFilter(self.rhodecode_vcs_repo.get_path_permissions(c.auth_user.username))
212 except RepositoryRequirementError as e:
212 except RepositoryRequirementError as e:
213 c.repository_requirements_missing = True
213 c.repository_requirements_missing = True
214 self._handle_missing_requirements(e)
214 self._handle_missing_requirements(e)
215 self.rhodecode_vcs_repo = None
215 self.rhodecode_vcs_repo = None
216 self.path_filter = None
216 self.path_filter = None
217
217
218 c.path_filter = self.path_filter # used by atom_feed_entry.mako
218 c.path_filter = self.path_filter # used by atom_feed_entry.mako
219
219
220 if (not c.repository_requirements_missing
220 if (not c.repository_requirements_missing
221 and self.rhodecode_vcs_repo is None):
221 and self.rhodecode_vcs_repo is None):
222 # unable to fetch this repo as vcs instance, report back to user
222 # unable to fetch this repo as vcs instance, report back to user
223 h.flash(_(
223 h.flash(_(
224 "The repository `%(repo_name)s` cannot be loaded in filesystem. "
224 "The repository `%(repo_name)s` cannot be loaded in filesystem. "
225 "Please check if it exist, or is not damaged.") %
225 "Please check if it exist, or is not damaged.") %
226 {'repo_name': c.repo_name},
226 {'repo_name': c.repo_name},
227 category='error', ignore_duplicate=True)
227 category='error', ignore_duplicate=True)
228 raise HTTPFound(h.route_path('home'))
228 raise HTTPFound(h.route_path('home'))
229
229
230 return c
230 return c
231
231
232 def _get_f_path(self, matchdict, default=None):
232 def _get_f_path_unchecked(self, matchdict, default=None):
233 """
234 Should only be used by redirects, everything else should call _get_f_path
235 """
233 f_path = matchdict.get('f_path')
236 f_path = matchdict.get('f_path')
234 if f_path:
237 if f_path:
235 # fix for multiple initial slashes that causes errors for GIT
238 # fix for multiple initial slashes that causes errors for GIT
236 return self.path_filter.assert_path_permissions(f_path.lstrip('/'))
239 return f_path.lstrip('/')
237
240
238 return self.path_filter.assert_path_permissions(default)
241 return default
242
243 def _get_f_path(self, matchdict, default=None):
244 return self.path_filter.assert_path_permissions(self._get_f_path_unchecked(matchdict, default))
239
245
240
246
241 class PathFilter(object):
247 class PathFilter(object):
242
248
243 # Expects and instance of BasePathPermissionChecker or None
249 # Expects and instance of BasePathPermissionChecker or None
244 def __init__(self, permission_checker):
250 def __init__(self, permission_checker):
245 self.permission_checker = permission_checker
251 self.permission_checker = permission_checker
246
252
247 def assert_path_permissions(self, path):
253 def assert_path_permissions(self, path):
248 if path and self.permission_checker and not self.permission_checker.has_access(path):
254 if path and self.permission_checker and not self.permission_checker.has_access(path):
249 raise HTTPForbidden()
255 raise HTTPForbidden()
250 return path
256 return path
251
257
252 def filter_patchset(self, patchset):
258 def filter_patchset(self, patchset):
253 if not self.permission_checker or not patchset:
259 if not self.permission_checker or not patchset:
254 return patchset, False
260 return patchset, False
255 had_filtered = False
261 had_filtered = False
256 filtered_patchset = []
262 filtered_patchset = []
257 for patch in patchset:
263 for patch in patchset:
258 filename = patch.get('filename', None)
264 filename = patch.get('filename', None)
259 if not filename or self.permission_checker.has_access(filename):
265 if not filename or self.permission_checker.has_access(filename):
260 filtered_patchset.append(patch)
266 filtered_patchset.append(patch)
261 else:
267 else:
262 had_filtered = True
268 had_filtered = True
263 if had_filtered:
269 if had_filtered:
264 if isinstance(patchset, diffs.LimitedDiffContainer):
270 if isinstance(patchset, diffs.LimitedDiffContainer):
265 filtered_patchset = diffs.LimitedDiffContainer(patchset.diff_limit, patchset.cur_diff_size, filtered_patchset)
271 filtered_patchset = diffs.LimitedDiffContainer(patchset.diff_limit, patchset.cur_diff_size, filtered_patchset)
266 return filtered_patchset, True
272 return filtered_patchset, True
267 else:
273 else:
268 return patchset, False
274 return patchset, False
269
275
270 def render_patchset_filtered(self, diffset, patchset, source_ref=None, target_ref=None):
276 def render_patchset_filtered(self, diffset, patchset, source_ref=None, target_ref=None):
271 filtered_patchset, has_hidden_changes = self.filter_patchset(patchset)
277 filtered_patchset, has_hidden_changes = self.filter_patchset(patchset)
272 result = diffset.render_patchset(filtered_patchset, source_ref=source_ref, target_ref=target_ref)
278 result = diffset.render_patchset(filtered_patchset, source_ref=source_ref, target_ref=target_ref)
273 result.has_hidden_changes = has_hidden_changes
279 result.has_hidden_changes = has_hidden_changes
274 return result
280 return result
275
281
276 def get_raw_patch(self, diff_processor):
282 def get_raw_patch(self, diff_processor):
277 if self.permission_checker is None:
283 if self.permission_checker is None:
278 return diff_processor.as_raw()
284 return diff_processor.as_raw()
279 elif self.permission_checker.has_full_access:
285 elif self.permission_checker.has_full_access:
280 return diff_processor.as_raw()
286 return diff_processor.as_raw()
281 else:
287 else:
282 return '# Repository has user-specific filters, raw patch generation is disabled.'
288 return '# Repository has user-specific filters, raw patch generation is disabled.'
283
289
284 @property
290 @property
285 def is_enabled(self):
291 def is_enabled(self):
286 return self.permission_checker is not None
292 return self.permission_checker is not None
287
293
288
294
289 class RepoGroupAppView(BaseAppView):
295 class RepoGroupAppView(BaseAppView):
290 def __init__(self, context, request):
296 def __init__(self, context, request):
291 super(RepoGroupAppView, self).__init__(context, request)
297 super(RepoGroupAppView, self).__init__(context, request)
292 self.db_repo_group = request.db_repo_group
298 self.db_repo_group = request.db_repo_group
293 self.db_repo_group_name = self.db_repo_group.group_name
299 self.db_repo_group_name = self.db_repo_group.group_name
294
300
295 def _revoke_perms_on_yourself(self, form_result):
301 def _revoke_perms_on_yourself(self, form_result):
296 _updates = filter(lambda u: self._rhodecode_user.user_id == int(u[0]),
302 _updates = filter(lambda u: self._rhodecode_user.user_id == int(u[0]),
297 form_result['perm_updates'])
303 form_result['perm_updates'])
298 _additions = filter(lambda u: self._rhodecode_user.user_id == int(u[0]),
304 _additions = filter(lambda u: self._rhodecode_user.user_id == int(u[0]),
299 form_result['perm_additions'])
305 form_result['perm_additions'])
300 _deletions = filter(lambda u: self._rhodecode_user.user_id == int(u[0]),
306 _deletions = filter(lambda u: self._rhodecode_user.user_id == int(u[0]),
301 form_result['perm_deletions'])
307 form_result['perm_deletions'])
302 admin_perm = 'group.admin'
308 admin_perm = 'group.admin'
303 if _updates and _updates[0][1] != admin_perm or \
309 if _updates and _updates[0][1] != admin_perm or \
304 _additions and _additions[0][1] != admin_perm or \
310 _additions and _additions[0][1] != admin_perm or \
305 _deletions and _deletions[0][1] != admin_perm:
311 _deletions and _deletions[0][1] != admin_perm:
306 return True
312 return True
307 return False
313 return False
308
314
309
315
310 class UserGroupAppView(BaseAppView):
316 class UserGroupAppView(BaseAppView):
311 def __init__(self, context, request):
317 def __init__(self, context, request):
312 super(UserGroupAppView, self).__init__(context, request)
318 super(UserGroupAppView, self).__init__(context, request)
313 self.db_user_group = request.db_user_group
319 self.db_user_group = request.db_user_group
314 self.db_user_group_name = self.db_user_group.users_group_name
320 self.db_user_group_name = self.db_user_group.users_group_name
315
321
316
322
317 class UserAppView(BaseAppView):
323 class UserAppView(BaseAppView):
318 def __init__(self, context, request):
324 def __init__(self, context, request):
319 super(UserAppView, self).__init__(context, request)
325 super(UserAppView, self).__init__(context, request)
320 self.db_user = request.db_user
326 self.db_user = request.db_user
321 self.db_user_id = self.db_user.user_id
327 self.db_user_id = self.db_user.user_id
322
328
323 _ = self.request.translate
329 _ = self.request.translate
324 if not request.db_user_supports_default:
330 if not request.db_user_supports_default:
325 if self.db_user.username == User.DEFAULT_USER:
331 if self.db_user.username == User.DEFAULT_USER:
326 h.flash(_("Editing user `{}` is disabled.".format(
332 h.flash(_("Editing user `{}` is disabled.".format(
327 User.DEFAULT_USER)), category='warning')
333 User.DEFAULT_USER)), category='warning')
328 raise HTTPFound(h.route_path('users'))
334 raise HTTPFound(h.route_path('users'))
329
335
330
336
331 class DataGridAppView(object):
337 class DataGridAppView(object):
332 """
338 """
333 Common class to have re-usable grid rendering components
339 Common class to have re-usable grid rendering components
334 """
340 """
335
341
336 def _extract_ordering(self, request, column_map=None):
342 def _extract_ordering(self, request, column_map=None):
337 column_map = column_map or {}
343 column_map = column_map or {}
338 column_index = safe_int(request.GET.get('order[0][column]'))
344 column_index = safe_int(request.GET.get('order[0][column]'))
339 order_dir = request.GET.get(
345 order_dir = request.GET.get(
340 'order[0][dir]', 'desc')
346 'order[0][dir]', 'desc')
341 order_by = request.GET.get(
347 order_by = request.GET.get(
342 'columns[%s][data][sort]' % column_index, 'name_raw')
348 'columns[%s][data][sort]' % column_index, 'name_raw')
343
349
344 # translate datatable to DB columns
350 # translate datatable to DB columns
345 order_by = column_map.get(order_by) or order_by
351 order_by = column_map.get(order_by) or order_by
346
352
347 search_q = request.GET.get('search[value]')
353 search_q = request.GET.get('search[value]')
348 return search_q, order_by, order_dir
354 return search_q, order_by, order_dir
349
355
350 def _extract_chunk(self, request):
356 def _extract_chunk(self, request):
351 start = safe_int(request.GET.get('start'), 0)
357 start = safe_int(request.GET.get('start'), 0)
352 length = safe_int(request.GET.get('length'), 25)
358 length = safe_int(request.GET.get('length'), 25)
353 draw = safe_int(request.GET.get('draw'))
359 draw = safe_int(request.GET.get('draw'))
354 return draw, start, length
360 return draw, start, length
355
361
356 def _get_order_col(self, order_by, model):
362 def _get_order_col(self, order_by, model):
357 if isinstance(order_by, basestring):
363 if isinstance(order_by, basestring):
358 try:
364 try:
359 return operator.attrgetter(order_by)(model)
365 return operator.attrgetter(order_by)(model)
360 except AttributeError:
366 except AttributeError:
361 return None
367 return None
362 else:
368 else:
363 return order_by
369 return order_by
364
370
365
371
366 class BaseReferencesView(RepoAppView):
372 class BaseReferencesView(RepoAppView):
367 """
373 """
368 Base for reference view for branches, tags and bookmarks.
374 Base for reference view for branches, tags and bookmarks.
369 """
375 """
370 def load_default_context(self):
376 def load_default_context(self):
371 c = self._get_local_tmpl_context()
377 c = self._get_local_tmpl_context()
372
378
373
379
374 return c
380 return c
375
381
376 def load_refs_context(self, ref_items, partials_template):
382 def load_refs_context(self, ref_items, partials_template):
377 _render = self.request.get_partial_renderer(partials_template)
383 _render = self.request.get_partial_renderer(partials_template)
378 pre_load = ["author", "date", "message"]
384 pre_load = ["author", "date", "message"]
379
385
380 is_svn = h.is_svn(self.rhodecode_vcs_repo)
386 is_svn = h.is_svn(self.rhodecode_vcs_repo)
381 is_hg = h.is_hg(self.rhodecode_vcs_repo)
387 is_hg = h.is_hg(self.rhodecode_vcs_repo)
382
388
383 format_ref_id = get_format_ref_id(self.rhodecode_vcs_repo)
389 format_ref_id = get_format_ref_id(self.rhodecode_vcs_repo)
384
390
385 closed_refs = {}
391 closed_refs = {}
386 if is_hg:
392 if is_hg:
387 closed_refs = self.rhodecode_vcs_repo.branches_closed
393 closed_refs = self.rhodecode_vcs_repo.branches_closed
388
394
389 data = []
395 data = []
390 for ref_name, commit_id in ref_items:
396 for ref_name, commit_id in ref_items:
391 commit = self.rhodecode_vcs_repo.get_commit(
397 commit = self.rhodecode_vcs_repo.get_commit(
392 commit_id=commit_id, pre_load=pre_load)
398 commit_id=commit_id, pre_load=pre_load)
393 closed = ref_name in closed_refs
399 closed = ref_name in closed_refs
394
400
395 # TODO: johbo: Unify generation of reference links
401 # TODO: johbo: Unify generation of reference links
396 use_commit_id = '/' in ref_name or is_svn
402 use_commit_id = '/' in ref_name or is_svn
397
403
398 if use_commit_id:
404 if use_commit_id:
399 files_url = h.route_path(
405 files_url = h.route_path(
400 'repo_files',
406 'repo_files',
401 repo_name=self.db_repo_name,
407 repo_name=self.db_repo_name,
402 f_path=ref_name if is_svn else '',
408 f_path=ref_name if is_svn else '',
403 commit_id=commit_id)
409 commit_id=commit_id)
404
410
405 else:
411 else:
406 files_url = h.route_path(
412 files_url = h.route_path(
407 'repo_files',
413 'repo_files',
408 repo_name=self.db_repo_name,
414 repo_name=self.db_repo_name,
409 f_path=ref_name if is_svn else '',
415 f_path=ref_name if is_svn else '',
410 commit_id=ref_name,
416 commit_id=ref_name,
411 _query=dict(at=ref_name))
417 _query=dict(at=ref_name))
412
418
413 data.append({
419 data.append({
414 "name": _render('name', ref_name, files_url, closed),
420 "name": _render('name', ref_name, files_url, closed),
415 "name_raw": ref_name,
421 "name_raw": ref_name,
416 "date": _render('date', commit.date),
422 "date": _render('date', commit.date),
417 "date_raw": datetime_to_time(commit.date),
423 "date_raw": datetime_to_time(commit.date),
418 "author": _render('author', commit.author),
424 "author": _render('author', commit.author),
419 "commit": _render(
425 "commit": _render(
420 'commit', commit.message, commit.raw_id, commit.idx),
426 'commit', commit.message, commit.raw_id, commit.idx),
421 "commit_raw": commit.idx,
427 "commit_raw": commit.idx,
422 "compare": _render(
428 "compare": _render(
423 'compare', format_ref_id(ref_name, commit.raw_id)),
429 'compare', format_ref_id(ref_name, commit.raw_id)),
424 })
430 })
425
431
426 return data
432 return data
427
433
428
434
429 class RepoRoutePredicate(object):
435 class RepoRoutePredicate(object):
430 def __init__(self, val, config):
436 def __init__(self, val, config):
431 self.val = val
437 self.val = val
432
438
433 def text(self):
439 def text(self):
434 return 'repo_route = %s' % self.val
440 return 'repo_route = %s' % self.val
435
441
436 phash = text
442 phash = text
437
443
438 def __call__(self, info, request):
444 def __call__(self, info, request):
439
445
440 if hasattr(request, 'vcs_call'):
446 if hasattr(request, 'vcs_call'):
441 # skip vcs calls
447 # skip vcs calls
442 return
448 return
443
449
444 repo_name = info['match']['repo_name']
450 repo_name = info['match']['repo_name']
445 repo_model = repo.RepoModel()
451 repo_model = repo.RepoModel()
446 by_name_match = repo_model.get_by_repo_name(repo_name, cache=True)
452 by_name_match = repo_model.get_by_repo_name(repo_name, cache=True)
447
453
448 def redirect_if_creating(db_repo):
454 def redirect_if_creating(db_repo):
449 if db_repo.repo_state in [repo.Repository.STATE_PENDING]:
455 if db_repo.repo_state in [repo.Repository.STATE_PENDING]:
450 raise HTTPFound(
456 raise HTTPFound(
451 request.route_path('repo_creating',
457 request.route_path('repo_creating',
452 repo_name=db_repo.repo_name))
458 repo_name=db_repo.repo_name))
453
459
454 if by_name_match:
460 if by_name_match:
455 # register this as request object we can re-use later
461 # register this as request object we can re-use later
456 request.db_repo = by_name_match
462 request.db_repo = by_name_match
457 redirect_if_creating(by_name_match)
463 redirect_if_creating(by_name_match)
458 return True
464 return True
459
465
460 by_id_match = repo_model.get_repo_by_id(repo_name)
466 by_id_match = repo_model.get_repo_by_id(repo_name)
461 if by_id_match:
467 if by_id_match:
462 request.db_repo = by_id_match
468 request.db_repo = by_id_match
463 redirect_if_creating(by_id_match)
469 redirect_if_creating(by_id_match)
464 return True
470 return True
465
471
466 return False
472 return False
467
473
468
474
469 class RepoTypeRoutePredicate(object):
475 class RepoTypeRoutePredicate(object):
470 def __init__(self, val, config):
476 def __init__(self, val, config):
471 self.val = val or ['hg', 'git', 'svn']
477 self.val = val or ['hg', 'git', 'svn']
472
478
473 def text(self):
479 def text(self):
474 return 'repo_accepted_type = %s' % self.val
480 return 'repo_accepted_type = %s' % self.val
475
481
476 phash = text
482 phash = text
477
483
478 def __call__(self, info, request):
484 def __call__(self, info, request):
479 if hasattr(request, 'vcs_call'):
485 if hasattr(request, 'vcs_call'):
480 # skip vcs calls
486 # skip vcs calls
481 return
487 return
482
488
483 rhodecode_db_repo = request.db_repo
489 rhodecode_db_repo = request.db_repo
484
490
485 log.debug(
491 log.debug(
486 '%s checking repo type for %s in %s',
492 '%s checking repo type for %s in %s',
487 self.__class__.__name__, rhodecode_db_repo.repo_type, self.val)
493 self.__class__.__name__, rhodecode_db_repo.repo_type, self.val)
488
494
489 if rhodecode_db_repo.repo_type in self.val:
495 if rhodecode_db_repo.repo_type in self.val:
490 return True
496 return True
491 else:
497 else:
492 log.warning('Current view is not supported for repo type:%s',
498 log.warning('Current view is not supported for repo type:%s',
493 rhodecode_db_repo.repo_type)
499 rhodecode_db_repo.repo_type)
494 #
500 #
495 # h.flash(h.literal(
501 # h.flash(h.literal(
496 # _('Action not supported for %s.' % rhodecode_repo.alias)),
502 # _('Action not supported for %s.' % rhodecode_repo.alias)),
497 # category='warning')
503 # category='warning')
498 # return redirect(
504 # return redirect(
499 # route_path('repo_summary', repo_name=cls.rhodecode_db_repo.repo_name))
505 # route_path('repo_summary', repo_name=cls.rhodecode_db_repo.repo_name))
500
506
501 return False
507 return False
502
508
503
509
504 class RepoGroupRoutePredicate(object):
510 class RepoGroupRoutePredicate(object):
505 def __init__(self, val, config):
511 def __init__(self, val, config):
506 self.val = val
512 self.val = val
507
513
508 def text(self):
514 def text(self):
509 return 'repo_group_route = %s' % self.val
515 return 'repo_group_route = %s' % self.val
510
516
511 phash = text
517 phash = text
512
518
513 def __call__(self, info, request):
519 def __call__(self, info, request):
514 if hasattr(request, 'vcs_call'):
520 if hasattr(request, 'vcs_call'):
515 # skip vcs calls
521 # skip vcs calls
516 return
522 return
517
523
518 repo_group_name = info['match']['repo_group_name']
524 repo_group_name = info['match']['repo_group_name']
519 repo_group_model = repo_group.RepoGroupModel()
525 repo_group_model = repo_group.RepoGroupModel()
520 by_name_match = repo_group_model.get_by_group_name(
526 by_name_match = repo_group_model.get_by_group_name(
521 repo_group_name, cache=True)
527 repo_group_name, cache=True)
522
528
523 if by_name_match:
529 if by_name_match:
524 # register this as request object we can re-use later
530 # register this as request object we can re-use later
525 request.db_repo_group = by_name_match
531 request.db_repo_group = by_name_match
526 return True
532 return True
527
533
528 return False
534 return False
529
535
530
536
531 class UserGroupRoutePredicate(object):
537 class UserGroupRoutePredicate(object):
532 def __init__(self, val, config):
538 def __init__(self, val, config):
533 self.val = val
539 self.val = val
534
540
535 def text(self):
541 def text(self):
536 return 'user_group_route = %s' % self.val
542 return 'user_group_route = %s' % self.val
537
543
538 phash = text
544 phash = text
539
545
540 def __call__(self, info, request):
546 def __call__(self, info, request):
541 if hasattr(request, 'vcs_call'):
547 if hasattr(request, 'vcs_call'):
542 # skip vcs calls
548 # skip vcs calls
543 return
549 return
544
550
545 user_group_id = info['match']['user_group_id']
551 user_group_id = info['match']['user_group_id']
546 user_group_model = user_group.UserGroup()
552 user_group_model = user_group.UserGroup()
547 by_id_match = user_group_model.get(
553 by_id_match = user_group_model.get(
548 user_group_id, cache=True)
554 user_group_id, cache=True)
549
555
550 if by_id_match:
556 if by_id_match:
551 # register this as request object we can re-use later
557 # register this as request object we can re-use later
552 request.db_user_group = by_id_match
558 request.db_user_group = by_id_match
553 return True
559 return True
554
560
555 return False
561 return False
556
562
557
563
558 class UserRoutePredicateBase(object):
564 class UserRoutePredicateBase(object):
559 supports_default = None
565 supports_default = None
560
566
561 def __init__(self, val, config):
567 def __init__(self, val, config):
562 self.val = val
568 self.val = val
563
569
564 def text(self):
570 def text(self):
565 raise NotImplementedError()
571 raise NotImplementedError()
566
572
567 def __call__(self, info, request):
573 def __call__(self, info, request):
568 if hasattr(request, 'vcs_call'):
574 if hasattr(request, 'vcs_call'):
569 # skip vcs calls
575 # skip vcs calls
570 return
576 return
571
577
572 user_id = info['match']['user_id']
578 user_id = info['match']['user_id']
573 user_model = user.User()
579 user_model = user.User()
574 by_id_match = user_model.get(
580 by_id_match = user_model.get(
575 user_id, cache=True)
581 user_id, cache=True)
576
582
577 if by_id_match:
583 if by_id_match:
578 # register this as request object we can re-use later
584 # register this as request object we can re-use later
579 request.db_user = by_id_match
585 request.db_user = by_id_match
580 request.db_user_supports_default = self.supports_default
586 request.db_user_supports_default = self.supports_default
581 return True
587 return True
582
588
583 return False
589 return False
584
590
585
591
586 class UserRoutePredicate(UserRoutePredicateBase):
592 class UserRoutePredicate(UserRoutePredicateBase):
587 supports_default = False
593 supports_default = False
588
594
589 def text(self):
595 def text(self):
590 return 'user_route = %s' % self.val
596 return 'user_route = %s' % self.val
591
597
592 phash = text
598 phash = text
593
599
594
600
595 class UserRouteWithDefaultPredicate(UserRoutePredicateBase):
601 class UserRouteWithDefaultPredicate(UserRoutePredicateBase):
596 supports_default = True
602 supports_default = True
597
603
598 def text(self):
604 def text(self):
599 return 'user_with_default_route = %s' % self.val
605 return 'user_with_default_route = %s' % self.val
600
606
601 phash = text
607 phash = text
602
608
603
609
604 def includeme(config):
610 def includeme(config):
605 config.add_route_predicate(
611 config.add_route_predicate(
606 'repo_route', RepoRoutePredicate)
612 'repo_route', RepoRoutePredicate)
607 config.add_route_predicate(
613 config.add_route_predicate(
608 'repo_accepted_types', RepoTypeRoutePredicate)
614 'repo_accepted_types', RepoTypeRoutePredicate)
609 config.add_route_predicate(
615 config.add_route_predicate(
610 'repo_group_route', RepoGroupRoutePredicate)
616 'repo_group_route', RepoGroupRoutePredicate)
611 config.add_route_predicate(
617 config.add_route_predicate(
612 'user_group_route', UserGroupRoutePredicate)
618 'user_group_route', UserGroupRoutePredicate)
613 config.add_route_predicate(
619 config.add_route_predicate(
614 'user_route_with_default', UserRouteWithDefaultPredicate)
620 'user_route_with_default', UserRouteWithDefaultPredicate)
615 config.add_route_predicate(
621 config.add_route_predicate(
616 'user_route', UserRoutePredicate) No newline at end of file
622 'user_route', UserRoutePredicate)
@@ -1,1292 +1,1292 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2018 RhodeCode GmbH
3 # Copyright (C) 2011-2018 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
27
28 from pyramid.httpexceptions import HTTPNotFound, HTTPBadRequest, HTTPFound
28 from pyramid.httpexceptions import HTTPNotFound, HTTPBadRequest, HTTPFound
29 from pyramid.view import view_config
29 from pyramid.view import view_config
30 from pyramid.renderers import render
30 from pyramid.renderers import render
31 from pyramid.response import Response
31 from pyramid.response import Response
32
32
33 from rhodecode.apps._base import RepoAppView
33 from rhodecode.apps._base import RepoAppView
34
34
35 from rhodecode.controllers.utils import parse_path_ref
35 from rhodecode.controllers.utils import parse_path_ref
36 from rhodecode.lib import diffs, helpers as h, caches
36 from rhodecode.lib import diffs, helpers as h, caches
37 from rhodecode.lib import audit_logger
37 from rhodecode.lib import audit_logger
38 from rhodecode.lib.exceptions import NonRelativePathError
38 from rhodecode.lib.exceptions import NonRelativePathError
39 from rhodecode.lib.codeblocks import (
39 from rhodecode.lib.codeblocks import (
40 filenode_as_lines_tokens, filenode_as_annotated_lines_tokens)
40 filenode_as_lines_tokens, filenode_as_annotated_lines_tokens)
41 from rhodecode.lib.utils2 import (
41 from rhodecode.lib.utils2 import (
42 convert_line_endings, detect_mode, safe_str, str2bool)
42 convert_line_endings, detect_mode, safe_str, str2bool)
43 from rhodecode.lib.auth import (
43 from rhodecode.lib.auth import (
44 LoginRequired, HasRepoPermissionAnyDecorator, CSRFRequired)
44 LoginRequired, HasRepoPermissionAnyDecorator, CSRFRequired)
45 from rhodecode.lib.vcs import path as vcspath
45 from rhodecode.lib.vcs import path as vcspath
46 from rhodecode.lib.vcs.backends.base import EmptyCommit
46 from rhodecode.lib.vcs.backends.base import EmptyCommit
47 from rhodecode.lib.vcs.conf import settings
47 from rhodecode.lib.vcs.conf import settings
48 from rhodecode.lib.vcs.nodes import FileNode
48 from rhodecode.lib.vcs.nodes import FileNode
49 from rhodecode.lib.vcs.exceptions import (
49 from rhodecode.lib.vcs.exceptions import (
50 RepositoryError, CommitDoesNotExistError, EmptyRepositoryError,
50 RepositoryError, CommitDoesNotExistError, EmptyRepositoryError,
51 ImproperArchiveTypeError, VCSError, NodeAlreadyExistsError,
51 ImproperArchiveTypeError, VCSError, NodeAlreadyExistsError,
52 NodeDoesNotExistError, CommitError, NodeError)
52 NodeDoesNotExistError, CommitError, NodeError)
53
53
54 from rhodecode.model.scm import ScmModel
54 from rhodecode.model.scm import ScmModel
55 from rhodecode.model.db import Repository
55 from rhodecode.model.db import Repository
56
56
57 log = logging.getLogger(__name__)
57 log = logging.getLogger(__name__)
58
58
59
59
60 class RepoFilesView(RepoAppView):
60 class RepoFilesView(RepoAppView):
61
61
62 @staticmethod
62 @staticmethod
63 def adjust_file_path_for_svn(f_path, repo):
63 def adjust_file_path_for_svn(f_path, repo):
64 """
64 """
65 Computes the relative path of `f_path`.
65 Computes the relative path of `f_path`.
66
66
67 This is mainly based on prefix matching of the recognized tags and
67 This is mainly based on prefix matching of the recognized tags and
68 branches in the underlying repository.
68 branches in the underlying repository.
69 """
69 """
70 tags_and_branches = itertools.chain(
70 tags_and_branches = itertools.chain(
71 repo.branches.iterkeys(),
71 repo.branches.iterkeys(),
72 repo.tags.iterkeys())
72 repo.tags.iterkeys())
73 tags_and_branches = sorted(tags_and_branches, key=len, reverse=True)
73 tags_and_branches = sorted(tags_and_branches, key=len, reverse=True)
74
74
75 for name in tags_and_branches:
75 for name in tags_and_branches:
76 if f_path.startswith('{}/'.format(name)):
76 if f_path.startswith('{}/'.format(name)):
77 f_path = vcspath.relpath(f_path, name)
77 f_path = vcspath.relpath(f_path, name)
78 break
78 break
79 return f_path
79 return f_path
80
80
81 def load_default_context(self):
81 def load_default_context(self):
82 c = self._get_local_tmpl_context(include_app_defaults=True)
82 c = self._get_local_tmpl_context(include_app_defaults=True)
83
83
84 c.rhodecode_repo = self.rhodecode_vcs_repo
84 c.rhodecode_repo = self.rhodecode_vcs_repo
85
85
86
86
87 return c
87 return c
88
88
89 def _ensure_not_locked(self):
89 def _ensure_not_locked(self):
90 _ = self.request.translate
90 _ = self.request.translate
91
91
92 repo = self.db_repo
92 repo = self.db_repo
93 if repo.enable_locking and repo.locked[0]:
93 if repo.enable_locking and repo.locked[0]:
94 h.flash(_('This repository has been locked by %s on %s')
94 h.flash(_('This repository has been locked by %s on %s')
95 % (h.person_by_id(repo.locked[0]),
95 % (h.person_by_id(repo.locked[0]),
96 h.format_date(h.time_to_datetime(repo.locked[1]))),
96 h.format_date(h.time_to_datetime(repo.locked[1]))),
97 'warning')
97 'warning')
98 files_url = h.route_path(
98 files_url = h.route_path(
99 'repo_files:default_path',
99 'repo_files:default_path',
100 repo_name=self.db_repo_name, commit_id='tip')
100 repo_name=self.db_repo_name, commit_id='tip')
101 raise HTTPFound(files_url)
101 raise HTTPFound(files_url)
102
102
103 def _get_commit_and_path(self):
103 def _get_commit_and_path(self):
104 default_commit_id = self.db_repo.landing_rev[1]
104 default_commit_id = self.db_repo.landing_rev[1]
105 default_f_path = '/'
105 default_f_path = '/'
106
106
107 commit_id = self.request.matchdict.get(
107 commit_id = self.request.matchdict.get(
108 'commit_id', default_commit_id)
108 'commit_id', default_commit_id)
109 f_path = self._get_f_path(self.request.matchdict, default_f_path)
109 f_path = self._get_f_path(self.request.matchdict, default_f_path)
110 return commit_id, f_path
110 return commit_id, f_path
111
111
112 def _get_default_encoding(self, c):
112 def _get_default_encoding(self, c):
113 enc_list = getattr(c, 'default_encodings', [])
113 enc_list = getattr(c, 'default_encodings', [])
114 return enc_list[0] if enc_list else 'UTF-8'
114 return enc_list[0] if enc_list else 'UTF-8'
115
115
116 def _get_commit_or_redirect(self, commit_id, redirect_after=True):
116 def _get_commit_or_redirect(self, commit_id, redirect_after=True):
117 """
117 """
118 This is a safe way to get commit. If an error occurs it redirects to
118 This is a safe way to get commit. If an error occurs it redirects to
119 tip with proper message
119 tip with proper message
120
120
121 :param commit_id: id of commit to fetch
121 :param commit_id: id of commit to fetch
122 :param redirect_after: toggle redirection
122 :param redirect_after: toggle redirection
123 """
123 """
124 _ = self.request.translate
124 _ = self.request.translate
125
125
126 try:
126 try:
127 return self.rhodecode_vcs_repo.get_commit(commit_id)
127 return self.rhodecode_vcs_repo.get_commit(commit_id)
128 except EmptyRepositoryError:
128 except EmptyRepositoryError:
129 if not redirect_after:
129 if not redirect_after:
130 return None
130 return None
131
131
132 _url = h.route_path(
132 _url = h.route_path(
133 'repo_files_add_file',
133 'repo_files_add_file',
134 repo_name=self.db_repo_name, commit_id=0, f_path='',
134 repo_name=self.db_repo_name, commit_id=0, f_path='',
135 _anchor='edit')
135 _anchor='edit')
136
136
137 if h.HasRepoPermissionAny(
137 if h.HasRepoPermissionAny(
138 'repository.write', 'repository.admin')(self.db_repo_name):
138 'repository.write', 'repository.admin')(self.db_repo_name):
139 add_new = h.link_to(
139 add_new = h.link_to(
140 _('Click here to add a new file.'), _url, class_="alert-link")
140 _('Click here to add a new file.'), _url, class_="alert-link")
141 else:
141 else:
142 add_new = ""
142 add_new = ""
143
143
144 h.flash(h.literal(
144 h.flash(h.literal(
145 _('There are no files yet. %s') % add_new), category='warning')
145 _('There are no files yet. %s') % add_new), category='warning')
146 raise HTTPFound(
146 raise HTTPFound(
147 h.route_path('repo_summary', repo_name=self.db_repo_name))
147 h.route_path('repo_summary', repo_name=self.db_repo_name))
148
148
149 except (CommitDoesNotExistError, LookupError):
149 except (CommitDoesNotExistError, LookupError):
150 msg = _('No such commit exists for this repository')
150 msg = _('No such commit exists for this repository')
151 h.flash(msg, category='error')
151 h.flash(msg, category='error')
152 raise HTTPNotFound()
152 raise HTTPNotFound()
153 except RepositoryError as e:
153 except RepositoryError as e:
154 h.flash(safe_str(h.escape(e)), category='error')
154 h.flash(safe_str(h.escape(e)), category='error')
155 raise HTTPNotFound()
155 raise HTTPNotFound()
156
156
157 def _get_filenode_or_redirect(self, commit_obj, path):
157 def _get_filenode_or_redirect(self, commit_obj, path):
158 """
158 """
159 Returns file_node, if error occurs or given path is directory,
159 Returns file_node, if error occurs or given path is directory,
160 it'll redirect to top level path
160 it'll redirect to top level path
161 """
161 """
162 _ = self.request.translate
162 _ = self.request.translate
163
163
164 try:
164 try:
165 file_node = commit_obj.get_node(path)
165 file_node = commit_obj.get_node(path)
166 if file_node.is_dir():
166 if file_node.is_dir():
167 raise RepositoryError('The given path is a directory')
167 raise RepositoryError('The given path is a directory')
168 except CommitDoesNotExistError:
168 except CommitDoesNotExistError:
169 log.exception('No such commit exists for this repository')
169 log.exception('No such commit exists for this repository')
170 h.flash(_('No such commit exists for this repository'), category='error')
170 h.flash(_('No such commit exists for this repository'), category='error')
171 raise HTTPNotFound()
171 raise HTTPNotFound()
172 except RepositoryError as e:
172 except RepositoryError as e:
173 log.warning('Repository error while fetching '
173 log.warning('Repository error while fetching '
174 'filenode `%s`. Err:%s', path, e)
174 'filenode `%s`. Err:%s', path, e)
175 h.flash(safe_str(h.escape(e)), category='error')
175 h.flash(safe_str(h.escape(e)), category='error')
176 raise HTTPNotFound()
176 raise HTTPNotFound()
177
177
178 return file_node
178 return file_node
179
179
180 def _is_valid_head(self, commit_id, repo):
180 def _is_valid_head(self, commit_id, repo):
181 # check if commit is a branch identifier- basically we cannot
181 # check if commit is a branch identifier- basically we cannot
182 # create multiple heads via file editing
182 # create multiple heads via file editing
183 valid_heads = repo.branches.keys() + repo.branches.values()
183 valid_heads = repo.branches.keys() + repo.branches.values()
184
184
185 if h.is_svn(repo) and not repo.is_empty():
185 if h.is_svn(repo) and not repo.is_empty():
186 # Note: Subversion only has one head, we add it here in case there
186 # Note: Subversion only has one head, we add it here in case there
187 # is no branch matched.
187 # is no branch matched.
188 valid_heads.append(repo.get_commit(commit_idx=-1).raw_id)
188 valid_heads.append(repo.get_commit(commit_idx=-1).raw_id)
189
189
190 # check if commit is a branch name or branch hash
190 # check if commit is a branch name or branch hash
191 return commit_id in valid_heads
191 return commit_id in valid_heads
192
192
193 def _get_tree_cache_manager(self, namespace_type):
193 def _get_tree_cache_manager(self, namespace_type):
194 _namespace = caches.get_repo_namespace_key(
194 _namespace = caches.get_repo_namespace_key(
195 namespace_type, self.db_repo_name)
195 namespace_type, self.db_repo_name)
196 return caches.get_cache_manager('repo_cache_long', _namespace)
196 return caches.get_cache_manager('repo_cache_long', _namespace)
197
197
198 def _get_tree_at_commit(
198 def _get_tree_at_commit(
199 self, c, commit_id, f_path, full_load=False, force=False):
199 self, c, commit_id, f_path, full_load=False, force=False):
200 def _cached_tree():
200 def _cached_tree():
201 log.debug('Generating cached file tree for %s, %s, %s',
201 log.debug('Generating cached file tree for %s, %s, %s',
202 self.db_repo_name, commit_id, f_path)
202 self.db_repo_name, commit_id, f_path)
203
203
204 c.full_load = full_load
204 c.full_load = full_load
205 return render(
205 return render(
206 'rhodecode:templates/files/files_browser_tree.mako',
206 'rhodecode:templates/files/files_browser_tree.mako',
207 self._get_template_context(c), self.request)
207 self._get_template_context(c), self.request)
208
208
209 cache_manager = self._get_tree_cache_manager(caches.FILE_TREE)
209 cache_manager = self._get_tree_cache_manager(caches.FILE_TREE)
210
210
211 cache_key = caches.compute_key_from_params(
211 cache_key = caches.compute_key_from_params(
212 self.db_repo_name, commit_id, f_path)
212 self.db_repo_name, commit_id, f_path)
213
213
214 if force:
214 if force:
215 # we want to force recompute of caches
215 # we want to force recompute of caches
216 cache_manager.remove_value(cache_key)
216 cache_manager.remove_value(cache_key)
217
217
218 return cache_manager.get(cache_key, createfunc=_cached_tree)
218 return cache_manager.get(cache_key, createfunc=_cached_tree)
219
219
220 def _get_archive_spec(self, fname):
220 def _get_archive_spec(self, fname):
221 log.debug('Detecting archive spec for: `%s`', fname)
221 log.debug('Detecting archive spec for: `%s`', fname)
222
222
223 fileformat = None
223 fileformat = None
224 ext = None
224 ext = None
225 content_type = None
225 content_type = None
226 for a_type, ext_data in settings.ARCHIVE_SPECS.items():
226 for a_type, ext_data in settings.ARCHIVE_SPECS.items():
227 content_type, extension = ext_data
227 content_type, extension = ext_data
228
228
229 if fname.endswith(extension):
229 if fname.endswith(extension):
230 fileformat = a_type
230 fileformat = a_type
231 log.debug('archive is of type: %s', fileformat)
231 log.debug('archive is of type: %s', fileformat)
232 ext = extension
232 ext = extension
233 break
233 break
234
234
235 if not fileformat:
235 if not fileformat:
236 raise ValueError()
236 raise ValueError()
237
237
238 # left over part of whole fname is the commit
238 # left over part of whole fname is the commit
239 commit_id = fname[:-len(ext)]
239 commit_id = fname[:-len(ext)]
240
240
241 return commit_id, ext, fileformat, content_type
241 return commit_id, ext, fileformat, content_type
242
242
243 @LoginRequired()
243 @LoginRequired()
244 @HasRepoPermissionAnyDecorator(
244 @HasRepoPermissionAnyDecorator(
245 'repository.read', 'repository.write', 'repository.admin')
245 'repository.read', 'repository.write', 'repository.admin')
246 @view_config(
246 @view_config(
247 route_name='repo_archivefile', request_method='GET',
247 route_name='repo_archivefile', request_method='GET',
248 renderer=None)
248 renderer=None)
249 def repo_archivefile(self):
249 def repo_archivefile(self):
250 # archive cache config
250 # archive cache config
251 from rhodecode import CONFIG
251 from rhodecode import CONFIG
252 _ = self.request.translate
252 _ = self.request.translate
253 self.load_default_context()
253 self.load_default_context()
254
254
255 fname = self.request.matchdict['fname']
255 fname = self.request.matchdict['fname']
256 subrepos = self.request.GET.get('subrepos') == 'true'
256 subrepos = self.request.GET.get('subrepos') == 'true'
257
257
258 if not self.db_repo.enable_downloads:
258 if not self.db_repo.enable_downloads:
259 return Response(_('Downloads disabled'))
259 return Response(_('Downloads disabled'))
260
260
261 try:
261 try:
262 commit_id, ext, fileformat, content_type = \
262 commit_id, ext, fileformat, content_type = \
263 self._get_archive_spec(fname)
263 self._get_archive_spec(fname)
264 except ValueError:
264 except ValueError:
265 return Response(_('Unknown archive type for: `{}`').format(
265 return Response(_('Unknown archive type for: `{}`').format(
266 h.escape(fname)))
266 h.escape(fname)))
267
267
268 try:
268 try:
269 commit = self.rhodecode_vcs_repo.get_commit(commit_id)
269 commit = self.rhodecode_vcs_repo.get_commit(commit_id)
270 except CommitDoesNotExistError:
270 except CommitDoesNotExistError:
271 return Response(_('Unknown commit_id {}').format(
271 return Response(_('Unknown commit_id {}').format(
272 h.escape(commit_id)))
272 h.escape(commit_id)))
273 except EmptyRepositoryError:
273 except EmptyRepositoryError:
274 return Response(_('Empty repository'))
274 return Response(_('Empty repository'))
275
275
276 archive_name = '%s-%s%s%s' % (
276 archive_name = '%s-%s%s%s' % (
277 safe_str(self.db_repo_name.replace('/', '_')),
277 safe_str(self.db_repo_name.replace('/', '_')),
278 '-sub' if subrepos else '',
278 '-sub' if subrepos else '',
279 safe_str(commit.short_id), ext)
279 safe_str(commit.short_id), ext)
280
280
281 use_cached_archive = False
281 use_cached_archive = False
282 archive_cache_enabled = CONFIG.get(
282 archive_cache_enabled = CONFIG.get(
283 'archive_cache_dir') and not self.request.GET.get('no_cache')
283 'archive_cache_dir') and not self.request.GET.get('no_cache')
284
284
285 if archive_cache_enabled:
285 if archive_cache_enabled:
286 # check if we it's ok to write
286 # check if we it's ok to write
287 if not os.path.isdir(CONFIG['archive_cache_dir']):
287 if not os.path.isdir(CONFIG['archive_cache_dir']):
288 os.makedirs(CONFIG['archive_cache_dir'])
288 os.makedirs(CONFIG['archive_cache_dir'])
289 cached_archive_path = os.path.join(
289 cached_archive_path = os.path.join(
290 CONFIG['archive_cache_dir'], archive_name)
290 CONFIG['archive_cache_dir'], archive_name)
291 if os.path.isfile(cached_archive_path):
291 if os.path.isfile(cached_archive_path):
292 log.debug('Found cached archive in %s', cached_archive_path)
292 log.debug('Found cached archive in %s', cached_archive_path)
293 fd, archive = None, cached_archive_path
293 fd, archive = None, cached_archive_path
294 use_cached_archive = True
294 use_cached_archive = True
295 else:
295 else:
296 log.debug('Archive %s is not yet cached', archive_name)
296 log.debug('Archive %s is not yet cached', archive_name)
297
297
298 if not use_cached_archive:
298 if not use_cached_archive:
299 # generate new archive
299 # generate new archive
300 fd, archive = tempfile.mkstemp()
300 fd, archive = tempfile.mkstemp()
301 log.debug('Creating new temp archive in %s', archive)
301 log.debug('Creating new temp archive in %s', archive)
302 try:
302 try:
303 commit.archive_repo(archive, kind=fileformat, subrepos=subrepos)
303 commit.archive_repo(archive, kind=fileformat, subrepos=subrepos)
304 except ImproperArchiveTypeError:
304 except ImproperArchiveTypeError:
305 return _('Unknown archive type')
305 return _('Unknown archive type')
306 if archive_cache_enabled:
306 if archive_cache_enabled:
307 # if we generated the archive and we have cache enabled
307 # if we generated the archive and we have cache enabled
308 # let's use this for future
308 # let's use this for future
309 log.debug('Storing new archive in %s', cached_archive_path)
309 log.debug('Storing new archive in %s', cached_archive_path)
310 shutil.move(archive, cached_archive_path)
310 shutil.move(archive, cached_archive_path)
311 archive = cached_archive_path
311 archive = cached_archive_path
312
312
313 # store download action
313 # store download action
314 audit_logger.store_web(
314 audit_logger.store_web(
315 'repo.archive.download', action_data={
315 'repo.archive.download', action_data={
316 'user_agent': self.request.user_agent,
316 'user_agent': self.request.user_agent,
317 'archive_name': archive_name,
317 'archive_name': archive_name,
318 'archive_spec': fname,
318 'archive_spec': fname,
319 'archive_cached': use_cached_archive},
319 'archive_cached': use_cached_archive},
320 user=self._rhodecode_user,
320 user=self._rhodecode_user,
321 repo=self.db_repo,
321 repo=self.db_repo,
322 commit=True
322 commit=True
323 )
323 )
324
324
325 def get_chunked_archive(archive):
325 def get_chunked_archive(archive):
326 with open(archive, 'rb') as stream:
326 with open(archive, 'rb') as stream:
327 while True:
327 while True:
328 data = stream.read(16 * 1024)
328 data = stream.read(16 * 1024)
329 if not data:
329 if not data:
330 if fd: # fd means we used temporary file
330 if fd: # fd means we used temporary file
331 os.close(fd)
331 os.close(fd)
332 if not archive_cache_enabled:
332 if not archive_cache_enabled:
333 log.debug('Destroying temp archive %s', archive)
333 log.debug('Destroying temp archive %s', archive)
334 os.remove(archive)
334 os.remove(archive)
335 break
335 break
336 yield data
336 yield data
337
337
338 response = Response(app_iter=get_chunked_archive(archive))
338 response = Response(app_iter=get_chunked_archive(archive))
339 response.content_disposition = str(
339 response.content_disposition = str(
340 'attachment; filename=%s' % archive_name)
340 'attachment; filename=%s' % archive_name)
341 response.content_type = str(content_type)
341 response.content_type = str(content_type)
342
342
343 return response
343 return response
344
344
345 def _get_file_node(self, commit_id, f_path):
345 def _get_file_node(self, commit_id, f_path):
346 if commit_id not in ['', None, 'None', '0' * 12, '0' * 40]:
346 if commit_id not in ['', None, 'None', '0' * 12, '0' * 40]:
347 commit = self.rhodecode_vcs_repo.get_commit(commit_id=commit_id)
347 commit = self.rhodecode_vcs_repo.get_commit(commit_id=commit_id)
348 try:
348 try:
349 node = commit.get_node(f_path)
349 node = commit.get_node(f_path)
350 if node.is_dir():
350 if node.is_dir():
351 raise NodeError('%s path is a %s not a file'
351 raise NodeError('%s path is a %s not a file'
352 % (node, type(node)))
352 % (node, type(node)))
353 except NodeDoesNotExistError:
353 except NodeDoesNotExistError:
354 commit = EmptyCommit(
354 commit = EmptyCommit(
355 commit_id=commit_id,
355 commit_id=commit_id,
356 idx=commit.idx,
356 idx=commit.idx,
357 repo=commit.repository,
357 repo=commit.repository,
358 alias=commit.repository.alias,
358 alias=commit.repository.alias,
359 message=commit.message,
359 message=commit.message,
360 author=commit.author,
360 author=commit.author,
361 date=commit.date)
361 date=commit.date)
362 node = FileNode(f_path, '', commit=commit)
362 node = FileNode(f_path, '', commit=commit)
363 else:
363 else:
364 commit = EmptyCommit(
364 commit = EmptyCommit(
365 repo=self.rhodecode_vcs_repo,
365 repo=self.rhodecode_vcs_repo,
366 alias=self.rhodecode_vcs_repo.alias)
366 alias=self.rhodecode_vcs_repo.alias)
367 node = FileNode(f_path, '', commit=commit)
367 node = FileNode(f_path, '', commit=commit)
368 return node
368 return node
369
369
370 @LoginRequired()
370 @LoginRequired()
371 @HasRepoPermissionAnyDecorator(
371 @HasRepoPermissionAnyDecorator(
372 'repository.read', 'repository.write', 'repository.admin')
372 'repository.read', 'repository.write', 'repository.admin')
373 @view_config(
373 @view_config(
374 route_name='repo_files_diff', request_method='GET',
374 route_name='repo_files_diff', request_method='GET',
375 renderer=None)
375 renderer=None)
376 def repo_files_diff(self):
376 def repo_files_diff(self):
377 c = self.load_default_context()
377 c = self.load_default_context()
378 f_path = self._get_f_path(self.request.matchdict)
378 f_path = self._get_f_path(self.request.matchdict)
379 diff1 = self.request.GET.get('diff1', '')
379 diff1 = self.request.GET.get('diff1', '')
380 diff2 = self.request.GET.get('diff2', '')
380 diff2 = self.request.GET.get('diff2', '')
381
381
382 path1, diff1 = parse_path_ref(diff1, default_path=f_path)
382 path1, diff1 = parse_path_ref(diff1, default_path=f_path)
383
383
384 ignore_whitespace = str2bool(self.request.GET.get('ignorews'))
384 ignore_whitespace = str2bool(self.request.GET.get('ignorews'))
385 line_context = self.request.GET.get('context', 3)
385 line_context = self.request.GET.get('context', 3)
386
386
387 if not any((diff1, diff2)):
387 if not any((diff1, diff2)):
388 h.flash(
388 h.flash(
389 'Need query parameter "diff1" or "diff2" to generate a diff.',
389 'Need query parameter "diff1" or "diff2" to generate a diff.',
390 category='error')
390 category='error')
391 raise HTTPBadRequest()
391 raise HTTPBadRequest()
392
392
393 c.action = self.request.GET.get('diff')
393 c.action = self.request.GET.get('diff')
394 if c.action not in ['download', 'raw']:
394 if c.action not in ['download', 'raw']:
395 compare_url = h.route_path(
395 compare_url = h.route_path(
396 'repo_compare',
396 'repo_compare',
397 repo_name=self.db_repo_name,
397 repo_name=self.db_repo_name,
398 source_ref_type='rev',
398 source_ref_type='rev',
399 source_ref=diff1,
399 source_ref=diff1,
400 target_repo=self.db_repo_name,
400 target_repo=self.db_repo_name,
401 target_ref_type='rev',
401 target_ref_type='rev',
402 target_ref=diff2,
402 target_ref=diff2,
403 _query=dict(f_path=f_path))
403 _query=dict(f_path=f_path))
404 # redirect to new view if we render diff
404 # redirect to new view if we render diff
405 raise HTTPFound(compare_url)
405 raise HTTPFound(compare_url)
406
406
407 try:
407 try:
408 node1 = self._get_file_node(diff1, path1)
408 node1 = self._get_file_node(diff1, path1)
409 node2 = self._get_file_node(diff2, f_path)
409 node2 = self._get_file_node(diff2, f_path)
410 except (RepositoryError, NodeError):
410 except (RepositoryError, NodeError):
411 log.exception("Exception while trying to get node from repository")
411 log.exception("Exception while trying to get node from repository")
412 raise HTTPFound(
412 raise HTTPFound(
413 h.route_path('repo_files', repo_name=self.db_repo_name,
413 h.route_path('repo_files', repo_name=self.db_repo_name,
414 commit_id='tip', f_path=f_path))
414 commit_id='tip', f_path=f_path))
415
415
416 if all(isinstance(node.commit, EmptyCommit)
416 if all(isinstance(node.commit, EmptyCommit)
417 for node in (node1, node2)):
417 for node in (node1, node2)):
418 raise HTTPNotFound()
418 raise HTTPNotFound()
419
419
420 c.commit_1 = node1.commit
420 c.commit_1 = node1.commit
421 c.commit_2 = node2.commit
421 c.commit_2 = node2.commit
422
422
423 if c.action == 'download':
423 if c.action == 'download':
424 _diff = diffs.get_gitdiff(node1, node2,
424 _diff = diffs.get_gitdiff(node1, node2,
425 ignore_whitespace=ignore_whitespace,
425 ignore_whitespace=ignore_whitespace,
426 context=line_context)
426 context=line_context)
427 diff = diffs.DiffProcessor(_diff, format='gitdiff')
427 diff = diffs.DiffProcessor(_diff, format='gitdiff')
428
428
429 response = Response(self.path_filter.get_raw_patch(diff))
429 response = Response(self.path_filter.get_raw_patch(diff))
430 response.content_type = 'text/plain'
430 response.content_type = 'text/plain'
431 response.content_disposition = (
431 response.content_disposition = (
432 'attachment; filename=%s_%s_vs_%s.diff' % (f_path, diff1, diff2)
432 'attachment; filename=%s_%s_vs_%s.diff' % (f_path, diff1, diff2)
433 )
433 )
434 charset = self._get_default_encoding(c)
434 charset = self._get_default_encoding(c)
435 if charset:
435 if charset:
436 response.charset = charset
436 response.charset = charset
437 return response
437 return response
438
438
439 elif c.action == 'raw':
439 elif c.action == 'raw':
440 _diff = diffs.get_gitdiff(node1, node2,
440 _diff = diffs.get_gitdiff(node1, node2,
441 ignore_whitespace=ignore_whitespace,
441 ignore_whitespace=ignore_whitespace,
442 context=line_context)
442 context=line_context)
443 diff = diffs.DiffProcessor(_diff, format='gitdiff')
443 diff = diffs.DiffProcessor(_diff, format='gitdiff')
444
444
445 response = Response(self.path_filter.get_raw_patch(diff))
445 response = Response(self.path_filter.get_raw_patch(diff))
446 response.content_type = 'text/plain'
446 response.content_type = 'text/plain'
447 charset = self._get_default_encoding(c)
447 charset = self._get_default_encoding(c)
448 if charset:
448 if charset:
449 response.charset = charset
449 response.charset = charset
450 return response
450 return response
451
451
452 # in case we ever end up here
452 # in case we ever end up here
453 raise HTTPNotFound()
453 raise HTTPNotFound()
454
454
455 @LoginRequired()
455 @LoginRequired()
456 @HasRepoPermissionAnyDecorator(
456 @HasRepoPermissionAnyDecorator(
457 'repository.read', 'repository.write', 'repository.admin')
457 'repository.read', 'repository.write', 'repository.admin')
458 @view_config(
458 @view_config(
459 route_name='repo_files_diff_2way_redirect', request_method='GET',
459 route_name='repo_files_diff_2way_redirect', request_method='GET',
460 renderer=None)
460 renderer=None)
461 def repo_files_diff_2way_redirect(self):
461 def repo_files_diff_2way_redirect(self):
462 """
462 """
463 Kept only to make OLD links work
463 Kept only to make OLD links work
464 """
464 """
465 f_path = self._get_f_path(self.request.matchdict)
465 f_path = self._get_f_path_unchecked(self.request.matchdict)
466 diff1 = self.request.GET.get('diff1', '')
466 diff1 = self.request.GET.get('diff1', '')
467 diff2 = self.request.GET.get('diff2', '')
467 diff2 = self.request.GET.get('diff2', '')
468
468
469 if not any((diff1, diff2)):
469 if not any((diff1, diff2)):
470 h.flash(
470 h.flash(
471 'Need query parameter "diff1" or "diff2" to generate a diff.',
471 'Need query parameter "diff1" or "diff2" to generate a diff.',
472 category='error')
472 category='error')
473 raise HTTPBadRequest()
473 raise HTTPBadRequest()
474
474
475 compare_url = h.route_path(
475 compare_url = h.route_path(
476 'repo_compare',
476 'repo_compare',
477 repo_name=self.db_repo_name,
477 repo_name=self.db_repo_name,
478 source_ref_type='rev',
478 source_ref_type='rev',
479 source_ref=diff1,
479 source_ref=diff1,
480 target_ref_type='rev',
480 target_ref_type='rev',
481 target_ref=diff2,
481 target_ref=diff2,
482 _query=dict(f_path=f_path, diffmode='sideside',
482 _query=dict(f_path=f_path, diffmode='sideside',
483 target_repo=self.db_repo_name,))
483 target_repo=self.db_repo_name,))
484 raise HTTPFound(compare_url)
484 raise HTTPFound(compare_url)
485
485
486 @LoginRequired()
486 @LoginRequired()
487 @HasRepoPermissionAnyDecorator(
487 @HasRepoPermissionAnyDecorator(
488 'repository.read', 'repository.write', 'repository.admin')
488 'repository.read', 'repository.write', 'repository.admin')
489 @view_config(
489 @view_config(
490 route_name='repo_files', request_method='GET',
490 route_name='repo_files', request_method='GET',
491 renderer=None)
491 renderer=None)
492 @view_config(
492 @view_config(
493 route_name='repo_files:default_path', request_method='GET',
493 route_name='repo_files:default_path', request_method='GET',
494 renderer=None)
494 renderer=None)
495 @view_config(
495 @view_config(
496 route_name='repo_files:default_commit', request_method='GET',
496 route_name='repo_files:default_commit', request_method='GET',
497 renderer=None)
497 renderer=None)
498 @view_config(
498 @view_config(
499 route_name='repo_files:rendered', request_method='GET',
499 route_name='repo_files:rendered', request_method='GET',
500 renderer=None)
500 renderer=None)
501 @view_config(
501 @view_config(
502 route_name='repo_files:annotated', request_method='GET',
502 route_name='repo_files:annotated', request_method='GET',
503 renderer=None)
503 renderer=None)
504 def repo_files(self):
504 def repo_files(self):
505 c = self.load_default_context()
505 c = self.load_default_context()
506
506
507 view_name = getattr(self.request.matched_route, 'name', None)
507 view_name = getattr(self.request.matched_route, 'name', None)
508
508
509 c.annotate = view_name == 'repo_files:annotated'
509 c.annotate = view_name == 'repo_files:annotated'
510 # default is false, but .rst/.md files later are auto rendered, we can
510 # default is false, but .rst/.md files later are auto rendered, we can
511 # overwrite auto rendering by setting this GET flag
511 # overwrite auto rendering by setting this GET flag
512 c.renderer = view_name == 'repo_files:rendered' or \
512 c.renderer = view_name == 'repo_files:rendered' or \
513 not self.request.GET.get('no-render', False)
513 not self.request.GET.get('no-render', False)
514
514
515 # redirect to given commit_id from form if given
515 # redirect to given commit_id from form if given
516 get_commit_id = self.request.GET.get('at_rev', None)
516 get_commit_id = self.request.GET.get('at_rev', None)
517 if get_commit_id:
517 if get_commit_id:
518 self._get_commit_or_redirect(get_commit_id)
518 self._get_commit_or_redirect(get_commit_id)
519
519
520 commit_id, f_path = self._get_commit_and_path()
520 commit_id, f_path = self._get_commit_and_path()
521 c.commit = self._get_commit_or_redirect(commit_id)
521 c.commit = self._get_commit_or_redirect(commit_id)
522 c.branch = self.request.GET.get('branch', None)
522 c.branch = self.request.GET.get('branch', None)
523 c.f_path = f_path
523 c.f_path = f_path
524
524
525 # prev link
525 # prev link
526 try:
526 try:
527 prev_commit = c.commit.prev(c.branch)
527 prev_commit = c.commit.prev(c.branch)
528 c.prev_commit = prev_commit
528 c.prev_commit = prev_commit
529 c.url_prev = h.route_path(
529 c.url_prev = h.route_path(
530 'repo_files', repo_name=self.db_repo_name,
530 'repo_files', repo_name=self.db_repo_name,
531 commit_id=prev_commit.raw_id, f_path=f_path)
531 commit_id=prev_commit.raw_id, f_path=f_path)
532 if c.branch:
532 if c.branch:
533 c.url_prev += '?branch=%s' % c.branch
533 c.url_prev += '?branch=%s' % c.branch
534 except (CommitDoesNotExistError, VCSError):
534 except (CommitDoesNotExistError, VCSError):
535 c.url_prev = '#'
535 c.url_prev = '#'
536 c.prev_commit = EmptyCommit()
536 c.prev_commit = EmptyCommit()
537
537
538 # next link
538 # next link
539 try:
539 try:
540 next_commit = c.commit.next(c.branch)
540 next_commit = c.commit.next(c.branch)
541 c.next_commit = next_commit
541 c.next_commit = next_commit
542 c.url_next = h.route_path(
542 c.url_next = h.route_path(
543 'repo_files', repo_name=self.db_repo_name,
543 'repo_files', repo_name=self.db_repo_name,
544 commit_id=next_commit.raw_id, f_path=f_path)
544 commit_id=next_commit.raw_id, f_path=f_path)
545 if c.branch:
545 if c.branch:
546 c.url_next += '?branch=%s' % c.branch
546 c.url_next += '?branch=%s' % c.branch
547 except (CommitDoesNotExistError, VCSError):
547 except (CommitDoesNotExistError, VCSError):
548 c.url_next = '#'
548 c.url_next = '#'
549 c.next_commit = EmptyCommit()
549 c.next_commit = EmptyCommit()
550
550
551 # files or dirs
551 # files or dirs
552 try:
552 try:
553 c.file = c.commit.get_node(f_path)
553 c.file = c.commit.get_node(f_path)
554 c.file_author = True
554 c.file_author = True
555 c.file_tree = ''
555 c.file_tree = ''
556
556
557 # load file content
557 # load file content
558 if c.file.is_file():
558 if c.file.is_file():
559 c.lf_node = c.file.get_largefile_node()
559 c.lf_node = c.file.get_largefile_node()
560
560
561 c.file_source_page = 'true'
561 c.file_source_page = 'true'
562 c.file_last_commit = c.file.last_commit
562 c.file_last_commit = c.file.last_commit
563 if c.file.size < c.visual.cut_off_limit_diff:
563 if c.file.size < c.visual.cut_off_limit_diff:
564 if c.annotate: # annotation has precedence over renderer
564 if c.annotate: # annotation has precedence over renderer
565 c.annotated_lines = filenode_as_annotated_lines_tokens(
565 c.annotated_lines = filenode_as_annotated_lines_tokens(
566 c.file
566 c.file
567 )
567 )
568 else:
568 else:
569 c.renderer = (
569 c.renderer = (
570 c.renderer and h.renderer_from_filename(c.file.path)
570 c.renderer and h.renderer_from_filename(c.file.path)
571 )
571 )
572 if not c.renderer:
572 if not c.renderer:
573 c.lines = filenode_as_lines_tokens(c.file)
573 c.lines = filenode_as_lines_tokens(c.file)
574
574
575 c.on_branch_head = self._is_valid_head(
575 c.on_branch_head = self._is_valid_head(
576 commit_id, self.rhodecode_vcs_repo)
576 commit_id, self.rhodecode_vcs_repo)
577
577
578 branch = c.commit.branch if (
578 branch = c.commit.branch if (
579 c.commit.branch and '/' not in c.commit.branch) else None
579 c.commit.branch and '/' not in c.commit.branch) else None
580 c.branch_or_raw_id = branch or c.commit.raw_id
580 c.branch_or_raw_id = branch or c.commit.raw_id
581 c.branch_name = c.commit.branch or h.short_id(c.commit.raw_id)
581 c.branch_name = c.commit.branch or h.short_id(c.commit.raw_id)
582
582
583 author = c.file_last_commit.author
583 author = c.file_last_commit.author
584 c.authors = [[
584 c.authors = [[
585 h.email(author),
585 h.email(author),
586 h.person(author, 'username_or_name_or_email'),
586 h.person(author, 'username_or_name_or_email'),
587 1
587 1
588 ]]
588 ]]
589
589
590 else: # load tree content at path
590 else: # load tree content at path
591 c.file_source_page = 'false'
591 c.file_source_page = 'false'
592 c.authors = []
592 c.authors = []
593 # this loads a simple tree without metadata to speed things up
593 # this loads a simple tree without metadata to speed things up
594 # later via ajax we call repo_nodetree_full and fetch whole
594 # later via ajax we call repo_nodetree_full and fetch whole
595 c.file_tree = self._get_tree_at_commit(
595 c.file_tree = self._get_tree_at_commit(
596 c, c.commit.raw_id, f_path)
596 c, c.commit.raw_id, f_path)
597
597
598 except RepositoryError as e:
598 except RepositoryError as e:
599 h.flash(safe_str(h.escape(e)), category='error')
599 h.flash(safe_str(h.escape(e)), category='error')
600 raise HTTPNotFound()
600 raise HTTPNotFound()
601
601
602 if self.request.environ.get('HTTP_X_PJAX'):
602 if self.request.environ.get('HTTP_X_PJAX'):
603 html = render('rhodecode:templates/files/files_pjax.mako',
603 html = render('rhodecode:templates/files/files_pjax.mako',
604 self._get_template_context(c), self.request)
604 self._get_template_context(c), self.request)
605 else:
605 else:
606 html = render('rhodecode:templates/files/files.mako',
606 html = render('rhodecode:templates/files/files.mako',
607 self._get_template_context(c), self.request)
607 self._get_template_context(c), self.request)
608 return Response(html)
608 return Response(html)
609
609
610 @HasRepoPermissionAnyDecorator(
610 @HasRepoPermissionAnyDecorator(
611 'repository.read', 'repository.write', 'repository.admin')
611 'repository.read', 'repository.write', 'repository.admin')
612 @view_config(
612 @view_config(
613 route_name='repo_files:annotated_previous', request_method='GET',
613 route_name='repo_files:annotated_previous', request_method='GET',
614 renderer=None)
614 renderer=None)
615 def repo_files_annotated_previous(self):
615 def repo_files_annotated_previous(self):
616 self.load_default_context()
616 self.load_default_context()
617
617
618 commit_id, f_path = self._get_commit_and_path()
618 commit_id, f_path = self._get_commit_and_path()
619 commit = self._get_commit_or_redirect(commit_id)
619 commit = self._get_commit_or_redirect(commit_id)
620 prev_commit_id = commit.raw_id
620 prev_commit_id = commit.raw_id
621 line_anchor = self.request.GET.get('line_anchor')
621 line_anchor = self.request.GET.get('line_anchor')
622 is_file = False
622 is_file = False
623 try:
623 try:
624 _file = commit.get_node(f_path)
624 _file = commit.get_node(f_path)
625 is_file = _file.is_file()
625 is_file = _file.is_file()
626 except (NodeDoesNotExistError, CommitDoesNotExistError, VCSError):
626 except (NodeDoesNotExistError, CommitDoesNotExistError, VCSError):
627 pass
627 pass
628
628
629 if is_file:
629 if is_file:
630 history = commit.get_file_history(f_path)
630 history = commit.get_file_history(f_path)
631 prev_commit_id = history[1].raw_id \
631 prev_commit_id = history[1].raw_id \
632 if len(history) > 1 else prev_commit_id
632 if len(history) > 1 else prev_commit_id
633 prev_url = h.route_path(
633 prev_url = h.route_path(
634 'repo_files:annotated', repo_name=self.db_repo_name,
634 'repo_files:annotated', repo_name=self.db_repo_name,
635 commit_id=prev_commit_id, f_path=f_path,
635 commit_id=prev_commit_id, f_path=f_path,
636 _anchor='L{}'.format(line_anchor))
636 _anchor='L{}'.format(line_anchor))
637
637
638 raise HTTPFound(prev_url)
638 raise HTTPFound(prev_url)
639
639
640 @LoginRequired()
640 @LoginRequired()
641 @HasRepoPermissionAnyDecorator(
641 @HasRepoPermissionAnyDecorator(
642 'repository.read', 'repository.write', 'repository.admin')
642 'repository.read', 'repository.write', 'repository.admin')
643 @view_config(
643 @view_config(
644 route_name='repo_nodetree_full', request_method='GET',
644 route_name='repo_nodetree_full', request_method='GET',
645 renderer=None, xhr=True)
645 renderer=None, xhr=True)
646 @view_config(
646 @view_config(
647 route_name='repo_nodetree_full:default_path', request_method='GET',
647 route_name='repo_nodetree_full:default_path', request_method='GET',
648 renderer=None, xhr=True)
648 renderer=None, xhr=True)
649 def repo_nodetree_full(self):
649 def repo_nodetree_full(self):
650 """
650 """
651 Returns rendered html of file tree that contains commit date,
651 Returns rendered html of file tree that contains commit date,
652 author, commit_id for the specified combination of
652 author, commit_id for the specified combination of
653 repo, commit_id and file path
653 repo, commit_id and file path
654 """
654 """
655 c = self.load_default_context()
655 c = self.load_default_context()
656
656
657 commit_id, f_path = self._get_commit_and_path()
657 commit_id, f_path = self._get_commit_and_path()
658 commit = self._get_commit_or_redirect(commit_id)
658 commit = self._get_commit_or_redirect(commit_id)
659 try:
659 try:
660 dir_node = commit.get_node(f_path)
660 dir_node = commit.get_node(f_path)
661 except RepositoryError as e:
661 except RepositoryError as e:
662 return Response('error: {}'.format(h.escape(safe_str(e))))
662 return Response('error: {}'.format(h.escape(safe_str(e))))
663
663
664 if dir_node.is_file():
664 if dir_node.is_file():
665 return Response('')
665 return Response('')
666
666
667 c.file = dir_node
667 c.file = dir_node
668 c.commit = commit
668 c.commit = commit
669
669
670 # using force=True here, make a little trick. We flush the cache and
670 # using force=True here, make a little trick. We flush the cache and
671 # compute it using the same key as without previous full_load, so now
671 # compute it using the same key as without previous full_load, so now
672 # the fully loaded tree is now returned instead of partial,
672 # the fully loaded tree is now returned instead of partial,
673 # and we store this in caches
673 # and we store this in caches
674 html = self._get_tree_at_commit(
674 html = self._get_tree_at_commit(
675 c, commit.raw_id, dir_node.path, full_load=True, force=True)
675 c, commit.raw_id, dir_node.path, full_load=True, force=True)
676
676
677 return Response(html)
677 return Response(html)
678
678
679 def _get_attachement_disposition(self, f_path):
679 def _get_attachement_disposition(self, f_path):
680 return 'attachment; filename=%s' % \
680 return 'attachment; filename=%s' % \
681 safe_str(f_path.split(Repository.NAME_SEP)[-1])
681 safe_str(f_path.split(Repository.NAME_SEP)[-1])
682
682
683 @LoginRequired()
683 @LoginRequired()
684 @HasRepoPermissionAnyDecorator(
684 @HasRepoPermissionAnyDecorator(
685 'repository.read', 'repository.write', 'repository.admin')
685 'repository.read', 'repository.write', 'repository.admin')
686 @view_config(
686 @view_config(
687 route_name='repo_file_raw', request_method='GET',
687 route_name='repo_file_raw', request_method='GET',
688 renderer=None)
688 renderer=None)
689 def repo_file_raw(self):
689 def repo_file_raw(self):
690 """
690 """
691 Action for show as raw, some mimetypes are "rendered",
691 Action for show as raw, some mimetypes are "rendered",
692 those include images, icons.
692 those include images, icons.
693 """
693 """
694 c = self.load_default_context()
694 c = self.load_default_context()
695
695
696 commit_id, f_path = self._get_commit_and_path()
696 commit_id, f_path = self._get_commit_and_path()
697 commit = self._get_commit_or_redirect(commit_id)
697 commit = self._get_commit_or_redirect(commit_id)
698 file_node = self._get_filenode_or_redirect(commit, f_path)
698 file_node = self._get_filenode_or_redirect(commit, f_path)
699
699
700 raw_mimetype_mapping = {
700 raw_mimetype_mapping = {
701 # map original mimetype to a mimetype used for "show as raw"
701 # map original mimetype to a mimetype used for "show as raw"
702 # you can also provide a content-disposition to override the
702 # you can also provide a content-disposition to override the
703 # default "attachment" disposition.
703 # default "attachment" disposition.
704 # orig_type: (new_type, new_dispo)
704 # orig_type: (new_type, new_dispo)
705
705
706 # show images inline:
706 # show images inline:
707 # Do not re-add SVG: it is unsafe and permits XSS attacks. One can
707 # Do not re-add SVG: it is unsafe and permits XSS attacks. One can
708 # for example render an SVG with javascript inside or even render
708 # for example render an SVG with javascript inside or even render
709 # HTML.
709 # HTML.
710 'image/x-icon': ('image/x-icon', 'inline'),
710 'image/x-icon': ('image/x-icon', 'inline'),
711 'image/png': ('image/png', 'inline'),
711 'image/png': ('image/png', 'inline'),
712 'image/gif': ('image/gif', 'inline'),
712 'image/gif': ('image/gif', 'inline'),
713 'image/jpeg': ('image/jpeg', 'inline'),
713 'image/jpeg': ('image/jpeg', 'inline'),
714 'application/pdf': ('application/pdf', 'inline'),
714 'application/pdf': ('application/pdf', 'inline'),
715 }
715 }
716
716
717 mimetype = file_node.mimetype
717 mimetype = file_node.mimetype
718 try:
718 try:
719 mimetype, disposition = raw_mimetype_mapping[mimetype]
719 mimetype, disposition = raw_mimetype_mapping[mimetype]
720 except KeyError:
720 except KeyError:
721 # we don't know anything special about this, handle it safely
721 # we don't know anything special about this, handle it safely
722 if file_node.is_binary:
722 if file_node.is_binary:
723 # do same as download raw for binary files
723 # do same as download raw for binary files
724 mimetype, disposition = 'application/octet-stream', 'attachment'
724 mimetype, disposition = 'application/octet-stream', 'attachment'
725 else:
725 else:
726 # do not just use the original mimetype, but force text/plain,
726 # do not just use the original mimetype, but force text/plain,
727 # otherwise it would serve text/html and that might be unsafe.
727 # otherwise it would serve text/html and that might be unsafe.
728 # Note: underlying vcs library fakes text/plain mimetype if the
728 # Note: underlying vcs library fakes text/plain mimetype if the
729 # mimetype can not be determined and it thinks it is not
729 # mimetype can not be determined and it thinks it is not
730 # binary.This might lead to erroneous text display in some
730 # binary.This might lead to erroneous text display in some
731 # cases, but helps in other cases, like with text files
731 # cases, but helps in other cases, like with text files
732 # without extension.
732 # without extension.
733 mimetype, disposition = 'text/plain', 'inline'
733 mimetype, disposition = 'text/plain', 'inline'
734
734
735 if disposition == 'attachment':
735 if disposition == 'attachment':
736 disposition = self._get_attachement_disposition(f_path)
736 disposition = self._get_attachement_disposition(f_path)
737
737
738 def stream_node():
738 def stream_node():
739 yield file_node.raw_bytes
739 yield file_node.raw_bytes
740
740
741 response = Response(app_iter=stream_node())
741 response = Response(app_iter=stream_node())
742 response.content_disposition = disposition
742 response.content_disposition = disposition
743 response.content_type = mimetype
743 response.content_type = mimetype
744
744
745 charset = self._get_default_encoding(c)
745 charset = self._get_default_encoding(c)
746 if charset:
746 if charset:
747 response.charset = charset
747 response.charset = charset
748
748
749 return response
749 return response
750
750
751 @LoginRequired()
751 @LoginRequired()
752 @HasRepoPermissionAnyDecorator(
752 @HasRepoPermissionAnyDecorator(
753 'repository.read', 'repository.write', 'repository.admin')
753 'repository.read', 'repository.write', 'repository.admin')
754 @view_config(
754 @view_config(
755 route_name='repo_file_download', request_method='GET',
755 route_name='repo_file_download', request_method='GET',
756 renderer=None)
756 renderer=None)
757 @view_config(
757 @view_config(
758 route_name='repo_file_download:legacy', request_method='GET',
758 route_name='repo_file_download:legacy', request_method='GET',
759 renderer=None)
759 renderer=None)
760 def repo_file_download(self):
760 def repo_file_download(self):
761 c = self.load_default_context()
761 c = self.load_default_context()
762
762
763 commit_id, f_path = self._get_commit_and_path()
763 commit_id, f_path = self._get_commit_and_path()
764 commit = self._get_commit_or_redirect(commit_id)
764 commit = self._get_commit_or_redirect(commit_id)
765 file_node = self._get_filenode_or_redirect(commit, f_path)
765 file_node = self._get_filenode_or_redirect(commit, f_path)
766
766
767 if self.request.GET.get('lf'):
767 if self.request.GET.get('lf'):
768 # only if lf get flag is passed, we download this file
768 # only if lf get flag is passed, we download this file
769 # as LFS/Largefile
769 # as LFS/Largefile
770 lf_node = file_node.get_largefile_node()
770 lf_node = file_node.get_largefile_node()
771 if lf_node:
771 if lf_node:
772 # overwrite our pointer with the REAL large-file
772 # overwrite our pointer with the REAL large-file
773 file_node = lf_node
773 file_node = lf_node
774
774
775 disposition = self._get_attachement_disposition(f_path)
775 disposition = self._get_attachement_disposition(f_path)
776
776
777 def stream_node():
777 def stream_node():
778 yield file_node.raw_bytes
778 yield file_node.raw_bytes
779
779
780 response = Response(app_iter=stream_node())
780 response = Response(app_iter=stream_node())
781 response.content_disposition = disposition
781 response.content_disposition = disposition
782 response.content_type = file_node.mimetype
782 response.content_type = file_node.mimetype
783
783
784 charset = self._get_default_encoding(c)
784 charset = self._get_default_encoding(c)
785 if charset:
785 if charset:
786 response.charset = charset
786 response.charset = charset
787
787
788 return response
788 return response
789
789
790 def _get_nodelist_at_commit(self, repo_name, commit_id, f_path):
790 def _get_nodelist_at_commit(self, repo_name, commit_id, f_path):
791 def _cached_nodes():
791 def _cached_nodes():
792 log.debug('Generating cached nodelist for %s, %s, %s',
792 log.debug('Generating cached nodelist for %s, %s, %s',
793 repo_name, commit_id, f_path)
793 repo_name, commit_id, f_path)
794 try:
794 try:
795 _d, _f = ScmModel().get_nodes(
795 _d, _f = ScmModel().get_nodes(
796 repo_name, commit_id, f_path, flat=False)
796 repo_name, commit_id, f_path, flat=False)
797 except (RepositoryError, CommitDoesNotExistError, Exception) as e:
797 except (RepositoryError, CommitDoesNotExistError, Exception) as e:
798 log.exception(safe_str(e))
798 log.exception(safe_str(e))
799 h.flash(safe_str(h.escape(e)), category='error')
799 h.flash(safe_str(h.escape(e)), category='error')
800 raise HTTPFound(h.route_path(
800 raise HTTPFound(h.route_path(
801 'repo_files', repo_name=self.db_repo_name,
801 'repo_files', repo_name=self.db_repo_name,
802 commit_id='tip', f_path='/'))
802 commit_id='tip', f_path='/'))
803 return _d + _f
803 return _d + _f
804
804
805 cache_manager = self._get_tree_cache_manager(
805 cache_manager = self._get_tree_cache_manager(
806 caches.FILE_SEARCH_TREE_META)
806 caches.FILE_SEARCH_TREE_META)
807
807
808 cache_key = caches.compute_key_from_params(
808 cache_key = caches.compute_key_from_params(
809 repo_name, commit_id, f_path)
809 repo_name, commit_id, f_path)
810 return cache_manager.get(cache_key, createfunc=_cached_nodes)
810 return cache_manager.get(cache_key, createfunc=_cached_nodes)
811
811
812 @LoginRequired()
812 @LoginRequired()
813 @HasRepoPermissionAnyDecorator(
813 @HasRepoPermissionAnyDecorator(
814 'repository.read', 'repository.write', 'repository.admin')
814 'repository.read', 'repository.write', 'repository.admin')
815 @view_config(
815 @view_config(
816 route_name='repo_files_nodelist', request_method='GET',
816 route_name='repo_files_nodelist', request_method='GET',
817 renderer='json_ext', xhr=True)
817 renderer='json_ext', xhr=True)
818 def repo_nodelist(self):
818 def repo_nodelist(self):
819 self.load_default_context()
819 self.load_default_context()
820
820
821 commit_id, f_path = self._get_commit_and_path()
821 commit_id, f_path = self._get_commit_and_path()
822 commit = self._get_commit_or_redirect(commit_id)
822 commit = self._get_commit_or_redirect(commit_id)
823
823
824 metadata = self._get_nodelist_at_commit(
824 metadata = self._get_nodelist_at_commit(
825 self.db_repo_name, commit.raw_id, f_path)
825 self.db_repo_name, commit.raw_id, f_path)
826 return {'nodes': metadata}
826 return {'nodes': metadata}
827
827
828 def _create_references(
828 def _create_references(
829 self, branches_or_tags, symbolic_reference, f_path):
829 self, branches_or_tags, symbolic_reference, f_path):
830 items = []
830 items = []
831 for name, commit_id in branches_or_tags.items():
831 for name, commit_id in branches_or_tags.items():
832 sym_ref = symbolic_reference(commit_id, name, f_path)
832 sym_ref = symbolic_reference(commit_id, name, f_path)
833 items.append((sym_ref, name))
833 items.append((sym_ref, name))
834 return items
834 return items
835
835
836 def _symbolic_reference(self, commit_id, name, f_path):
836 def _symbolic_reference(self, commit_id, name, f_path):
837 return commit_id
837 return commit_id
838
838
839 def _symbolic_reference_svn(self, commit_id, name, f_path):
839 def _symbolic_reference_svn(self, commit_id, name, f_path):
840 new_f_path = vcspath.join(name, f_path)
840 new_f_path = vcspath.join(name, f_path)
841 return u'%s@%s' % (new_f_path, commit_id)
841 return u'%s@%s' % (new_f_path, commit_id)
842
842
843 def _get_node_history(self, commit_obj, f_path, commits=None):
843 def _get_node_history(self, commit_obj, f_path, commits=None):
844 """
844 """
845 get commit history for given node
845 get commit history for given node
846
846
847 :param commit_obj: commit to calculate history
847 :param commit_obj: commit to calculate history
848 :param f_path: path for node to calculate history for
848 :param f_path: path for node to calculate history for
849 :param commits: if passed don't calculate history and take
849 :param commits: if passed don't calculate history and take
850 commits defined in this list
850 commits defined in this list
851 """
851 """
852 _ = self.request.translate
852 _ = self.request.translate
853
853
854 # calculate history based on tip
854 # calculate history based on tip
855 tip = self.rhodecode_vcs_repo.get_commit()
855 tip = self.rhodecode_vcs_repo.get_commit()
856 if commits is None:
856 if commits is None:
857 pre_load = ["author", "branch"]
857 pre_load = ["author", "branch"]
858 try:
858 try:
859 commits = tip.get_file_history(f_path, pre_load=pre_load)
859 commits = tip.get_file_history(f_path, pre_load=pre_load)
860 except (NodeDoesNotExistError, CommitError):
860 except (NodeDoesNotExistError, CommitError):
861 # this node is not present at tip!
861 # this node is not present at tip!
862 commits = commit_obj.get_file_history(f_path, pre_load=pre_load)
862 commits = commit_obj.get_file_history(f_path, pre_load=pre_load)
863
863
864 history = []
864 history = []
865 commits_group = ([], _("Changesets"))
865 commits_group = ([], _("Changesets"))
866 for commit in commits:
866 for commit in commits:
867 branch = ' (%s)' % commit.branch if commit.branch else ''
867 branch = ' (%s)' % commit.branch if commit.branch else ''
868 n_desc = 'r%s:%s%s' % (commit.idx, commit.short_id, branch)
868 n_desc = 'r%s:%s%s' % (commit.idx, commit.short_id, branch)
869 commits_group[0].append((commit.raw_id, n_desc,))
869 commits_group[0].append((commit.raw_id, n_desc,))
870 history.append(commits_group)
870 history.append(commits_group)
871
871
872 symbolic_reference = self._symbolic_reference
872 symbolic_reference = self._symbolic_reference
873
873
874 if self.rhodecode_vcs_repo.alias == 'svn':
874 if self.rhodecode_vcs_repo.alias == 'svn':
875 adjusted_f_path = RepoFilesView.adjust_file_path_for_svn(
875 adjusted_f_path = RepoFilesView.adjust_file_path_for_svn(
876 f_path, self.rhodecode_vcs_repo)
876 f_path, self.rhodecode_vcs_repo)
877 if adjusted_f_path != f_path:
877 if adjusted_f_path != f_path:
878 log.debug(
878 log.debug(
879 'Recognized svn tag or branch in file "%s", using svn '
879 'Recognized svn tag or branch in file "%s", using svn '
880 'specific symbolic references', f_path)
880 'specific symbolic references', f_path)
881 f_path = adjusted_f_path
881 f_path = adjusted_f_path
882 symbolic_reference = self._symbolic_reference_svn
882 symbolic_reference = self._symbolic_reference_svn
883
883
884 branches = self._create_references(
884 branches = self._create_references(
885 self.rhodecode_vcs_repo.branches, symbolic_reference, f_path)
885 self.rhodecode_vcs_repo.branches, symbolic_reference, f_path)
886 branches_group = (branches, _("Branches"))
886 branches_group = (branches, _("Branches"))
887
887
888 tags = self._create_references(
888 tags = self._create_references(
889 self.rhodecode_vcs_repo.tags, symbolic_reference, f_path)
889 self.rhodecode_vcs_repo.tags, symbolic_reference, f_path)
890 tags_group = (tags, _("Tags"))
890 tags_group = (tags, _("Tags"))
891
891
892 history.append(branches_group)
892 history.append(branches_group)
893 history.append(tags_group)
893 history.append(tags_group)
894
894
895 return history, commits
895 return history, commits
896
896
897 @LoginRequired()
897 @LoginRequired()
898 @HasRepoPermissionAnyDecorator(
898 @HasRepoPermissionAnyDecorator(
899 'repository.read', 'repository.write', 'repository.admin')
899 'repository.read', 'repository.write', 'repository.admin')
900 @view_config(
900 @view_config(
901 route_name='repo_file_history', request_method='GET',
901 route_name='repo_file_history', request_method='GET',
902 renderer='json_ext')
902 renderer='json_ext')
903 def repo_file_history(self):
903 def repo_file_history(self):
904 self.load_default_context()
904 self.load_default_context()
905
905
906 commit_id, f_path = self._get_commit_and_path()
906 commit_id, f_path = self._get_commit_and_path()
907 commit = self._get_commit_or_redirect(commit_id)
907 commit = self._get_commit_or_redirect(commit_id)
908 file_node = self._get_filenode_or_redirect(commit, f_path)
908 file_node = self._get_filenode_or_redirect(commit, f_path)
909
909
910 if file_node.is_file():
910 if file_node.is_file():
911 file_history, _hist = self._get_node_history(commit, f_path)
911 file_history, _hist = self._get_node_history(commit, f_path)
912
912
913 res = []
913 res = []
914 for obj in file_history:
914 for obj in file_history:
915 res.append({
915 res.append({
916 'text': obj[1],
916 'text': obj[1],
917 'children': [{'id': o[0], 'text': o[1]} for o in obj[0]]
917 'children': [{'id': o[0], 'text': o[1]} for o in obj[0]]
918 })
918 })
919
919
920 data = {
920 data = {
921 'more': False,
921 'more': False,
922 'results': res
922 'results': res
923 }
923 }
924 return data
924 return data
925
925
926 log.warning('Cannot fetch history for directory')
926 log.warning('Cannot fetch history for directory')
927 raise HTTPBadRequest()
927 raise HTTPBadRequest()
928
928
929 @LoginRequired()
929 @LoginRequired()
930 @HasRepoPermissionAnyDecorator(
930 @HasRepoPermissionAnyDecorator(
931 'repository.read', 'repository.write', 'repository.admin')
931 'repository.read', 'repository.write', 'repository.admin')
932 @view_config(
932 @view_config(
933 route_name='repo_file_authors', request_method='GET',
933 route_name='repo_file_authors', request_method='GET',
934 renderer='rhodecode:templates/files/file_authors_box.mako')
934 renderer='rhodecode:templates/files/file_authors_box.mako')
935 def repo_file_authors(self):
935 def repo_file_authors(self):
936 c = self.load_default_context()
936 c = self.load_default_context()
937
937
938 commit_id, f_path = self._get_commit_and_path()
938 commit_id, f_path = self._get_commit_and_path()
939 commit = self._get_commit_or_redirect(commit_id)
939 commit = self._get_commit_or_redirect(commit_id)
940 file_node = self._get_filenode_or_redirect(commit, f_path)
940 file_node = self._get_filenode_or_redirect(commit, f_path)
941
941
942 if not file_node.is_file():
942 if not file_node.is_file():
943 raise HTTPBadRequest()
943 raise HTTPBadRequest()
944
944
945 c.file_last_commit = file_node.last_commit
945 c.file_last_commit = file_node.last_commit
946 if self.request.GET.get('annotate') == '1':
946 if self.request.GET.get('annotate') == '1':
947 # use _hist from annotation if annotation mode is on
947 # use _hist from annotation if annotation mode is on
948 commit_ids = set(x[1] for x in file_node.annotate)
948 commit_ids = set(x[1] for x in file_node.annotate)
949 _hist = (
949 _hist = (
950 self.rhodecode_vcs_repo.get_commit(commit_id)
950 self.rhodecode_vcs_repo.get_commit(commit_id)
951 for commit_id in commit_ids)
951 for commit_id in commit_ids)
952 else:
952 else:
953 _f_history, _hist = self._get_node_history(commit, f_path)
953 _f_history, _hist = self._get_node_history(commit, f_path)
954 c.file_author = False
954 c.file_author = False
955
955
956 unique = collections.OrderedDict()
956 unique = collections.OrderedDict()
957 for commit in _hist:
957 for commit in _hist:
958 author = commit.author
958 author = commit.author
959 if author not in unique:
959 if author not in unique:
960 unique[commit.author] = [
960 unique[commit.author] = [
961 h.email(author),
961 h.email(author),
962 h.person(author, 'username_or_name_or_email'),
962 h.person(author, 'username_or_name_or_email'),
963 1 # counter
963 1 # counter
964 ]
964 ]
965
965
966 else:
966 else:
967 # increase counter
967 # increase counter
968 unique[commit.author][2] += 1
968 unique[commit.author][2] += 1
969
969
970 c.authors = [val for val in unique.values()]
970 c.authors = [val for val in unique.values()]
971
971
972 return self._get_template_context(c)
972 return self._get_template_context(c)
973
973
974 @LoginRequired()
974 @LoginRequired()
975 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
975 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
976 @view_config(
976 @view_config(
977 route_name='repo_files_remove_file', request_method='GET',
977 route_name='repo_files_remove_file', request_method='GET',
978 renderer='rhodecode:templates/files/files_delete.mako')
978 renderer='rhodecode:templates/files/files_delete.mako')
979 def repo_files_remove_file(self):
979 def repo_files_remove_file(self):
980 _ = self.request.translate
980 _ = self.request.translate
981 c = self.load_default_context()
981 c = self.load_default_context()
982 commit_id, f_path = self._get_commit_and_path()
982 commit_id, f_path = self._get_commit_and_path()
983
983
984 self._ensure_not_locked()
984 self._ensure_not_locked()
985
985
986 if not self._is_valid_head(commit_id, self.rhodecode_vcs_repo):
986 if not self._is_valid_head(commit_id, self.rhodecode_vcs_repo):
987 h.flash(_('You can only delete files with commit '
987 h.flash(_('You can only delete files with commit '
988 'being a valid branch '), category='warning')
988 'being a valid branch '), category='warning')
989 raise HTTPFound(
989 raise HTTPFound(
990 h.route_path('repo_files',
990 h.route_path('repo_files',
991 repo_name=self.db_repo_name, commit_id='tip',
991 repo_name=self.db_repo_name, commit_id='tip',
992 f_path=f_path))
992 f_path=f_path))
993
993
994 c.commit = self._get_commit_or_redirect(commit_id)
994 c.commit = self._get_commit_or_redirect(commit_id)
995 c.file = self._get_filenode_or_redirect(c.commit, f_path)
995 c.file = self._get_filenode_or_redirect(c.commit, f_path)
996
996
997 c.default_message = _(
997 c.default_message = _(
998 'Deleted file {} via RhodeCode Enterprise').format(f_path)
998 'Deleted file {} via RhodeCode Enterprise').format(f_path)
999 c.f_path = f_path
999 c.f_path = f_path
1000
1000
1001 return self._get_template_context(c)
1001 return self._get_template_context(c)
1002
1002
1003 @LoginRequired()
1003 @LoginRequired()
1004 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1004 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1005 @CSRFRequired()
1005 @CSRFRequired()
1006 @view_config(
1006 @view_config(
1007 route_name='repo_files_delete_file', request_method='POST',
1007 route_name='repo_files_delete_file', request_method='POST',
1008 renderer=None)
1008 renderer=None)
1009 def repo_files_delete_file(self):
1009 def repo_files_delete_file(self):
1010 _ = self.request.translate
1010 _ = self.request.translate
1011
1011
1012 c = self.load_default_context()
1012 c = self.load_default_context()
1013 commit_id, f_path = self._get_commit_and_path()
1013 commit_id, f_path = self._get_commit_and_path()
1014
1014
1015 self._ensure_not_locked()
1015 self._ensure_not_locked()
1016
1016
1017 if not self._is_valid_head(commit_id, self.rhodecode_vcs_repo):
1017 if not self._is_valid_head(commit_id, self.rhodecode_vcs_repo):
1018 h.flash(_('You can only delete files with commit '
1018 h.flash(_('You can only delete files with commit '
1019 'being a valid branch '), category='warning')
1019 'being a valid branch '), category='warning')
1020 raise HTTPFound(
1020 raise HTTPFound(
1021 h.route_path('repo_files',
1021 h.route_path('repo_files',
1022 repo_name=self.db_repo_name, commit_id='tip',
1022 repo_name=self.db_repo_name, commit_id='tip',
1023 f_path=f_path))
1023 f_path=f_path))
1024
1024
1025 c.commit = self._get_commit_or_redirect(commit_id)
1025 c.commit = self._get_commit_or_redirect(commit_id)
1026 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1026 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1027
1027
1028 c.default_message = _(
1028 c.default_message = _(
1029 'Deleted file {} via RhodeCode Enterprise').format(f_path)
1029 'Deleted file {} via RhodeCode Enterprise').format(f_path)
1030 c.f_path = f_path
1030 c.f_path = f_path
1031 node_path = f_path
1031 node_path = f_path
1032 author = self._rhodecode_db_user.full_contact
1032 author = self._rhodecode_db_user.full_contact
1033 message = self.request.POST.get('message') or c.default_message
1033 message = self.request.POST.get('message') or c.default_message
1034 try:
1034 try:
1035 nodes = {
1035 nodes = {
1036 node_path: {
1036 node_path: {
1037 'content': ''
1037 'content': ''
1038 }
1038 }
1039 }
1039 }
1040 ScmModel().delete_nodes(
1040 ScmModel().delete_nodes(
1041 user=self._rhodecode_db_user.user_id, repo=self.db_repo,
1041 user=self._rhodecode_db_user.user_id, repo=self.db_repo,
1042 message=message,
1042 message=message,
1043 nodes=nodes,
1043 nodes=nodes,
1044 parent_commit=c.commit,
1044 parent_commit=c.commit,
1045 author=author,
1045 author=author,
1046 )
1046 )
1047
1047
1048 h.flash(
1048 h.flash(
1049 _('Successfully deleted file `{}`').format(
1049 _('Successfully deleted file `{}`').format(
1050 h.escape(f_path)), category='success')
1050 h.escape(f_path)), category='success')
1051 except Exception:
1051 except Exception:
1052 log.exception('Error during commit operation')
1052 log.exception('Error during commit operation')
1053 h.flash(_('Error occurred during commit'), category='error')
1053 h.flash(_('Error occurred during commit'), category='error')
1054 raise HTTPFound(
1054 raise HTTPFound(
1055 h.route_path('repo_commit', repo_name=self.db_repo_name,
1055 h.route_path('repo_commit', repo_name=self.db_repo_name,
1056 commit_id='tip'))
1056 commit_id='tip'))
1057
1057
1058 @LoginRequired()
1058 @LoginRequired()
1059 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1059 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1060 @view_config(
1060 @view_config(
1061 route_name='repo_files_edit_file', request_method='GET',
1061 route_name='repo_files_edit_file', request_method='GET',
1062 renderer='rhodecode:templates/files/files_edit.mako')
1062 renderer='rhodecode:templates/files/files_edit.mako')
1063 def repo_files_edit_file(self):
1063 def repo_files_edit_file(self):
1064 _ = self.request.translate
1064 _ = self.request.translate
1065 c = self.load_default_context()
1065 c = self.load_default_context()
1066 commit_id, f_path = self._get_commit_and_path()
1066 commit_id, f_path = self._get_commit_and_path()
1067
1067
1068 self._ensure_not_locked()
1068 self._ensure_not_locked()
1069
1069
1070 if not self._is_valid_head(commit_id, self.rhodecode_vcs_repo):
1070 if not self._is_valid_head(commit_id, self.rhodecode_vcs_repo):
1071 h.flash(_('You can only edit files with commit '
1071 h.flash(_('You can only edit files with commit '
1072 'being a valid branch '), category='warning')
1072 'being a valid branch '), category='warning')
1073 raise HTTPFound(
1073 raise HTTPFound(
1074 h.route_path('repo_files',
1074 h.route_path('repo_files',
1075 repo_name=self.db_repo_name, commit_id='tip',
1075 repo_name=self.db_repo_name, commit_id='tip',
1076 f_path=f_path))
1076 f_path=f_path))
1077
1077
1078 c.commit = self._get_commit_or_redirect(commit_id)
1078 c.commit = self._get_commit_or_redirect(commit_id)
1079 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1079 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1080
1080
1081 if c.file.is_binary:
1081 if c.file.is_binary:
1082 files_url = h.route_path(
1082 files_url = h.route_path(
1083 'repo_files',
1083 'repo_files',
1084 repo_name=self.db_repo_name,
1084 repo_name=self.db_repo_name,
1085 commit_id=c.commit.raw_id, f_path=f_path)
1085 commit_id=c.commit.raw_id, f_path=f_path)
1086 raise HTTPFound(files_url)
1086 raise HTTPFound(files_url)
1087
1087
1088 c.default_message = _(
1088 c.default_message = _(
1089 'Edited file {} via RhodeCode Enterprise').format(f_path)
1089 'Edited file {} via RhodeCode Enterprise').format(f_path)
1090 c.f_path = f_path
1090 c.f_path = f_path
1091
1091
1092 return self._get_template_context(c)
1092 return self._get_template_context(c)
1093
1093
1094 @LoginRequired()
1094 @LoginRequired()
1095 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1095 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1096 @CSRFRequired()
1096 @CSRFRequired()
1097 @view_config(
1097 @view_config(
1098 route_name='repo_files_update_file', request_method='POST',
1098 route_name='repo_files_update_file', request_method='POST',
1099 renderer=None)
1099 renderer=None)
1100 def repo_files_update_file(self):
1100 def repo_files_update_file(self):
1101 _ = self.request.translate
1101 _ = self.request.translate
1102 c = self.load_default_context()
1102 c = self.load_default_context()
1103 commit_id, f_path = self._get_commit_and_path()
1103 commit_id, f_path = self._get_commit_and_path()
1104
1104
1105 self._ensure_not_locked()
1105 self._ensure_not_locked()
1106
1106
1107 if not self._is_valid_head(commit_id, self.rhodecode_vcs_repo):
1107 if not self._is_valid_head(commit_id, self.rhodecode_vcs_repo):
1108 h.flash(_('You can only edit files with commit '
1108 h.flash(_('You can only edit files with commit '
1109 'being a valid branch '), category='warning')
1109 'being a valid branch '), category='warning')
1110 raise HTTPFound(
1110 raise HTTPFound(
1111 h.route_path('repo_files',
1111 h.route_path('repo_files',
1112 repo_name=self.db_repo_name, commit_id='tip',
1112 repo_name=self.db_repo_name, commit_id='tip',
1113 f_path=f_path))
1113 f_path=f_path))
1114
1114
1115 c.commit = self._get_commit_or_redirect(commit_id)
1115 c.commit = self._get_commit_or_redirect(commit_id)
1116 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1116 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1117
1117
1118 if c.file.is_binary:
1118 if c.file.is_binary:
1119 raise HTTPFound(
1119 raise HTTPFound(
1120 h.route_path('repo_files',
1120 h.route_path('repo_files',
1121 repo_name=self.db_repo_name,
1121 repo_name=self.db_repo_name,
1122 commit_id=c.commit.raw_id,
1122 commit_id=c.commit.raw_id,
1123 f_path=f_path))
1123 f_path=f_path))
1124
1124
1125 c.default_message = _(
1125 c.default_message = _(
1126 'Edited file {} via RhodeCode Enterprise').format(f_path)
1126 'Edited file {} via RhodeCode Enterprise').format(f_path)
1127 c.f_path = f_path
1127 c.f_path = f_path
1128 old_content = c.file.content
1128 old_content = c.file.content
1129 sl = old_content.splitlines(1)
1129 sl = old_content.splitlines(1)
1130 first_line = sl[0] if sl else ''
1130 first_line = sl[0] if sl else ''
1131
1131
1132 r_post = self.request.POST
1132 r_post = self.request.POST
1133 # modes: 0 - Unix, 1 - Mac, 2 - DOS
1133 # modes: 0 - Unix, 1 - Mac, 2 - DOS
1134 mode = detect_mode(first_line, 0)
1134 mode = detect_mode(first_line, 0)
1135 content = convert_line_endings(r_post.get('content', ''), mode)
1135 content = convert_line_endings(r_post.get('content', ''), mode)
1136
1136
1137 message = r_post.get('message') or c.default_message
1137 message = r_post.get('message') or c.default_message
1138 org_f_path = c.file.unicode_path
1138 org_f_path = c.file.unicode_path
1139 filename = r_post['filename']
1139 filename = r_post['filename']
1140 org_filename = c.file.name
1140 org_filename = c.file.name
1141
1141
1142 if content == old_content and filename == org_filename:
1142 if content == old_content and filename == org_filename:
1143 h.flash(_('No changes'), category='warning')
1143 h.flash(_('No changes'), category='warning')
1144 raise HTTPFound(
1144 raise HTTPFound(
1145 h.route_path('repo_commit', repo_name=self.db_repo_name,
1145 h.route_path('repo_commit', repo_name=self.db_repo_name,
1146 commit_id='tip'))
1146 commit_id='tip'))
1147 try:
1147 try:
1148 mapping = {
1148 mapping = {
1149 org_f_path: {
1149 org_f_path: {
1150 'org_filename': org_f_path,
1150 'org_filename': org_f_path,
1151 'filename': os.path.join(c.file.dir_path, filename),
1151 'filename': os.path.join(c.file.dir_path, filename),
1152 'content': content,
1152 'content': content,
1153 'lexer': '',
1153 'lexer': '',
1154 'op': 'mod',
1154 'op': 'mod',
1155 }
1155 }
1156 }
1156 }
1157
1157
1158 ScmModel().update_nodes(
1158 ScmModel().update_nodes(
1159 user=self._rhodecode_db_user.user_id,
1159 user=self._rhodecode_db_user.user_id,
1160 repo=self.db_repo,
1160 repo=self.db_repo,
1161 message=message,
1161 message=message,
1162 nodes=mapping,
1162 nodes=mapping,
1163 parent_commit=c.commit,
1163 parent_commit=c.commit,
1164 )
1164 )
1165
1165
1166 h.flash(
1166 h.flash(
1167 _('Successfully committed changes to file `{}`').format(
1167 _('Successfully committed changes to file `{}`').format(
1168 h.escape(f_path)), category='success')
1168 h.escape(f_path)), category='success')
1169 except Exception:
1169 except Exception:
1170 log.exception('Error occurred during commit')
1170 log.exception('Error occurred during commit')
1171 h.flash(_('Error occurred during commit'), category='error')
1171 h.flash(_('Error occurred during commit'), category='error')
1172 raise HTTPFound(
1172 raise HTTPFound(
1173 h.route_path('repo_commit', repo_name=self.db_repo_name,
1173 h.route_path('repo_commit', repo_name=self.db_repo_name,
1174 commit_id='tip'))
1174 commit_id='tip'))
1175
1175
1176 @LoginRequired()
1176 @LoginRequired()
1177 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1177 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1178 @view_config(
1178 @view_config(
1179 route_name='repo_files_add_file', request_method='GET',
1179 route_name='repo_files_add_file', request_method='GET',
1180 renderer='rhodecode:templates/files/files_add.mako')
1180 renderer='rhodecode:templates/files/files_add.mako')
1181 def repo_files_add_file(self):
1181 def repo_files_add_file(self):
1182 _ = self.request.translate
1182 _ = self.request.translate
1183 c = self.load_default_context()
1183 c = self.load_default_context()
1184 commit_id, f_path = self._get_commit_and_path()
1184 commit_id, f_path = self._get_commit_and_path()
1185
1185
1186 self._ensure_not_locked()
1186 self._ensure_not_locked()
1187
1187
1188 c.commit = self._get_commit_or_redirect(commit_id, redirect_after=False)
1188 c.commit = self._get_commit_or_redirect(commit_id, redirect_after=False)
1189 if c.commit is None:
1189 if c.commit is None:
1190 c.commit = EmptyCommit(alias=self.rhodecode_vcs_repo.alias)
1190 c.commit = EmptyCommit(alias=self.rhodecode_vcs_repo.alias)
1191 c.default_message = (_('Added file via RhodeCode Enterprise'))
1191 c.default_message = (_('Added file via RhodeCode Enterprise'))
1192 c.f_path = f_path.lstrip('/') # ensure not relative path
1192 c.f_path = f_path.lstrip('/') # ensure not relative path
1193
1193
1194 return self._get_template_context(c)
1194 return self._get_template_context(c)
1195
1195
1196 @LoginRequired()
1196 @LoginRequired()
1197 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1197 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1198 @CSRFRequired()
1198 @CSRFRequired()
1199 @view_config(
1199 @view_config(
1200 route_name='repo_files_create_file', request_method='POST',
1200 route_name='repo_files_create_file', request_method='POST',
1201 renderer=None)
1201 renderer=None)
1202 def repo_files_create_file(self):
1202 def repo_files_create_file(self):
1203 _ = self.request.translate
1203 _ = self.request.translate
1204 c = self.load_default_context()
1204 c = self.load_default_context()
1205 commit_id, f_path = self._get_commit_and_path()
1205 commit_id, f_path = self._get_commit_and_path()
1206
1206
1207 self._ensure_not_locked()
1207 self._ensure_not_locked()
1208
1208
1209 r_post = self.request.POST
1209 r_post = self.request.POST
1210
1210
1211 c.commit = self._get_commit_or_redirect(
1211 c.commit = self._get_commit_or_redirect(
1212 commit_id, redirect_after=False)
1212 commit_id, redirect_after=False)
1213 if c.commit is None:
1213 if c.commit is None:
1214 c.commit = EmptyCommit(alias=self.rhodecode_vcs_repo.alias)
1214 c.commit = EmptyCommit(alias=self.rhodecode_vcs_repo.alias)
1215 c.default_message = (_('Added file via RhodeCode Enterprise'))
1215 c.default_message = (_('Added file via RhodeCode Enterprise'))
1216 c.f_path = f_path
1216 c.f_path = f_path
1217 unix_mode = 0
1217 unix_mode = 0
1218 content = convert_line_endings(r_post.get('content', ''), unix_mode)
1218 content = convert_line_endings(r_post.get('content', ''), unix_mode)
1219
1219
1220 message = r_post.get('message') or c.default_message
1220 message = r_post.get('message') or c.default_message
1221 filename = r_post.get('filename')
1221 filename = r_post.get('filename')
1222 location = r_post.get('location', '') # dir location
1222 location = r_post.get('location', '') # dir location
1223 file_obj = r_post.get('upload_file', None)
1223 file_obj = r_post.get('upload_file', None)
1224
1224
1225 if file_obj is not None and hasattr(file_obj, 'filename'):
1225 if file_obj is not None and hasattr(file_obj, 'filename'):
1226 filename = r_post.get('filename_upload')
1226 filename = r_post.get('filename_upload')
1227 content = file_obj.file
1227 content = file_obj.file
1228
1228
1229 if hasattr(content, 'file'):
1229 if hasattr(content, 'file'):
1230 # non posix systems store real file under file attr
1230 # non posix systems store real file under file attr
1231 content = content.file
1231 content = content.file
1232
1232
1233 if self.rhodecode_vcs_repo.is_empty:
1233 if self.rhodecode_vcs_repo.is_empty:
1234 default_redirect_url = h.route_path(
1234 default_redirect_url = h.route_path(
1235 'repo_summary', repo_name=self.db_repo_name)
1235 'repo_summary', repo_name=self.db_repo_name)
1236 else:
1236 else:
1237 default_redirect_url = h.route_path(
1237 default_redirect_url = h.route_path(
1238 'repo_commit', repo_name=self.db_repo_name, commit_id='tip')
1238 'repo_commit', repo_name=self.db_repo_name, commit_id='tip')
1239
1239
1240 # If there's no commit, redirect to repo summary
1240 # If there's no commit, redirect to repo summary
1241 if type(c.commit) is EmptyCommit:
1241 if type(c.commit) is EmptyCommit:
1242 redirect_url = h.route_path(
1242 redirect_url = h.route_path(
1243 'repo_summary', repo_name=self.db_repo_name)
1243 'repo_summary', repo_name=self.db_repo_name)
1244 else:
1244 else:
1245 redirect_url = default_redirect_url
1245 redirect_url = default_redirect_url
1246
1246
1247 if not filename:
1247 if not filename:
1248 h.flash(_('No filename'), category='warning')
1248 h.flash(_('No filename'), category='warning')
1249 raise HTTPFound(redirect_url)
1249 raise HTTPFound(redirect_url)
1250
1250
1251 # extract the location from filename,
1251 # extract the location from filename,
1252 # allows using foo/bar.txt syntax to create subdirectories
1252 # allows using foo/bar.txt syntax to create subdirectories
1253 subdir_loc = filename.rsplit('/', 1)
1253 subdir_loc = filename.rsplit('/', 1)
1254 if len(subdir_loc) == 2:
1254 if len(subdir_loc) == 2:
1255 location = os.path.join(location, subdir_loc[0])
1255 location = os.path.join(location, subdir_loc[0])
1256
1256
1257 # strip all crap out of file, just leave the basename
1257 # strip all crap out of file, just leave the basename
1258 filename = os.path.basename(filename)
1258 filename = os.path.basename(filename)
1259 node_path = os.path.join(location, filename)
1259 node_path = os.path.join(location, filename)
1260 author = self._rhodecode_db_user.full_contact
1260 author = self._rhodecode_db_user.full_contact
1261
1261
1262 try:
1262 try:
1263 nodes = {
1263 nodes = {
1264 node_path: {
1264 node_path: {
1265 'content': content
1265 'content': content
1266 }
1266 }
1267 }
1267 }
1268 ScmModel().create_nodes(
1268 ScmModel().create_nodes(
1269 user=self._rhodecode_db_user.user_id,
1269 user=self._rhodecode_db_user.user_id,
1270 repo=self.db_repo,
1270 repo=self.db_repo,
1271 message=message,
1271 message=message,
1272 nodes=nodes,
1272 nodes=nodes,
1273 parent_commit=c.commit,
1273 parent_commit=c.commit,
1274 author=author,
1274 author=author,
1275 )
1275 )
1276
1276
1277 h.flash(
1277 h.flash(
1278 _('Successfully committed new file `{}`').format(
1278 _('Successfully committed new file `{}`').format(
1279 h.escape(node_path)), category='success')
1279 h.escape(node_path)), category='success')
1280 except NonRelativePathError:
1280 except NonRelativePathError:
1281 log.exception('Non Relative path found')
1281 log.exception('Non Relative path found')
1282 h.flash(_(
1282 h.flash(_(
1283 'The location specified must be a relative path and must not '
1283 'The location specified must be a relative path and must not '
1284 'contain .. in the path'), category='warning')
1284 'contain .. in the path'), category='warning')
1285 raise HTTPFound(default_redirect_url)
1285 raise HTTPFound(default_redirect_url)
1286 except (NodeError, NodeAlreadyExistsError) as e:
1286 except (NodeError, NodeAlreadyExistsError) as e:
1287 h.flash(_(h.escape(e)), category='error')
1287 h.flash(_(h.escape(e)), category='error')
1288 except Exception:
1288 except Exception:
1289 log.exception('Error occurred during commit')
1289 log.exception('Error occurred during commit')
1290 h.flash(_('Error occurred during commit'), category='error')
1290 h.flash(_('Error occurred during commit'), category='error')
1291
1291
1292 raise HTTPFound(default_redirect_url)
1292 raise HTTPFound(default_redirect_url)
General Comments 0
You need to be logged in to leave comments. Login now