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