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