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