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