##// END OF EJS Templates
archives: don't do automatic cull, we should only do it manually.
super-admin -
r5129:1a4ebf7b default
parent child Browse files
Show More
@@ -1,1584 +1,1581 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
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_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(db_repo_name.replace('/', '_'))
69 69
70 70 # e.g vcsserver-sub-1-abcfdef-archive-all.zip
71 71 # vcsserver-sub-0-abcfdef-COMMIT_SHA-PATH_SHA.zip
72 72
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}-{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_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_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 # NOTE: we get the config to pass to a call to lazy-init the SAME type of cache on vcsserver
417 417 d_cache_conf = get_archival_config(config=CONFIG)
418 418
419 419 reentrant_lock_key = archive_name_key + '.lock'
420 420 with ReentrantLock(d_cache, reentrant_lock_key):
421 421 # This is also a cache key
422 422 use_cached_archive = False
423 423 if archive_name_key in d_cache and not archive_cache_disable:
424 424 reader, tag = d_cache.get(archive_name_key, read=True, tag=True, retry=True)
425 425 use_cached_archive = True
426 426 log.debug('Found cached archive as key=%s tag=%s, serving archive from cache reader=%s',
427 427 archive_name_key, tag, reader.name)
428 428 else:
429 429 reader = None
430 430 log.debug('Archive with key=%s is not yet cached, creating one now...', archive_name_key)
431 431
432 432 # generate new archive, as previous was not found in the cache
433 433 if not reader:
434 # first remove expired items, before generating a new one :)
435 # we di this manually because automatic eviction is disabled
436 d_cache.cull(retry=True)
437 434
438 435 try:
439 436 commit.archive_repo(archive_name_key, archive_dir_name=archive_dir_name,
440 437 kind=fileformat, subrepos=subrepos,
441 438 archive_at_path=at_path, cache_config=d_cache_conf)
442 439 except ImproperArchiveTypeError:
443 440 return _('Unknown archive type')
444 441
445 442 reader, tag = d_cache.get(archive_name_key, read=True, tag=True, retry=True)
446 443
447 444 if not reader:
448 445 raise ValueError('archive cache reader is empty, failed to fetch file from distributed archive cache')
449 446
450 447 def archive_iterator(_reader):
451 448 while 1:
452 449 data = _reader.read(1024)
453 450 if not data:
454 451 break
455 452 yield data
456 453
457 454 response = Response(app_iter=archive_iterator(reader))
458 455 response.content_disposition = f'attachment; filename={response_archive_name}'
459 456 response.content_type = str(content_type)
460 457
461 458 try:
462 459 return response
463 460 finally:
464 461 # store download action
465 462 audit_logger.store_web(
466 463 'repo.archive.download', action_data={
467 464 'user_agent': self.request.user_agent,
468 465 'archive_name': archive_name_key,
469 466 'archive_spec': fname,
470 467 'archive_cached': use_cached_archive},
471 468 user=self._rhodecode_user,
472 469 repo=self.db_repo,
473 470 commit=True
474 471 )
475 472
476 473 def _get_file_node(self, commit_id, f_path):
477 474 if commit_id not in ['', None, 'None', '0' * 12, '0' * 40]:
478 475 commit = self.rhodecode_vcs_repo.get_commit(commit_id=commit_id)
479 476 try:
480 477 node = commit.get_node(f_path)
481 478 if node.is_dir():
482 479 raise NodeError(f'{node} path is a {type(node)} not a file')
483 480 except NodeDoesNotExistError:
484 481 commit = EmptyCommit(
485 482 commit_id=commit_id,
486 483 idx=commit.idx,
487 484 repo=commit.repository,
488 485 alias=commit.repository.alias,
489 486 message=commit.message,
490 487 author=commit.author,
491 488 date=commit.date)
492 489 node = FileNode(safe_bytes(f_path), b'', commit=commit)
493 490 else:
494 491 commit = EmptyCommit(
495 492 repo=self.rhodecode_vcs_repo,
496 493 alias=self.rhodecode_vcs_repo.alias)
497 494 node = FileNode(safe_bytes(f_path), b'', commit=commit)
498 495 return node
499 496
500 497 @LoginRequired()
501 498 @HasRepoPermissionAnyDecorator(
502 499 'repository.read', 'repository.write', 'repository.admin')
503 500 def repo_files_diff(self):
504 501 c = self.load_default_context()
505 502 f_path = self._get_f_path(self.request.matchdict)
506 503 diff1 = self.request.GET.get('diff1', '')
507 504 diff2 = self.request.GET.get('diff2', '')
508 505
509 506 path1, diff1 = parse_path_ref(diff1, default_path=f_path)
510 507
511 508 ignore_whitespace = str2bool(self.request.GET.get('ignorews'))
512 509 line_context = self.request.GET.get('context', 3)
513 510
514 511 if not any((diff1, diff2)):
515 512 h.flash(
516 513 'Need query parameter "diff1" or "diff2" to generate a diff.',
517 514 category='error')
518 515 raise HTTPBadRequest()
519 516
520 517 c.action = self.request.GET.get('diff')
521 518 if c.action not in ['download', 'raw']:
522 519 compare_url = h.route_path(
523 520 'repo_compare',
524 521 repo_name=self.db_repo_name,
525 522 source_ref_type='rev',
526 523 source_ref=diff1,
527 524 target_repo=self.db_repo_name,
528 525 target_ref_type='rev',
529 526 target_ref=diff2,
530 527 _query=dict(f_path=f_path))
531 528 # redirect to new view if we render diff
532 529 raise HTTPFound(compare_url)
533 530
534 531 try:
535 532 node1 = self._get_file_node(diff1, path1)
536 533 node2 = self._get_file_node(diff2, f_path)
537 534 except (RepositoryError, NodeError):
538 535 log.exception("Exception while trying to get node from repository")
539 536 raise HTTPFound(
540 537 h.route_path('repo_files', repo_name=self.db_repo_name,
541 538 commit_id='tip', f_path=f_path))
542 539
543 540 if all(isinstance(node.commit, EmptyCommit)
544 541 for node in (node1, node2)):
545 542 raise HTTPNotFound()
546 543
547 544 c.commit_1 = node1.commit
548 545 c.commit_2 = node2.commit
549 546
550 547 if c.action == 'download':
551 548 _diff = diffs.get_gitdiff(node1, node2,
552 549 ignore_whitespace=ignore_whitespace,
553 550 context=line_context)
554 551 # NOTE: this was using diff_format='gitdiff'
555 552 diff = diffs.DiffProcessor(_diff, diff_format='newdiff')
556 553
557 554 response = Response(self.path_filter.get_raw_patch(diff))
558 555 response.content_type = 'text/plain'
559 556 response.content_disposition = (
560 557 f'attachment; filename={f_path}_{diff1}_vs_{diff2}.diff'
561 558 )
562 559 charset = self._get_default_encoding(c)
563 560 if charset:
564 561 response.charset = charset
565 562 return response
566 563
567 564 elif c.action == 'raw':
568 565 _diff = diffs.get_gitdiff(node1, node2,
569 566 ignore_whitespace=ignore_whitespace,
570 567 context=line_context)
571 568 # NOTE: this was using diff_format='gitdiff'
572 569 diff = diffs.DiffProcessor(_diff, diff_format='newdiff')
573 570
574 571 response = Response(self.path_filter.get_raw_patch(diff))
575 572 response.content_type = 'text/plain'
576 573 charset = self._get_default_encoding(c)
577 574 if charset:
578 575 response.charset = charset
579 576 return response
580 577
581 578 # in case we ever end up here
582 579 raise HTTPNotFound()
583 580
584 581 @LoginRequired()
585 582 @HasRepoPermissionAnyDecorator(
586 583 'repository.read', 'repository.write', 'repository.admin')
587 584 def repo_files_diff_2way_redirect(self):
588 585 """
589 586 Kept only to make OLD links work
590 587 """
591 588 f_path = self._get_f_path_unchecked(self.request.matchdict)
592 589 diff1 = self.request.GET.get('diff1', '')
593 590 diff2 = self.request.GET.get('diff2', '')
594 591
595 592 if not any((diff1, diff2)):
596 593 h.flash(
597 594 'Need query parameter "diff1" or "diff2" to generate a diff.',
598 595 category='error')
599 596 raise HTTPBadRequest()
600 597
601 598 compare_url = h.route_path(
602 599 'repo_compare',
603 600 repo_name=self.db_repo_name,
604 601 source_ref_type='rev',
605 602 source_ref=diff1,
606 603 target_ref_type='rev',
607 604 target_ref=diff2,
608 605 _query=dict(f_path=f_path, diffmode='sideside',
609 606 target_repo=self.db_repo_name,))
610 607 raise HTTPFound(compare_url)
611 608
612 609 @LoginRequired()
613 610 def repo_files_default_commit_redirect(self):
614 611 """
615 612 Special page that redirects to the landing page of files based on the default
616 613 commit for repository
617 614 """
618 615 c = self.load_default_context()
619 616 ref_name = c.rhodecode_db_repo.landing_ref_name
620 617 landing_url = h.repo_files_by_ref_url(
621 618 c.rhodecode_db_repo.repo_name,
622 619 c.rhodecode_db_repo.repo_type,
623 620 f_path='',
624 621 ref_name=ref_name,
625 622 commit_id='tip',
626 623 query=dict(at=ref_name)
627 624 )
628 625
629 626 raise HTTPFound(landing_url)
630 627
631 628 @LoginRequired()
632 629 @HasRepoPermissionAnyDecorator(
633 630 'repository.read', 'repository.write', 'repository.admin')
634 631 def repo_files(self):
635 632 c = self.load_default_context()
636 633
637 634 view_name = getattr(self.request.matched_route, 'name', None)
638 635
639 636 c.annotate = view_name == 'repo_files:annotated'
640 637 # default is false, but .rst/.md files later are auto rendered, we can
641 638 # overwrite auto rendering by setting this GET flag
642 639 c.renderer = view_name == 'repo_files:rendered' or not self.request.GET.get('no-render', False)
643 640
644 641 commit_id, f_path = self._get_commit_and_path()
645 642
646 643 c.commit = self._get_commit_or_redirect(commit_id)
647 644 c.branch = self.request.GET.get('branch', None)
648 645 c.f_path = f_path
649 646 at_rev = self.request.GET.get('at')
650 647
651 648 # prev link
652 649 try:
653 650 prev_commit = c.commit.prev(c.branch)
654 651 c.prev_commit = prev_commit
655 652 c.url_prev = h.route_path(
656 653 'repo_files', repo_name=self.db_repo_name,
657 654 commit_id=prev_commit.raw_id, f_path=f_path)
658 655 if c.branch:
659 656 c.url_prev += '?branch=%s' % c.branch
660 657 except (CommitDoesNotExistError, VCSError):
661 658 c.url_prev = '#'
662 659 c.prev_commit = EmptyCommit()
663 660
664 661 # next link
665 662 try:
666 663 next_commit = c.commit.next(c.branch)
667 664 c.next_commit = next_commit
668 665 c.url_next = h.route_path(
669 666 'repo_files', repo_name=self.db_repo_name,
670 667 commit_id=next_commit.raw_id, f_path=f_path)
671 668 if c.branch:
672 669 c.url_next += '?branch=%s' % c.branch
673 670 except (CommitDoesNotExistError, VCSError):
674 671 c.url_next = '#'
675 672 c.next_commit = EmptyCommit()
676 673
677 674 # files or dirs
678 675 try:
679 676 c.file = c.commit.get_node(f_path, pre_load=['is_binary', 'size', 'data'])
680 677
681 678 c.file_author = True
682 679 c.file_tree = ''
683 680
684 681 # load file content
685 682 if c.file.is_file():
686 683 c.lf_node = {}
687 684
688 685 has_lf_enabled = self._is_lf_enabled(self.db_repo)
689 686 if has_lf_enabled:
690 687 c.lf_node = c.file.get_largefile_node()
691 688
692 689 c.file_source_page = 'true'
693 690 c.file_last_commit = c.file.last_commit
694 691
695 692 c.file_size_too_big = c.file.size > c.visual.cut_off_limit_file
696 693
697 694 if not (c.file_size_too_big or c.file.is_binary):
698 695 if c.annotate: # annotation has precedence over renderer
699 696 c.annotated_lines = filenode_as_annotated_lines_tokens(
700 697 c.file
701 698 )
702 699 else:
703 700 c.renderer = (
704 701 c.renderer and h.renderer_from_filename(c.file.path)
705 702 )
706 703 if not c.renderer:
707 704 c.lines = filenode_as_lines_tokens(c.file)
708 705
709 706 _branch_name, _sha_commit_id, is_head = \
710 707 self._is_valid_head(commit_id, self.rhodecode_vcs_repo,
711 708 landing_ref=self.db_repo.landing_ref_name)
712 709 c.on_branch_head = is_head
713 710
714 711 branch = c.commit.branch if (
715 712 c.commit.branch and '/' not in c.commit.branch) else None
716 713 c.branch_or_raw_id = branch or c.commit.raw_id
717 714 c.branch_name = c.commit.branch or h.short_id(c.commit.raw_id)
718 715
719 716 author = c.file_last_commit.author
720 717 c.authors = [[
721 718 h.email(author),
722 719 h.person(author, 'username_or_name_or_email'),
723 720 1
724 721 ]]
725 722
726 723 else: # load tree content at path
727 724 c.file_source_page = 'false'
728 725 c.authors = []
729 726 # this loads a simple tree without metadata to speed things up
730 727 # later via ajax we call repo_nodetree_full and fetch whole
731 728 c.file_tree = self._get_tree_at_commit(c, c.commit.raw_id, f_path, at_rev=at_rev)
732 729
733 730 c.readme_data, c.readme_file = \
734 731 self._get_readme_data(self.db_repo, c.visual.default_renderer,
735 732 c.commit.raw_id, f_path)
736 733
737 734 except RepositoryError as e:
738 735 h.flash(h.escape(safe_str(e)), category='error')
739 736 raise HTTPNotFound()
740 737
741 738 if self.request.environ.get('HTTP_X_PJAX'):
742 739 html = render('rhodecode:templates/files/files_pjax.mako',
743 740 self._get_template_context(c), self.request)
744 741 else:
745 742 html = render('rhodecode:templates/files/files.mako',
746 743 self._get_template_context(c), self.request)
747 744 return Response(html)
748 745
749 746 @HasRepoPermissionAnyDecorator(
750 747 'repository.read', 'repository.write', 'repository.admin')
751 748 def repo_files_annotated_previous(self):
752 749 self.load_default_context()
753 750
754 751 commit_id, f_path = self._get_commit_and_path()
755 752 commit = self._get_commit_or_redirect(commit_id)
756 753 prev_commit_id = commit.raw_id
757 754 line_anchor = self.request.GET.get('line_anchor')
758 755 is_file = False
759 756 try:
760 757 _file = commit.get_node(f_path)
761 758 is_file = _file.is_file()
762 759 except (NodeDoesNotExistError, CommitDoesNotExistError, VCSError):
763 760 pass
764 761
765 762 if is_file:
766 763 history = commit.get_path_history(f_path)
767 764 prev_commit_id = history[1].raw_id \
768 765 if len(history) > 1 else prev_commit_id
769 766 prev_url = h.route_path(
770 767 'repo_files:annotated', repo_name=self.db_repo_name,
771 768 commit_id=prev_commit_id, f_path=f_path,
772 769 _anchor=f'L{line_anchor}')
773 770
774 771 raise HTTPFound(prev_url)
775 772
776 773 @LoginRequired()
777 774 @HasRepoPermissionAnyDecorator(
778 775 'repository.read', 'repository.write', 'repository.admin')
779 776 def repo_nodetree_full(self):
780 777 """
781 778 Returns rendered html of file tree that contains commit date,
782 779 author, commit_id for the specified combination of
783 780 repo, commit_id and file path
784 781 """
785 782 c = self.load_default_context()
786 783
787 784 commit_id, f_path = self._get_commit_and_path()
788 785 commit = self._get_commit_or_redirect(commit_id)
789 786 try:
790 787 dir_node = commit.get_node(f_path)
791 788 except RepositoryError as e:
792 789 return Response(f'error: {h.escape(safe_str(e))}')
793 790
794 791 if dir_node.is_file():
795 792 return Response('')
796 793
797 794 c.file = dir_node
798 795 c.commit = commit
799 796 at_rev = self.request.GET.get('at')
800 797
801 798 html = self._get_tree_at_commit(
802 799 c, commit.raw_id, dir_node.path, full_load=True, at_rev=at_rev)
803 800
804 801 return Response(html)
805 802
806 803 def _get_attachement_headers(self, f_path):
807 804 f_name = safe_str(f_path.split(Repository.NAME_SEP)[-1])
808 805 safe_path = f_name.replace('"', '\\"')
809 806 encoded_path = urllib.parse.quote(f_name)
810 807
811 808 return "attachment; " \
812 809 "filename=\"{}\"; " \
813 810 "filename*=UTF-8\'\'{}".format(safe_path, encoded_path)
814 811
815 812 @LoginRequired()
816 813 @HasRepoPermissionAnyDecorator(
817 814 'repository.read', 'repository.write', 'repository.admin')
818 815 def repo_file_raw(self):
819 816 """
820 817 Action for show as raw, some mimetypes are "rendered",
821 818 those include images, icons.
822 819 """
823 820 c = self.load_default_context()
824 821
825 822 commit_id, f_path = self._get_commit_and_path()
826 823 commit = self._get_commit_or_redirect(commit_id)
827 824 file_node = self._get_filenode_or_redirect(commit, f_path)
828 825
829 826 raw_mimetype_mapping = {
830 827 # map original mimetype to a mimetype used for "show as raw"
831 828 # you can also provide a content-disposition to override the
832 829 # default "attachment" disposition.
833 830 # orig_type: (new_type, new_dispo)
834 831
835 832 # show images inline:
836 833 # Do not re-add SVG: it is unsafe and permits XSS attacks. One can
837 834 # for example render an SVG with javascript inside or even render
838 835 # HTML.
839 836 'image/x-icon': ('image/x-icon', 'inline'),
840 837 'image/png': ('image/png', 'inline'),
841 838 'image/gif': ('image/gif', 'inline'),
842 839 'image/jpeg': ('image/jpeg', 'inline'),
843 840 'application/pdf': ('application/pdf', 'inline'),
844 841 }
845 842
846 843 mimetype = file_node.mimetype
847 844 try:
848 845 mimetype, disposition = raw_mimetype_mapping[mimetype]
849 846 except KeyError:
850 847 # we don't know anything special about this, handle it safely
851 848 if file_node.is_binary:
852 849 # do same as download raw for binary files
853 850 mimetype, disposition = 'application/octet-stream', 'attachment'
854 851 else:
855 852 # do not just use the original mimetype, but force text/plain,
856 853 # otherwise it would serve text/html and that might be unsafe.
857 854 # Note: underlying vcs library fakes text/plain mimetype if the
858 855 # mimetype can not be determined and it thinks it is not
859 856 # binary.This might lead to erroneous text display in some
860 857 # cases, but helps in other cases, like with text files
861 858 # without extension.
862 859 mimetype, disposition = 'text/plain', 'inline'
863 860
864 861 if disposition == 'attachment':
865 862 disposition = self._get_attachement_headers(f_path)
866 863
867 864 stream_content = file_node.stream_bytes()
868 865
869 866 response = Response(app_iter=stream_content)
870 867 response.content_disposition = disposition
871 868 response.content_type = mimetype
872 869
873 870 charset = self._get_default_encoding(c)
874 871 if charset:
875 872 response.charset = charset
876 873
877 874 return response
878 875
879 876 @LoginRequired()
880 877 @HasRepoPermissionAnyDecorator(
881 878 'repository.read', 'repository.write', 'repository.admin')
882 879 def repo_file_download(self):
883 880 c = self.load_default_context()
884 881
885 882 commit_id, f_path = self._get_commit_and_path()
886 883 commit = self._get_commit_or_redirect(commit_id)
887 884 file_node = self._get_filenode_or_redirect(commit, f_path)
888 885
889 886 if self.request.GET.get('lf'):
890 887 # only if lf get flag is passed, we download this file
891 888 # as LFS/Largefile
892 889 lf_node = file_node.get_largefile_node()
893 890 if lf_node:
894 891 # overwrite our pointer with the REAL large-file
895 892 file_node = lf_node
896 893
897 894 disposition = self._get_attachement_headers(f_path)
898 895
899 896 stream_content = file_node.stream_bytes()
900 897
901 898 response = Response(app_iter=stream_content)
902 899 response.content_disposition = disposition
903 900 response.content_type = file_node.mimetype
904 901
905 902 charset = self._get_default_encoding(c)
906 903 if charset:
907 904 response.charset = charset
908 905
909 906 return response
910 907
911 908 def _get_nodelist_at_commit(self, repo_name, repo_id, commit_id, f_path):
912 909
913 910 cache_seconds = safe_int(
914 911 rhodecode.CONFIG.get('rc_cache.cache_repo.expiration_time'))
915 912 cache_on = cache_seconds > 0
916 913 log.debug(
917 914 'Computing FILE SEARCH for repo_id %s commit_id `%s` and path `%s`'
918 915 'with caching: %s[TTL: %ss]' % (
919 916 repo_id, commit_id, f_path, cache_on, cache_seconds or 0))
920 917
921 918 cache_namespace_uid = f'repo.{repo_id}'
922 919 region = rc_cache.get_or_create_region('cache_repo', cache_namespace_uid)
923 920
924 921 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid, condition=cache_on)
925 922 def compute_file_search(_name_hash, _repo_id, _commit_id, _f_path):
926 923 log.debug('Generating cached nodelist for repo_id:%s, %s, %s',
927 924 _repo_id, commit_id, f_path)
928 925 try:
929 926 _d, _f = ScmModel().get_quick_filter_nodes(repo_name, _commit_id, _f_path)
930 927 except (RepositoryError, CommitDoesNotExistError, Exception) as e:
931 928 log.exception(safe_str(e))
932 929 h.flash(h.escape(safe_str(e)), category='error')
933 930 raise HTTPFound(h.route_path(
934 931 'repo_files', repo_name=self.db_repo_name,
935 932 commit_id='tip', f_path='/'))
936 933
937 934 return _d + _f
938 935
939 936 result = compute_file_search(self.db_repo.repo_name_hash, self.db_repo.repo_id,
940 937 commit_id, f_path)
941 938 return filter(lambda n: self.path_filter.path_access_allowed(n['name']), result)
942 939
943 940 @LoginRequired()
944 941 @HasRepoPermissionAnyDecorator(
945 942 'repository.read', 'repository.write', 'repository.admin')
946 943 def repo_nodelist(self):
947 944 self.load_default_context()
948 945
949 946 commit_id, f_path = self._get_commit_and_path()
950 947 commit = self._get_commit_or_redirect(commit_id)
951 948
952 949 metadata = self._get_nodelist_at_commit(
953 950 self.db_repo_name, self.db_repo.repo_id, commit.raw_id, f_path)
954 951 return {'nodes': [x for x in metadata]}
955 952
956 953 def _create_references(self, branches_or_tags, symbolic_reference, f_path, ref_type):
957 954 items = []
958 955 for name, commit_id in branches_or_tags.items():
959 956 sym_ref = symbolic_reference(commit_id, name, f_path, ref_type)
960 957 items.append((sym_ref, name, ref_type))
961 958 return items
962 959
963 960 def _symbolic_reference(self, commit_id, name, f_path, ref_type):
964 961 return commit_id
965 962
966 963 def _symbolic_reference_svn(self, commit_id, name, f_path, ref_type):
967 964 return commit_id
968 965
969 966 # NOTE(dan): old code we used in "diff" mode compare
970 967 new_f_path = vcspath.join(name, f_path)
971 968 return f'{new_f_path}@{commit_id}'
972 969
973 970 def _get_node_history(self, commit_obj, f_path, commits=None):
974 971 """
975 972 get commit history for given node
976 973
977 974 :param commit_obj: commit to calculate history
978 975 :param f_path: path for node to calculate history for
979 976 :param commits: if passed don't calculate history and take
980 977 commits defined in this list
981 978 """
982 979 _ = self.request.translate
983 980
984 981 # calculate history based on tip
985 982 tip = self.rhodecode_vcs_repo.get_commit()
986 983 if commits is None:
987 984 pre_load = ["author", "branch"]
988 985 try:
989 986 commits = tip.get_path_history(f_path, pre_load=pre_load)
990 987 except (NodeDoesNotExistError, CommitError):
991 988 # this node is not present at tip!
992 989 commits = commit_obj.get_path_history(f_path, pre_load=pre_load)
993 990
994 991 history = []
995 992 commits_group = ([], _("Changesets"))
996 993 for commit in commits:
997 994 branch = ' (%s)' % commit.branch if commit.branch else ''
998 995 n_desc = f'r{commit.idx}:{commit.short_id}{branch}'
999 996 commits_group[0].append((commit.raw_id, n_desc, 'sha'))
1000 997 history.append(commits_group)
1001 998
1002 999 symbolic_reference = self._symbolic_reference
1003 1000
1004 1001 if self.rhodecode_vcs_repo.alias == 'svn':
1005 1002 adjusted_f_path = RepoFilesView.adjust_file_path_for_svn(
1006 1003 f_path, self.rhodecode_vcs_repo)
1007 1004 if adjusted_f_path != f_path:
1008 1005 log.debug(
1009 1006 'Recognized svn tag or branch in file "%s", using svn '
1010 1007 'specific symbolic references', f_path)
1011 1008 f_path = adjusted_f_path
1012 1009 symbolic_reference = self._symbolic_reference_svn
1013 1010
1014 1011 branches = self._create_references(
1015 1012 self.rhodecode_vcs_repo.branches, symbolic_reference, f_path, 'branch')
1016 1013 branches_group = (branches, _("Branches"))
1017 1014
1018 1015 tags = self._create_references(
1019 1016 self.rhodecode_vcs_repo.tags, symbolic_reference, f_path, 'tag')
1020 1017 tags_group = (tags, _("Tags"))
1021 1018
1022 1019 history.append(branches_group)
1023 1020 history.append(tags_group)
1024 1021
1025 1022 return history, commits
1026 1023
1027 1024 @LoginRequired()
1028 1025 @HasRepoPermissionAnyDecorator(
1029 1026 'repository.read', 'repository.write', 'repository.admin')
1030 1027 def repo_file_history(self):
1031 1028 self.load_default_context()
1032 1029
1033 1030 commit_id, f_path = self._get_commit_and_path()
1034 1031 commit = self._get_commit_or_redirect(commit_id)
1035 1032 file_node = self._get_filenode_or_redirect(commit, f_path)
1036 1033
1037 1034 if file_node.is_file():
1038 1035 file_history, _hist = self._get_node_history(commit, f_path)
1039 1036
1040 1037 res = []
1041 1038 for section_items, section in file_history:
1042 1039 items = []
1043 1040 for obj_id, obj_text, obj_type in section_items:
1044 1041 at_rev = ''
1045 1042 if obj_type in ['branch', 'bookmark', 'tag']:
1046 1043 at_rev = obj_text
1047 1044 entry = {
1048 1045 'id': obj_id,
1049 1046 'text': obj_text,
1050 1047 'type': obj_type,
1051 1048 'at_rev': at_rev
1052 1049 }
1053 1050
1054 1051 items.append(entry)
1055 1052
1056 1053 res.append({
1057 1054 'text': section,
1058 1055 'children': items
1059 1056 })
1060 1057
1061 1058 data = {
1062 1059 'more': False,
1063 1060 'results': res
1064 1061 }
1065 1062 return data
1066 1063
1067 1064 log.warning('Cannot fetch history for directory')
1068 1065 raise HTTPBadRequest()
1069 1066
1070 1067 @LoginRequired()
1071 1068 @HasRepoPermissionAnyDecorator(
1072 1069 'repository.read', 'repository.write', 'repository.admin')
1073 1070 def repo_file_authors(self):
1074 1071 c = self.load_default_context()
1075 1072
1076 1073 commit_id, f_path = self._get_commit_and_path()
1077 1074 commit = self._get_commit_or_redirect(commit_id)
1078 1075 file_node = self._get_filenode_or_redirect(commit, f_path)
1079 1076
1080 1077 if not file_node.is_file():
1081 1078 raise HTTPBadRequest()
1082 1079
1083 1080 c.file_last_commit = file_node.last_commit
1084 1081 if self.request.GET.get('annotate') == '1':
1085 1082 # use _hist from annotation if annotation mode is on
1086 1083 commit_ids = {x[1] for x in file_node.annotate}
1087 1084 _hist = (
1088 1085 self.rhodecode_vcs_repo.get_commit(commit_id)
1089 1086 for commit_id in commit_ids)
1090 1087 else:
1091 1088 _f_history, _hist = self._get_node_history(commit, f_path)
1092 1089 c.file_author = False
1093 1090
1094 1091 unique = collections.OrderedDict()
1095 1092 for commit in _hist:
1096 1093 author = commit.author
1097 1094 if author not in unique:
1098 1095 unique[commit.author] = [
1099 1096 h.email(author),
1100 1097 h.person(author, 'username_or_name_or_email'),
1101 1098 1 # counter
1102 1099 ]
1103 1100
1104 1101 else:
1105 1102 # increase counter
1106 1103 unique[commit.author][2] += 1
1107 1104
1108 1105 c.authors = [val for val in unique.values()]
1109 1106
1110 1107 return self._get_template_context(c)
1111 1108
1112 1109 @LoginRequired()
1113 1110 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1114 1111 def repo_files_check_head(self):
1115 1112 self.load_default_context()
1116 1113
1117 1114 commit_id, f_path = self._get_commit_and_path()
1118 1115 _branch_name, _sha_commit_id, is_head = \
1119 1116 self._is_valid_head(commit_id, self.rhodecode_vcs_repo,
1120 1117 landing_ref=self.db_repo.landing_ref_name)
1121 1118
1122 1119 new_path = self.request.POST.get('path')
1123 1120 operation = self.request.POST.get('operation')
1124 1121 path_exist = ''
1125 1122
1126 1123 if new_path and operation in ['create', 'upload']:
1127 1124 new_f_path = os.path.join(f_path.lstrip('/'), new_path)
1128 1125 try:
1129 1126 commit_obj = self.rhodecode_vcs_repo.get_commit(commit_id)
1130 1127 # NOTE(dan): construct whole path without leading /
1131 1128 file_node = commit_obj.get_node(new_f_path)
1132 1129 if file_node is not None:
1133 1130 path_exist = new_f_path
1134 1131 except EmptyRepositoryError:
1135 1132 pass
1136 1133 except Exception:
1137 1134 pass
1138 1135
1139 1136 return {
1140 1137 'branch': _branch_name,
1141 1138 'sha': _sha_commit_id,
1142 1139 'is_head': is_head,
1143 1140 'path_exists': path_exist
1144 1141 }
1145 1142
1146 1143 @LoginRequired()
1147 1144 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1148 1145 def repo_files_remove_file(self):
1149 1146 _ = self.request.translate
1150 1147 c = self.load_default_context()
1151 1148 commit_id, f_path = self._get_commit_and_path()
1152 1149
1153 1150 self._ensure_not_locked()
1154 1151 _branch_name, _sha_commit_id, is_head = \
1155 1152 self._is_valid_head(commit_id, self.rhodecode_vcs_repo,
1156 1153 landing_ref=self.db_repo.landing_ref_name)
1157 1154
1158 1155 self.forbid_non_head(is_head, f_path)
1159 1156 self.check_branch_permission(_branch_name)
1160 1157
1161 1158 c.commit = self._get_commit_or_redirect(commit_id)
1162 1159 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1163 1160
1164 1161 c.default_message = _(
1165 1162 'Deleted file {} via RhodeCode Enterprise').format(f_path)
1166 1163 c.f_path = f_path
1167 1164
1168 1165 return self._get_template_context(c)
1169 1166
1170 1167 @LoginRequired()
1171 1168 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1172 1169 @CSRFRequired()
1173 1170 def repo_files_delete_file(self):
1174 1171 _ = self.request.translate
1175 1172
1176 1173 c = self.load_default_context()
1177 1174 commit_id, f_path = self._get_commit_and_path()
1178 1175
1179 1176 self._ensure_not_locked()
1180 1177 _branch_name, _sha_commit_id, is_head = \
1181 1178 self._is_valid_head(commit_id, self.rhodecode_vcs_repo,
1182 1179 landing_ref=self.db_repo.landing_ref_name)
1183 1180
1184 1181 self.forbid_non_head(is_head, f_path)
1185 1182 self.check_branch_permission(_branch_name)
1186 1183
1187 1184 c.commit = self._get_commit_or_redirect(commit_id)
1188 1185 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1189 1186
1190 1187 c.default_message = _(
1191 1188 'Deleted file {} via RhodeCode Enterprise').format(f_path)
1192 1189 c.f_path = f_path
1193 1190 node_path = f_path
1194 1191 author = self._rhodecode_db_user.full_contact
1195 1192 message = self.request.POST.get('message') or c.default_message
1196 1193 try:
1197 1194 nodes = {
1198 1195 safe_bytes(node_path): {
1199 1196 'content': b''
1200 1197 }
1201 1198 }
1202 1199 ScmModel().delete_nodes(
1203 1200 user=self._rhodecode_db_user.user_id, repo=self.db_repo,
1204 1201 message=message,
1205 1202 nodes=nodes,
1206 1203 parent_commit=c.commit,
1207 1204 author=author,
1208 1205 )
1209 1206
1210 1207 h.flash(
1211 1208 _('Successfully deleted file `{}`').format(
1212 1209 h.escape(f_path)), category='success')
1213 1210 except Exception:
1214 1211 log.exception('Error during commit operation')
1215 1212 h.flash(_('Error occurred during commit'), category='error')
1216 1213 raise HTTPFound(
1217 1214 h.route_path('repo_commit', repo_name=self.db_repo_name,
1218 1215 commit_id='tip'))
1219 1216
1220 1217 @LoginRequired()
1221 1218 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1222 1219 def repo_files_edit_file(self):
1223 1220 _ = self.request.translate
1224 1221 c = self.load_default_context()
1225 1222 commit_id, f_path = self._get_commit_and_path()
1226 1223
1227 1224 self._ensure_not_locked()
1228 1225 _branch_name, _sha_commit_id, is_head = \
1229 1226 self._is_valid_head(commit_id, self.rhodecode_vcs_repo,
1230 1227 landing_ref=self.db_repo.landing_ref_name)
1231 1228
1232 1229 self.forbid_non_head(is_head, f_path, commit_id=commit_id)
1233 1230 self.check_branch_permission(_branch_name, commit_id=commit_id)
1234 1231
1235 1232 c.commit = self._get_commit_or_redirect(commit_id)
1236 1233 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1237 1234
1238 1235 if c.file.is_binary:
1239 1236 files_url = h.route_path(
1240 1237 'repo_files',
1241 1238 repo_name=self.db_repo_name,
1242 1239 commit_id=c.commit.raw_id, f_path=f_path)
1243 1240 raise HTTPFound(files_url)
1244 1241
1245 1242 c.default_message = _('Edited file {} via RhodeCode Enterprise').format(f_path)
1246 1243 c.f_path = f_path
1247 1244
1248 1245 return self._get_template_context(c)
1249 1246
1250 1247 @LoginRequired()
1251 1248 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1252 1249 @CSRFRequired()
1253 1250 def repo_files_update_file(self):
1254 1251 _ = self.request.translate
1255 1252 c = self.load_default_context()
1256 1253 commit_id, f_path = self._get_commit_and_path()
1257 1254
1258 1255 self._ensure_not_locked()
1259 1256
1260 1257 c.commit = self._get_commit_or_redirect(commit_id)
1261 1258 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1262 1259
1263 1260 if c.file.is_binary:
1264 1261 raise HTTPFound(h.route_path('repo_files', repo_name=self.db_repo_name,
1265 1262 commit_id=c.commit.raw_id, f_path=f_path))
1266 1263
1267 1264 _branch_name, _sha_commit_id, is_head = \
1268 1265 self._is_valid_head(commit_id, self.rhodecode_vcs_repo,
1269 1266 landing_ref=self.db_repo.landing_ref_name)
1270 1267
1271 1268 self.forbid_non_head(is_head, f_path, commit_id=commit_id)
1272 1269 self.check_branch_permission(_branch_name, commit_id=commit_id)
1273 1270
1274 1271 c.default_message = _('Edited file {} via RhodeCode Enterprise').format(f_path)
1275 1272 c.f_path = f_path
1276 1273
1277 1274 old_content = c.file.str_content
1278 1275 sl = old_content.splitlines(1)
1279 1276 first_line = sl[0] if sl else ''
1280 1277
1281 1278 r_post = self.request.POST
1282 1279 # line endings: 0 - Unix, 1 - Mac, 2 - DOS
1283 1280 line_ending_mode = detect_mode(first_line, 0)
1284 1281 content = convert_line_endings(r_post.get('content', ''), line_ending_mode)
1285 1282
1286 1283 message = r_post.get('message') or c.default_message
1287 1284
1288 1285 org_node_path = c.file.str_path
1289 1286 filename = r_post['filename']
1290 1287
1291 1288 root_path = c.file.dir_path
1292 1289 pure_path = self.create_pure_path(root_path, filename)
1293 1290 node_path = pure_path.as_posix()
1294 1291
1295 1292 default_redirect_url = h.route_path('repo_commit', repo_name=self.db_repo_name,
1296 1293 commit_id=commit_id)
1297 1294 if content == old_content and node_path == org_node_path:
1298 1295 h.flash(_('No changes detected on {}').format(h.escape(org_node_path)),
1299 1296 category='warning')
1300 1297 raise HTTPFound(default_redirect_url)
1301 1298
1302 1299 try:
1303 1300 mapping = {
1304 1301 c.file.bytes_path: {
1305 1302 'org_filename': org_node_path,
1306 1303 'filename': safe_bytes(node_path),
1307 1304 'content': safe_bytes(content),
1308 1305 'lexer': '',
1309 1306 'op': 'mod',
1310 1307 'mode': c.file.mode
1311 1308 }
1312 1309 }
1313 1310
1314 1311 commit = ScmModel().update_nodes(
1315 1312 user=self._rhodecode_db_user.user_id,
1316 1313 repo=self.db_repo,
1317 1314 message=message,
1318 1315 nodes=mapping,
1319 1316 parent_commit=c.commit,
1320 1317 )
1321 1318
1322 1319 h.flash(_('Successfully committed changes to file `{}`').format(
1323 1320 h.escape(f_path)), category='success')
1324 1321 default_redirect_url = h.route_path(
1325 1322 'repo_commit', repo_name=self.db_repo_name, commit_id=commit.raw_id)
1326 1323
1327 1324 except Exception:
1328 1325 log.exception('Error occurred during commit')
1329 1326 h.flash(_('Error occurred during commit'), category='error')
1330 1327
1331 1328 raise HTTPFound(default_redirect_url)
1332 1329
1333 1330 @LoginRequired()
1334 1331 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1335 1332 def repo_files_add_file(self):
1336 1333 _ = self.request.translate
1337 1334 c = self.load_default_context()
1338 1335 commit_id, f_path = self._get_commit_and_path()
1339 1336
1340 1337 self._ensure_not_locked()
1341 1338
1342 1339 c.commit = self._get_commit_or_redirect(commit_id, redirect_after=False)
1343 1340 if c.commit is None:
1344 1341 c.commit = EmptyCommit(alias=self.rhodecode_vcs_repo.alias)
1345 1342
1346 1343 if self.rhodecode_vcs_repo.is_empty():
1347 1344 # for empty repository we cannot check for current branch, we rely on
1348 1345 # c.commit.branch instead
1349 1346 _branch_name, _sha_commit_id, is_head = c.commit.branch, '', True
1350 1347 else:
1351 1348 _branch_name, _sha_commit_id, is_head = \
1352 1349 self._is_valid_head(commit_id, self.rhodecode_vcs_repo,
1353 1350 landing_ref=self.db_repo.landing_ref_name)
1354 1351
1355 1352 self.forbid_non_head(is_head, f_path, commit_id=commit_id)
1356 1353 self.check_branch_permission(_branch_name, commit_id=commit_id)
1357 1354
1358 1355 c.default_message = (_('Added file via RhodeCode Enterprise'))
1359 1356 c.f_path = f_path.lstrip('/') # ensure not relative path
1360 1357
1361 1358 return self._get_template_context(c)
1362 1359
1363 1360 @LoginRequired()
1364 1361 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1365 1362 @CSRFRequired()
1366 1363 def repo_files_create_file(self):
1367 1364 _ = self.request.translate
1368 1365 c = self.load_default_context()
1369 1366 commit_id, f_path = self._get_commit_and_path()
1370 1367
1371 1368 self._ensure_not_locked()
1372 1369
1373 1370 c.commit = self._get_commit_or_redirect(commit_id, redirect_after=False)
1374 1371 if c.commit is None:
1375 1372 c.commit = EmptyCommit(alias=self.rhodecode_vcs_repo.alias)
1376 1373
1377 1374 # calculate redirect URL
1378 1375 if self.rhodecode_vcs_repo.is_empty():
1379 1376 default_redirect_url = h.route_path(
1380 1377 'repo_summary', repo_name=self.db_repo_name)
1381 1378 else:
1382 1379 default_redirect_url = h.route_path(
1383 1380 'repo_commit', repo_name=self.db_repo_name, commit_id='tip')
1384 1381
1385 1382 if self.rhodecode_vcs_repo.is_empty():
1386 1383 # for empty repository we cannot check for current branch, we rely on
1387 1384 # c.commit.branch instead
1388 1385 _branch_name, _sha_commit_id, is_head = c.commit.branch, '', True
1389 1386 else:
1390 1387 _branch_name, _sha_commit_id, is_head = \
1391 1388 self._is_valid_head(commit_id, self.rhodecode_vcs_repo,
1392 1389 landing_ref=self.db_repo.landing_ref_name)
1393 1390
1394 1391 self.forbid_non_head(is_head, f_path, commit_id=commit_id)
1395 1392 self.check_branch_permission(_branch_name, commit_id=commit_id)
1396 1393
1397 1394 c.default_message = (_('Added file via RhodeCode Enterprise'))
1398 1395 c.f_path = f_path
1399 1396
1400 1397 r_post = self.request.POST
1401 1398 message = r_post.get('message') or c.default_message
1402 1399 filename = r_post.get('filename')
1403 1400 unix_mode = 0
1404 1401
1405 1402 if not filename:
1406 1403 # If there's no commit, redirect to repo summary
1407 1404 if type(c.commit) is EmptyCommit:
1408 1405 redirect_url = h.route_path(
1409 1406 'repo_summary', repo_name=self.db_repo_name)
1410 1407 else:
1411 1408 redirect_url = default_redirect_url
1412 1409 h.flash(_('No filename specified'), category='warning')
1413 1410 raise HTTPFound(redirect_url)
1414 1411
1415 1412 root_path = f_path
1416 1413 pure_path = self.create_pure_path(root_path, filename)
1417 1414 node_path = pure_path.as_posix().lstrip('/')
1418 1415
1419 1416 author = self._rhodecode_db_user.full_contact
1420 1417 content = convert_line_endings(r_post.get('content', ''), unix_mode)
1421 1418 nodes = {
1422 1419 safe_bytes(node_path): {
1423 1420 'content': safe_bytes(content)
1424 1421 }
1425 1422 }
1426 1423
1427 1424 try:
1428 1425
1429 1426 commit = ScmModel().create_nodes(
1430 1427 user=self._rhodecode_db_user.user_id,
1431 1428 repo=self.db_repo,
1432 1429 message=message,
1433 1430 nodes=nodes,
1434 1431 parent_commit=c.commit,
1435 1432 author=author,
1436 1433 )
1437 1434
1438 1435 h.flash(_('Successfully committed new file `{}`').format(
1439 1436 h.escape(node_path)), category='success')
1440 1437
1441 1438 default_redirect_url = h.route_path(
1442 1439 'repo_commit', repo_name=self.db_repo_name, commit_id=commit.raw_id)
1443 1440
1444 1441 except NonRelativePathError:
1445 1442 log.exception('Non Relative path found')
1446 1443 h.flash(_('The location specified must be a relative path and must not '
1447 1444 'contain .. in the path'), category='warning')
1448 1445 raise HTTPFound(default_redirect_url)
1449 1446 except (NodeError, NodeAlreadyExistsError) as e:
1450 1447 h.flash(h.escape(safe_str(e)), category='error')
1451 1448 except Exception:
1452 1449 log.exception('Error occurred during commit')
1453 1450 h.flash(_('Error occurred during commit'), category='error')
1454 1451
1455 1452 raise HTTPFound(default_redirect_url)
1456 1453
1457 1454 @LoginRequired()
1458 1455 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1459 1456 @CSRFRequired()
1460 1457 def repo_files_upload_file(self):
1461 1458 _ = self.request.translate
1462 1459 c = self.load_default_context()
1463 1460 commit_id, f_path = self._get_commit_and_path()
1464 1461
1465 1462 self._ensure_not_locked()
1466 1463
1467 1464 c.commit = self._get_commit_or_redirect(commit_id, redirect_after=False)
1468 1465 if c.commit is None:
1469 1466 c.commit = EmptyCommit(alias=self.rhodecode_vcs_repo.alias)
1470 1467
1471 1468 # calculate redirect URL
1472 1469 if self.rhodecode_vcs_repo.is_empty():
1473 1470 default_redirect_url = h.route_path(
1474 1471 'repo_summary', repo_name=self.db_repo_name)
1475 1472 else:
1476 1473 default_redirect_url = h.route_path(
1477 1474 'repo_commit', repo_name=self.db_repo_name, commit_id='tip')
1478 1475
1479 1476 if self.rhodecode_vcs_repo.is_empty():
1480 1477 # for empty repository we cannot check for current branch, we rely on
1481 1478 # c.commit.branch instead
1482 1479 _branch_name, _sha_commit_id, is_head = c.commit.branch, '', True
1483 1480 else:
1484 1481 _branch_name, _sha_commit_id, is_head = \
1485 1482 self._is_valid_head(commit_id, self.rhodecode_vcs_repo,
1486 1483 landing_ref=self.db_repo.landing_ref_name)
1487 1484
1488 1485 error = self.forbid_non_head(is_head, f_path, json_mode=True)
1489 1486 if error:
1490 1487 return {
1491 1488 'error': error,
1492 1489 'redirect_url': default_redirect_url
1493 1490 }
1494 1491 error = self.check_branch_permission(_branch_name, json_mode=True)
1495 1492 if error:
1496 1493 return {
1497 1494 'error': error,
1498 1495 'redirect_url': default_redirect_url
1499 1496 }
1500 1497
1501 1498 c.default_message = (_('Uploaded file via RhodeCode Enterprise'))
1502 1499 c.f_path = f_path
1503 1500
1504 1501 r_post = self.request.POST
1505 1502
1506 1503 message = c.default_message
1507 1504 user_message = r_post.getall('message')
1508 1505 if isinstance(user_message, list) and user_message:
1509 1506 # we take the first from duplicated results if it's not empty
1510 1507 message = user_message[0] if user_message[0] else message
1511 1508
1512 1509 nodes = {}
1513 1510
1514 1511 for file_obj in r_post.getall('files_upload') or []:
1515 1512 content = file_obj.file
1516 1513 filename = file_obj.filename
1517 1514
1518 1515 root_path = f_path
1519 1516 pure_path = self.create_pure_path(root_path, filename)
1520 1517 node_path = pure_path.as_posix().lstrip('/')
1521 1518
1522 1519 nodes[safe_bytes(node_path)] = {
1523 1520 'content': content
1524 1521 }
1525 1522
1526 1523 if not nodes:
1527 1524 error = 'missing files'
1528 1525 return {
1529 1526 'error': error,
1530 1527 'redirect_url': default_redirect_url
1531 1528 }
1532 1529
1533 1530 author = self._rhodecode_db_user.full_contact
1534 1531
1535 1532 try:
1536 1533 commit = ScmModel().create_nodes(
1537 1534 user=self._rhodecode_db_user.user_id,
1538 1535 repo=self.db_repo,
1539 1536 message=message,
1540 1537 nodes=nodes,
1541 1538 parent_commit=c.commit,
1542 1539 author=author,
1543 1540 )
1544 1541 if len(nodes) == 1:
1545 1542 flash_message = _('Successfully committed {} new files').format(len(nodes))
1546 1543 else:
1547 1544 flash_message = _('Successfully committed 1 new file')
1548 1545
1549 1546 h.flash(flash_message, category='success')
1550 1547
1551 1548 default_redirect_url = h.route_path(
1552 1549 'repo_commit', repo_name=self.db_repo_name, commit_id=commit.raw_id)
1553 1550
1554 1551 except NonRelativePathError:
1555 1552 log.exception('Non Relative path found')
1556 1553 error = _('The location specified must be a relative path and must not '
1557 1554 'contain .. in the path')
1558 1555 h.flash(error, category='warning')
1559 1556
1560 1557 return {
1561 1558 'error': error,
1562 1559 'redirect_url': default_redirect_url
1563 1560 }
1564 1561 except (NodeError, NodeAlreadyExistsError) as e:
1565 1562 error = h.escape(e)
1566 1563 h.flash(error, category='error')
1567 1564
1568 1565 return {
1569 1566 'error': error,
1570 1567 'redirect_url': default_redirect_url
1571 1568 }
1572 1569 except Exception:
1573 1570 log.exception('Error occurred during commit')
1574 1571 error = _('Error occurred during commit')
1575 1572 h.flash(error, category='error')
1576 1573 return {
1577 1574 'error': error,
1578 1575 'redirect_url': default_redirect_url
1579 1576 }
1580 1577
1581 1578 return {
1582 1579 'error': None,
1583 1580 'redirect_url': default_redirect_url
1584 1581 }
General Comments 0
You need to be logged in to leave comments. Login now