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