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