##// END OF EJS Templates
fix(app): Adjusted accordingly to PR suggestions. Fixes: RCCE-37
ilin.s -
r5270:53acf5d4 default
parent child Browse files
Show More
@@ -1,1586 +1,1586 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 424 if archive_name_key in d_cache and not archive_cache_disable:
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 return headers.encode().decode('iso-8859-1', errors='replace')
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 c.commit = self._get_commit_or_redirect(commit_id, redirect_after=False)
1345 1345 if c.commit is None:
1346 1346 c.commit = EmptyCommit(alias=self.rhodecode_vcs_repo.alias)
1347 1347
1348 1348 if self.rhodecode_vcs_repo.is_empty():
1349 1349 # for empty repository we cannot check for current branch, we rely on
1350 1350 # c.commit.branch instead
1351 1351 _branch_name, _sha_commit_id, is_head = c.commit.branch, '', True
1352 1352 else:
1353 1353 _branch_name, _sha_commit_id, is_head = \
1354 1354 self._is_valid_head(commit_id, self.rhodecode_vcs_repo,
1355 1355 landing_ref=self.db_repo.landing_ref_name)
1356 1356
1357 1357 self.forbid_non_head(is_head, f_path, commit_id=commit_id)
1358 1358 self.check_branch_permission(_branch_name, commit_id=commit_id)
1359 1359
1360 1360 c.default_message = (_('Added file via RhodeCode Enterprise'))
1361 1361 c.f_path = f_path.lstrip('/') # ensure not relative path
1362 1362
1363 1363 return self._get_template_context(c)
1364 1364
1365 1365 @LoginRequired()
1366 1366 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1367 1367 @CSRFRequired()
1368 1368 def repo_files_create_file(self):
1369 1369 _ = self.request.translate
1370 1370 c = self.load_default_context()
1371 1371 commit_id, f_path = self._get_commit_and_path()
1372 1372
1373 1373 self._ensure_not_locked()
1374 1374
1375 1375 c.commit = self._get_commit_or_redirect(commit_id, redirect_after=False)
1376 1376 if c.commit is None:
1377 1377 c.commit = EmptyCommit(alias=self.rhodecode_vcs_repo.alias)
1378 1378
1379 1379 # calculate redirect URL
1380 1380 if self.rhodecode_vcs_repo.is_empty():
1381 1381 default_redirect_url = h.route_path(
1382 1382 'repo_summary', repo_name=self.db_repo_name)
1383 1383 else:
1384 1384 default_redirect_url = h.route_path(
1385 1385 'repo_commit', repo_name=self.db_repo_name, commit_id='tip')
1386 1386
1387 1387 if self.rhodecode_vcs_repo.is_empty():
1388 1388 # for empty repository we cannot check for current branch, we rely on
1389 1389 # c.commit.branch instead
1390 1390 _branch_name, _sha_commit_id, is_head = c.commit.branch, '', True
1391 1391 else:
1392 1392 _branch_name, _sha_commit_id, is_head = \
1393 1393 self._is_valid_head(commit_id, self.rhodecode_vcs_repo,
1394 1394 landing_ref=self.db_repo.landing_ref_name)
1395 1395
1396 1396 self.forbid_non_head(is_head, f_path, commit_id=commit_id)
1397 1397 self.check_branch_permission(_branch_name, commit_id=commit_id)
1398 1398
1399 1399 c.default_message = (_('Added file via RhodeCode Enterprise'))
1400 1400 c.f_path = f_path
1401 1401
1402 1402 r_post = self.request.POST
1403 1403 message = r_post.get('message') or c.default_message
1404 1404 filename = r_post.get('filename')
1405 1405 unix_mode = 0
1406 1406
1407 1407 if not filename:
1408 1408 # If there's no commit, redirect to repo summary
1409 1409 if type(c.commit) is EmptyCommit:
1410 1410 redirect_url = h.route_path(
1411 1411 'repo_summary', repo_name=self.db_repo_name)
1412 1412 else:
1413 1413 redirect_url = default_redirect_url
1414 1414 h.flash(_('No filename specified'), category='warning')
1415 1415 raise HTTPFound(redirect_url)
1416 1416
1417 1417 root_path = f_path
1418 1418 pure_path = self.create_pure_path(root_path, filename)
1419 1419 node_path = pure_path.as_posix().lstrip('/')
1420 1420
1421 1421 author = self._rhodecode_db_user.full_contact
1422 1422 content = convert_line_endings(r_post.get('content', ''), unix_mode)
1423 1423 nodes = {
1424 1424 safe_bytes(node_path): {
1425 1425 'content': safe_bytes(content)
1426 1426 }
1427 1427 }
1428 1428
1429 1429 try:
1430 1430
1431 1431 commit = ScmModel().create_nodes(
1432 1432 user=self._rhodecode_db_user.user_id,
1433 1433 repo=self.db_repo,
1434 1434 message=message,
1435 1435 nodes=nodes,
1436 1436 parent_commit=c.commit,
1437 1437 author=author,
1438 1438 )
1439 1439
1440 1440 h.flash(_('Successfully committed new file `{}`').format(
1441 1441 h.escape(node_path)), category='success')
1442 1442
1443 1443 default_redirect_url = h.route_path(
1444 1444 'repo_commit', repo_name=self.db_repo_name, commit_id=commit.raw_id)
1445 1445
1446 1446 except NonRelativePathError:
1447 1447 log.exception('Non Relative path found')
1448 1448 h.flash(_('The location specified must be a relative path and must not '
1449 1449 'contain .. in the path'), category='warning')
1450 1450 raise HTTPFound(default_redirect_url)
1451 1451 except (NodeError, NodeAlreadyExistsError) as e:
1452 1452 h.flash(h.escape(safe_str(e)), category='error')
1453 1453 except Exception:
1454 1454 log.exception('Error occurred during commit')
1455 1455 h.flash(_('Error occurred during commit'), category='error')
1456 1456
1457 1457 raise HTTPFound(default_redirect_url)
1458 1458
1459 1459 @LoginRequired()
1460 1460 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1461 1461 @CSRFRequired()
1462 1462 def repo_files_upload_file(self):
1463 1463 _ = self.request.translate
1464 1464 c = self.load_default_context()
1465 1465 commit_id, f_path = self._get_commit_and_path()
1466 1466
1467 1467 self._ensure_not_locked()
1468 1468
1469 1469 c.commit = self._get_commit_or_redirect(commit_id, redirect_after=False)
1470 1470 if c.commit is None:
1471 1471 c.commit = EmptyCommit(alias=self.rhodecode_vcs_repo.alias)
1472 1472
1473 1473 # calculate redirect URL
1474 1474 if self.rhodecode_vcs_repo.is_empty():
1475 1475 default_redirect_url = h.route_path(
1476 1476 'repo_summary', repo_name=self.db_repo_name)
1477 1477 else:
1478 1478 default_redirect_url = h.route_path(
1479 1479 'repo_commit', repo_name=self.db_repo_name, commit_id='tip')
1480 1480
1481 1481 if self.rhodecode_vcs_repo.is_empty():
1482 1482 # for empty repository we cannot check for current branch, we rely on
1483 1483 # c.commit.branch instead
1484 1484 _branch_name, _sha_commit_id, is_head = c.commit.branch, '', True
1485 1485 else:
1486 1486 _branch_name, _sha_commit_id, is_head = \
1487 1487 self._is_valid_head(commit_id, self.rhodecode_vcs_repo,
1488 1488 landing_ref=self.db_repo.landing_ref_name)
1489 1489
1490 1490 error = self.forbid_non_head(is_head, f_path, json_mode=True)
1491 1491 if error:
1492 1492 return {
1493 1493 'error': error,
1494 1494 'redirect_url': default_redirect_url
1495 1495 }
1496 1496 error = self.check_branch_permission(_branch_name, json_mode=True)
1497 1497 if error:
1498 1498 return {
1499 1499 'error': error,
1500 1500 'redirect_url': default_redirect_url
1501 1501 }
1502 1502
1503 1503 c.default_message = (_('Uploaded file via RhodeCode Enterprise'))
1504 1504 c.f_path = f_path
1505 1505
1506 1506 r_post = self.request.POST
1507 1507
1508 1508 message = c.default_message
1509 1509 user_message = r_post.getall('message')
1510 1510 if isinstance(user_message, list) and user_message:
1511 1511 # we take the first from duplicated results if it's not empty
1512 1512 message = user_message[0] if user_message[0] else message
1513 1513
1514 1514 nodes = {}
1515 1515
1516 1516 for file_obj in r_post.getall('files_upload') or []:
1517 1517 content = file_obj.file
1518 1518 filename = file_obj.filename
1519 1519
1520 1520 root_path = f_path
1521 1521 pure_path = self.create_pure_path(root_path, filename)
1522 1522 node_path = pure_path.as_posix().lstrip('/')
1523 1523
1524 1524 nodes[safe_bytes(node_path)] = {
1525 1525 'content': content
1526 1526 }
1527 1527
1528 1528 if not nodes:
1529 1529 error = 'missing files'
1530 1530 return {
1531 1531 'error': error,
1532 1532 'redirect_url': default_redirect_url
1533 1533 }
1534 1534
1535 1535 author = self._rhodecode_db_user.full_contact
1536 1536
1537 1537 try:
1538 1538 commit = ScmModel().create_nodes(
1539 1539 user=self._rhodecode_db_user.user_id,
1540 1540 repo=self.db_repo,
1541 1541 message=message,
1542 1542 nodes=nodes,
1543 1543 parent_commit=c.commit,
1544 1544 author=author,
1545 1545 )
1546 1546 if len(nodes) == 1:
1547 1547 flash_message = _('Successfully committed {} new files').format(len(nodes))
1548 1548 else:
1549 1549 flash_message = _('Successfully committed 1 new file')
1550 1550
1551 1551 h.flash(flash_message, category='success')
1552 1552
1553 1553 default_redirect_url = h.route_path(
1554 1554 'repo_commit', repo_name=self.db_repo_name, commit_id=commit.raw_id)
1555 1555
1556 1556 except NonRelativePathError:
1557 1557 log.exception('Non Relative path found')
1558 1558 error = _('The location specified must be a relative path and must not '
1559 1559 'contain .. in the path')
1560 1560 h.flash(error, category='warning')
1561 1561
1562 1562 return {
1563 1563 'error': error,
1564 1564 'redirect_url': default_redirect_url
1565 1565 }
1566 1566 except (NodeError, NodeAlreadyExistsError) as e:
1567 1567 error = h.escape(e)
1568 1568 h.flash(error, category='error')
1569 1569
1570 1570 return {
1571 1571 'error': error,
1572 1572 'redirect_url': default_redirect_url
1573 1573 }
1574 1574 except Exception:
1575 1575 log.exception('Error occurred during commit')
1576 1576 error = _('Error occurred during commit')
1577 1577 h.flash(error, category='error')
1578 1578 return {
1579 1579 'error': error,
1580 1580 'redirect_url': default_redirect_url
1581 1581 }
1582 1582
1583 1583 return {
1584 1584 'error': None,
1585 1585 'redirect_url': default_redirect_url
1586 1586 }
General Comments 0
You need to be logged in to leave comments. Login now