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