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