##// END OF EJS Templates
files: ported repository files controllers to pyramid views.
marcink -
r1927:e6df2b71 default
parent child Browse files
Show More
This diff has been collapsed as it changes many lines, (1278 lines changed) Show them Hide them
@@ -0,0 +1,1278 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2011-2017 RhodeCode GmbH
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21 import itertools
22 import logging
23 import os
24 import shutil
25 import tempfile
26 import collections
27
28 from pyramid.httpexceptions import HTTPNotFound, HTTPBadRequest, HTTPFound
29 from pyramid.view import view_config
30 from pyramid.renderers import render
31 from pyramid.response import Response
32
33 from rhodecode.apps._base import RepoAppView
34
35 from rhodecode.controllers.utils import parse_path_ref
36 from rhodecode.lib import diffs, helpers as h, caches
37 from rhodecode.lib import audit_logger
38 from rhodecode.lib.exceptions import NonRelativePathError
39 from rhodecode.lib.codeblocks import (
40 filenode_as_lines_tokens, filenode_as_annotated_lines_tokens)
41 from rhodecode.lib.utils2 import (
42 convert_line_endings, detect_mode, safe_str, str2bool)
43 from rhodecode.lib.auth import (
44 LoginRequired, HasRepoPermissionAnyDecorator, CSRFRequired)
45 from rhodecode.lib.vcs import path as vcspath
46 from rhodecode.lib.vcs.backends.base import EmptyCommit
47 from rhodecode.lib.vcs.conf import settings
48 from rhodecode.lib.vcs.nodes import FileNode
49 from rhodecode.lib.vcs.exceptions import (
50 RepositoryError, CommitDoesNotExistError, EmptyRepositoryError,
51 ImproperArchiveTypeError, VCSError, NodeAlreadyExistsError,
52 NodeDoesNotExistError, CommitError, NodeError)
53
54 from rhodecode.model.scm import ScmModel
55 from rhodecode.model.db import Repository
56
57 log = logging.getLogger(__name__)
58
59
60 class RepoFilesView(RepoAppView):
61
62 @staticmethod
63 def adjust_file_path_for_svn(f_path, repo):
64 """
65 Computes the relative path of `f_path`.
66
67 This is mainly based on prefix matching of the recognized tags and
68 branches in the underlying repository.
69 """
70 tags_and_branches = itertools.chain(
71 repo.branches.iterkeys(),
72 repo.tags.iterkeys())
73 tags_and_branches = sorted(tags_and_branches, key=len, reverse=True)
74
75 for name in tags_and_branches:
76 if f_path.startswith('{}/'.format(name)):
77 f_path = vcspath.relpath(f_path, name)
78 break
79 return f_path
80
81 def load_default_context(self):
82 c = self._get_local_tmpl_context(include_app_defaults=True)
83
84 # TODO(marcink): remove repo_info and use c.rhodecode_db_repo instead
85 c.repo_info = self.db_repo
86 c.rhodecode_repo = self.rhodecode_vcs_repo
87
88 self._register_global_c(c)
89 return c
90
91 def _ensure_not_locked(self):
92 _ = self.request.translate
93
94 repo = self.db_repo
95 if repo.enable_locking and repo.locked[0]:
96 h.flash(_('This repository has been locked by %s on %s')
97 % (h.person_by_id(repo.locked[0]),
98 h.format_date(h.time_to_datetime(repo.locked[1]))),
99 'warning')
100 files_url = h.route_path(
101 'repo_files:default_path',
102 repo_name=self.db_repo_name, commit_id='tip')
103 raise HTTPFound(files_url)
104
105 def _get_commit_and_path(self):
106 default_commit_id = self.db_repo.landing_rev[1]
107 default_f_path = '/'
108
109 commit_id = self.request.matchdict.get(
110 'commit_id', default_commit_id)
111 f_path = self.request.matchdict.get('f_path', default_f_path)
112 return commit_id, f_path
113
114 def _get_default_encoding(self, c):
115 enc_list = getattr(c, 'default_encodings', [])
116 return enc_list[0] if enc_list else 'UTF-8'
117
118 def _get_commit_or_redirect(self, commit_id, redirect_after=True):
119 """
120 This is a safe way to get commit. If an error occurs it redirects to
121 tip with proper message
122
123 :param commit_id: id of commit to fetch
124 :param redirect_after: toggle redirection
125 """
126 _ = self.request.translate
127
128 try:
129 return self.rhodecode_vcs_repo.get_commit(commit_id)
130 except EmptyRepositoryError:
131 if not redirect_after:
132 return None
133
134 _url = h.route_path(
135 'repo_files_add_file',
136 repo_name=self.db_repo_name, commit_id=0, f_path='',
137 _anchor='edit')
138
139 if h.HasRepoPermissionAny(
140 'repository.write', 'repository.admin')(self.db_repo_name):
141 add_new = h.link_to(
142 _('Click here to add a new file.'), _url, class_="alert-link")
143 else:
144 add_new = ""
145
146 h.flash(h.literal(
147 _('There are no files yet. %s') % add_new), category='warning')
148 raise HTTPFound(
149 h.route_path('repo_summary', repo_name=self.db_repo_name))
150
151 except (CommitDoesNotExistError, LookupError):
152 msg = _('No such commit exists for this repository')
153 h.flash(msg, category='error')
154 raise HTTPNotFound()
155 except RepositoryError as e:
156 h.flash(safe_str(h.escape(e)), category='error')
157 raise HTTPNotFound()
158
159 def _get_filenode_or_redirect(self, commit_obj, path):
160 """
161 Returns file_node, if error occurs or given path is directory,
162 it'll redirect to top level path
163 """
164 _ = self.request.translate
165
166 try:
167 file_node = commit_obj.get_node(path)
168 if file_node.is_dir():
169 raise RepositoryError('The given path is a directory')
170 except CommitDoesNotExistError:
171 log.exception('No such commit exists for this repository')
172 h.flash(_('No such commit exists for this repository'), category='error')
173 raise HTTPNotFound()
174 except RepositoryError as e:
175 log.warning('Repository error while fetching '
176 'filenode `%s`. Err:%s', path, e)
177 h.flash(safe_str(h.escape(e)), category='error')
178 raise HTTPNotFound()
179
180 return file_node
181
182 def _is_valid_head(self, commit_id, repo):
183 # check if commit is a branch identifier- basically we cannot
184 # create multiple heads via file editing
185 valid_heads = repo.branches.keys() + repo.branches.values()
186
187 if h.is_svn(repo) and not repo.is_empty():
188 # Note: Subversion only has one head, we add it here in case there
189 # is no branch matched.
190 valid_heads.append(repo.get_commit(commit_idx=-1).raw_id)
191
192 # check if commit is a branch name or branch hash
193 return commit_id in valid_heads
194
195 def _get_tree_cache_manager(self, namespace_type):
196 _namespace = caches.get_repo_namespace_key(
197 namespace_type, self.db_repo_name)
198 return caches.get_cache_manager('repo_cache_long', _namespace)
199
200 def _get_tree_at_commit(
201 self, c, commit_id, f_path, full_load=False, force=False):
202 def _cached_tree():
203 log.debug('Generating cached file tree for %s, %s, %s',
204 self.db_repo_name, commit_id, f_path)
205
206 c.full_load = full_load
207 return render(
208 'rhodecode:templates/files/files_browser_tree.mako',
209 self._get_template_context(c), self.request)
210
211 cache_manager = self._get_tree_cache_manager(caches.FILE_TREE)
212
213 cache_key = caches.compute_key_from_params(
214 self.db_repo_name, commit_id, f_path)
215
216 if force:
217 # we want to force recompute of caches
218 cache_manager.remove_value(cache_key)
219
220 return cache_manager.get(cache_key, createfunc=_cached_tree)
221
222 def _get_archive_spec(self, fname):
223 log.debug('Detecting archive spec for: `%s`', fname)
224
225 fileformat = None
226 ext = None
227 content_type = None
228 for a_type, ext_data in settings.ARCHIVE_SPECS.items():
229 content_type, extension = ext_data
230
231 if fname.endswith(extension):
232 fileformat = a_type
233 log.debug('archive is of type: %s', fileformat)
234 ext = extension
235 break
236
237 if not fileformat:
238 raise ValueError()
239
240 # left over part of whole fname is the commit
241 commit_id = fname[:-len(ext)]
242
243 return commit_id, ext, fileformat, content_type
244
245 @LoginRequired()
246 @HasRepoPermissionAnyDecorator(
247 'repository.read', 'repository.write', 'repository.admin')
248 @view_config(
249 route_name='repo_archivefile', request_method='GET',
250 renderer=None)
251 def repo_archivefile(self):
252 # archive cache config
253 from rhodecode import CONFIG
254 _ = self.request.translate
255 self.load_default_context()
256
257 fname = self.request.matchdict['fname']
258 subrepos = self.request.GET.get('subrepos') == 'true'
259
260 if not self.db_repo.enable_downloads:
261 return Response(_('Downloads disabled'))
262
263 try:
264 commit_id, ext, fileformat, content_type = \
265 self._get_archive_spec(fname)
266 except ValueError:
267 return Response(_('Unknown archive type for: `{}`').format(fname))
268
269 try:
270 commit = self.rhodecode_vcs_repo.get_commit(commit_id)
271 except CommitDoesNotExistError:
272 return Response(_('Unknown commit_id %s') % commit_id)
273 except EmptyRepositoryError:
274 return Response(_('Empty repository'))
275
276 archive_name = '%s-%s%s%s' % (
277 safe_str(self.db_repo_name.replace('/', '_')),
278 '-sub' if subrepos else '',
279 safe_str(commit.short_id), ext)
280
281 use_cached_archive = False
282 archive_cache_enabled = CONFIG.get(
283 'archive_cache_dir') and not self.request.GET.get('no_cache')
284
285 if archive_cache_enabled:
286 # check if we it's ok to write
287 if not os.path.isdir(CONFIG['archive_cache_dir']):
288 os.makedirs(CONFIG['archive_cache_dir'])
289 cached_archive_path = os.path.join(
290 CONFIG['archive_cache_dir'], archive_name)
291 if os.path.isfile(cached_archive_path):
292 log.debug('Found cached archive in %s', cached_archive_path)
293 fd, archive = None, cached_archive_path
294 use_cached_archive = True
295 else:
296 log.debug('Archive %s is not yet cached', archive_name)
297
298 if not use_cached_archive:
299 # generate new archive
300 fd, archive = tempfile.mkstemp()
301 log.debug('Creating new temp archive in %s', archive)
302 try:
303 commit.archive_repo(archive, kind=fileformat, subrepos=subrepos)
304 except ImproperArchiveTypeError:
305 return _('Unknown archive type')
306 if archive_cache_enabled:
307 # if we generated the archive and we have cache enabled
308 # let's use this for future
309 log.debug('Storing new archive in %s', cached_archive_path)
310 shutil.move(archive, cached_archive_path)
311 archive = cached_archive_path
312
313 # store download action
314 audit_logger.store_web(
315 'repo.archive.download', action_data={
316 'user_agent': self.request.user_agent,
317 'archive_name': archive_name,
318 'archive_spec': fname,
319 'archive_cached': use_cached_archive},
320 user=self._rhodecode_user,
321 repo=self.db_repo,
322 commit=True
323 )
324
325 def get_chunked_archive(archive):
326 with open(archive, 'rb') as stream:
327 while True:
328 data = stream.read(16 * 1024)
329 if not data:
330 if fd: # fd means we used temporary file
331 os.close(fd)
332 if not archive_cache_enabled:
333 log.debug('Destroying temp archive %s', archive)
334 os.remove(archive)
335 break
336 yield data
337
338 response = Response(app_iter=get_chunked_archive(archive))
339 response.content_disposition = str(
340 'attachment; filename=%s' % archive_name)
341 response.content_type = str(content_type)
342
343 return response
344
345 def _get_file_node(self, commit_id, f_path):
346 if commit_id not in ['', None, 'None', '0' * 12, '0' * 40]:
347 commit = self.rhodecode_vcs_repo.get_commit(commit_id=commit_id)
348 try:
349 node = commit.get_node(f_path)
350 if node.is_dir():
351 raise NodeError('%s path is a %s not a file'
352 % (node, type(node)))
353 except NodeDoesNotExistError:
354 commit = EmptyCommit(
355 commit_id=commit_id,
356 idx=commit.idx,
357 repo=commit.repository,
358 alias=commit.repository.alias,
359 message=commit.message,
360 author=commit.author,
361 date=commit.date)
362 node = FileNode(f_path, '', commit=commit)
363 else:
364 commit = EmptyCommit(
365 repo=self.rhodecode_vcs_repo,
366 alias=self.rhodecode_vcs_repo.alias)
367 node = FileNode(f_path, '', commit=commit)
368 return node
369
370 @LoginRequired()
371 @HasRepoPermissionAnyDecorator(
372 'repository.read', 'repository.write', 'repository.admin')
373 @view_config(
374 route_name='repo_files_diff', request_method='GET',
375 renderer=None)
376 def repo_files_diff(self):
377 c = self.load_default_context()
378 diff1 = self.request.GET.get('diff1', '')
379 diff2 = self.request.GET.get('diff2', '')
380 f_path = self.request.matchdict['f_path']
381
382 path1, diff1 = parse_path_ref(diff1, default_path=f_path)
383
384 ignore_whitespace = str2bool(self.request.GET.get('ignorews'))
385 line_context = self.request.GET.get('context', 3)
386
387 if not any((diff1, diff2)):
388 h.flash(
389 'Need query parameter "diff1" or "diff2" to generate a diff.',
390 category='error')
391 raise HTTPBadRequest()
392
393 c.action = self.request.GET.get('diff')
394 if c.action not in ['download', 'raw']:
395 compare_url = h.url(
396 'compare_url', repo_name=self.db_repo_name,
397 source_ref_type='rev',
398 source_ref=diff1,
399 target_repo=self.db_repo_name,
400 target_ref_type='rev',
401 target_ref=diff2,
402 f_path=f_path)
403 # redirect to new view if we render diff
404 raise HTTPFound(compare_url)
405
406 try:
407 node1 = self._get_file_node(diff1, path1)
408 node2 = self._get_file_node(diff2, f_path)
409 except (RepositoryError, NodeError):
410 log.exception("Exception while trying to get node from repository")
411 raise HTTPFound(
412 h.route_path('repo_files', repo_name=self.db_repo_name,
413 commit_id='tip', f_path=f_path))
414
415 if all(isinstance(node.commit, EmptyCommit)
416 for node in (node1, node2)):
417 raise HTTPNotFound()
418
419 c.commit_1 = node1.commit
420 c.commit_2 = node2.commit
421
422 if c.action == 'download':
423 _diff = diffs.get_gitdiff(node1, node2,
424 ignore_whitespace=ignore_whitespace,
425 context=line_context)
426 diff = diffs.DiffProcessor(_diff, format='gitdiff')
427
428 response = Response(diff.as_raw())
429 response.content_type = 'text/plain'
430 response.content_disposition = (
431 'attachment; filename=%s_%s_vs_%s.diff' % (f_path, diff1, diff2)
432 )
433 charset = self._get_default_encoding(c)
434 if charset:
435 response.charset = charset
436 return response
437
438 elif c.action == 'raw':
439 _diff = diffs.get_gitdiff(node1, node2,
440 ignore_whitespace=ignore_whitespace,
441 context=line_context)
442 diff = diffs.DiffProcessor(_diff, format='gitdiff')
443
444 response = Response(diff.as_raw())
445 response.content_type = 'text/plain'
446 charset = self._get_default_encoding(c)
447 if charset:
448 response.charset = charset
449 return response
450
451 # in case we ever end up here
452 raise HTTPNotFound()
453
454 @LoginRequired()
455 @HasRepoPermissionAnyDecorator(
456 'repository.read', 'repository.write', 'repository.admin')
457 @view_config(
458 route_name='repo_files_diff_2way_redirect', request_method='GET',
459 renderer=None)
460 def repo_files_diff_2way_redirect(self):
461 """
462 Kept only to make OLD links work
463 """
464 diff1 = self.request.GET.get('diff1', '')
465 diff2 = self.request.GET.get('diff2', '')
466 f_path = self.request.matchdict['f_path']
467
468 if not any((diff1, diff2)):
469 h.flash(
470 'Need query parameter "diff1" or "diff2" to generate a diff.',
471 category='error')
472 raise HTTPBadRequest()
473
474 compare_url = h.url(
475 'compare_url', repo_name=self.db_repo_name,
476 source_ref_type='rev',
477 source_ref=diff1,
478 target_repo=self.db_repo_name,
479 target_ref_type='rev',
480 target_ref=diff2,
481 f_path=f_path,
482 diffmode='sideside')
483 raise HTTPFound(compare_url)
484
485 @LoginRequired()
486 @HasRepoPermissionAnyDecorator(
487 'repository.read', 'repository.write', 'repository.admin')
488 @view_config(
489 route_name='repo_files', request_method='GET',
490 renderer=None)
491 @view_config(
492 route_name='repo_files:default_path', request_method='GET',
493 renderer=None)
494 @view_config(
495 route_name='repo_files:default_commit', request_method='GET',
496 renderer=None)
497 @view_config(
498 route_name='repo_files:rendered', request_method='GET',
499 renderer=None)
500 @view_config(
501 route_name='repo_files:annotated', request_method='GET',
502 renderer=None)
503 def repo_files(self):
504 c = self.load_default_context()
505
506 view_name = getattr(self.request.matched_route, 'name', None)
507
508 c.annotate = view_name == 'repo_files:annotated'
509 # default is false, but .rst/.md files later are auto rendered, we can
510 # overwrite auto rendering by setting this GET flag
511 c.renderer = view_name == 'repo_files:rendered' or \
512 not self.request.GET.get('no-render', False)
513
514 # redirect to given commit_id from form if given
515 get_commit_id = self.request.GET.get('at_rev', None)
516 if get_commit_id:
517 self._get_commit_or_redirect(get_commit_id)
518
519 commit_id, f_path = self._get_commit_and_path()
520 c.commit = self._get_commit_or_redirect(commit_id)
521 c.branch = self.request.GET.get('branch', None)
522 c.f_path = f_path
523
524 # prev link
525 try:
526 prev_commit = c.commit.prev(c.branch)
527 c.prev_commit = prev_commit
528 c.url_prev = h.route_path(
529 'repo_files', repo_name=self.db_repo_name,
530 commit_id=prev_commit.raw_id, f_path=f_path)
531 if c.branch:
532 c.url_prev += '?branch=%s' % c.branch
533 except (CommitDoesNotExistError, VCSError):
534 c.url_prev = '#'
535 c.prev_commit = EmptyCommit()
536
537 # next link
538 try:
539 next_commit = c.commit.next(c.branch)
540 c.next_commit = next_commit
541 c.url_next = h.route_path(
542 'repo_files', repo_name=self.db_repo_name,
543 commit_id=next_commit.raw_id, f_path=f_path)
544 if c.branch:
545 c.url_next += '?branch=%s' % c.branch
546 except (CommitDoesNotExistError, VCSError):
547 c.url_next = '#'
548 c.next_commit = EmptyCommit()
549
550 # files or dirs
551 try:
552 c.file = c.commit.get_node(f_path)
553 c.file_author = True
554 c.file_tree = ''
555
556 # load file content
557 if c.file.is_file():
558 c.lf_node = c.file.get_largefile_node()
559
560 c.file_source_page = 'true'
561 c.file_last_commit = c.file.last_commit
562 if c.file.size < c.visual.cut_off_limit_diff:
563 if c.annotate: # annotation has precedence over renderer
564 c.annotated_lines = filenode_as_annotated_lines_tokens(
565 c.file
566 )
567 else:
568 c.renderer = (
569 c.renderer and h.renderer_from_filename(c.file.path)
570 )
571 if not c.renderer:
572 c.lines = filenode_as_lines_tokens(c.file)
573
574 c.on_branch_head = self._is_valid_head(
575 commit_id, self.rhodecode_vcs_repo)
576
577 branch = c.commit.branch if (
578 c.commit.branch and '/' not in c.commit.branch) else None
579 c.branch_or_raw_id = branch or c.commit.raw_id
580 c.branch_name = c.commit.branch or h.short_id(c.commit.raw_id)
581
582 author = c.file_last_commit.author
583 c.authors = [[
584 h.email(author),
585 h.person(author, 'username_or_name_or_email'),
586 1
587 ]]
588
589 else: # load tree content at path
590 c.file_source_page = 'false'
591 c.authors = []
592 # this loads a simple tree without metadata to speed things up
593 # later via ajax we call repo_nodetree_full and fetch whole
594 c.file_tree = self._get_tree_at_commit(
595 c, c.commit.raw_id, f_path)
596
597 except RepositoryError as e:
598 h.flash(safe_str(h.escape(e)), category='error')
599 raise HTTPNotFound()
600
601 if self.request.environ.get('HTTP_X_PJAX'):
602 html = render('rhodecode:templates/files/files_pjax.mako',
603 self._get_template_context(c), self.request)
604 else:
605 html = render('rhodecode:templates/files/files.mako',
606 self._get_template_context(c), self.request)
607 return Response(html)
608
609 @HasRepoPermissionAnyDecorator(
610 'repository.read', 'repository.write', 'repository.admin')
611 @view_config(
612 route_name='repo_files:annotated_previous', request_method='GET',
613 renderer=None)
614 def repo_files_annotated_previous(self):
615 self.load_default_context()
616
617 commit_id, f_path = self._get_commit_and_path()
618 commit = self._get_commit_or_redirect(commit_id)
619 prev_commit_id = commit.raw_id
620 line_anchor = self.request.GET.get('line_anchor')
621 is_file = False
622 try:
623 _file = commit.get_node(f_path)
624 is_file = _file.is_file()
625 except (NodeDoesNotExistError, CommitDoesNotExistError, VCSError):
626 pass
627
628 if is_file:
629 history = commit.get_file_history(f_path)
630 prev_commit_id = history[1].raw_id \
631 if len(history) > 1 else prev_commit_id
632 prev_url = h.route_path(
633 'repo_files:annotated', repo_name=self.db_repo_name,
634 commit_id=prev_commit_id, f_path=f_path,
635 _anchor='L{}'.format(line_anchor))
636
637 raise HTTPFound(prev_url)
638
639 @LoginRequired()
640 @HasRepoPermissionAnyDecorator(
641 'repository.read', 'repository.write', 'repository.admin')
642 @view_config(
643 route_name='repo_nodetree_full', request_method='GET',
644 renderer=None, xhr=True)
645 @view_config(
646 route_name='repo_nodetree_full:default_path', request_method='GET',
647 renderer=None, xhr=True)
648 def repo_nodetree_full(self):
649 """
650 Returns rendered html of file tree that contains commit date,
651 author, commit_id for the specified combination of
652 repo, commit_id and file path
653 """
654 c = self.load_default_context()
655
656 commit_id, f_path = self._get_commit_and_path()
657 commit = self._get_commit_or_redirect(commit_id)
658 try:
659 dir_node = commit.get_node(f_path)
660 except RepositoryError as e:
661 return Response('error: {}'.format(safe_str(e)))
662
663 if dir_node.is_file():
664 return Response('')
665
666 c.file = dir_node
667 c.commit = commit
668
669 # using force=True here, make a little trick. We flush the cache and
670 # compute it using the same key as without previous full_load, so now
671 # the fully loaded tree is now returned instead of partial,
672 # and we store this in caches
673 html = self._get_tree_at_commit(
674 c, commit.raw_id, dir_node.path, full_load=True, force=True)
675
676 return Response(html)
677
678 def _get_attachement_disposition(self, f_path):
679 return 'attachment; filename=%s' % \
680 safe_str(f_path.split(Repository.NAME_SEP)[-1])
681
682 @LoginRequired()
683 @HasRepoPermissionAnyDecorator(
684 'repository.read', 'repository.write', 'repository.admin')
685 @view_config(
686 route_name='repo_file_raw', request_method='GET',
687 renderer=None)
688 def repo_file_raw(self):
689 """
690 Action for show as raw, some mimetypes are "rendered",
691 those include images, icons.
692 """
693 c = self.load_default_context()
694
695 commit_id, f_path = self._get_commit_and_path()
696 commit = self._get_commit_or_redirect(commit_id)
697 file_node = self._get_filenode_or_redirect(commit, f_path)
698
699 raw_mimetype_mapping = {
700 # map original mimetype to a mimetype used for "show as raw"
701 # you can also provide a content-disposition to override the
702 # default "attachment" disposition.
703 # orig_type: (new_type, new_dispo)
704
705 # show images inline:
706 # Do not re-add SVG: it is unsafe and permits XSS attacks. One can
707 # for example render an SVG with javascript inside or even render
708 # HTML.
709 'image/x-icon': ('image/x-icon', 'inline'),
710 'image/png': ('image/png', 'inline'),
711 'image/gif': ('image/gif', 'inline'),
712 'image/jpeg': ('image/jpeg', 'inline'),
713 'application/pdf': ('application/pdf', 'inline'),
714 }
715
716 mimetype = file_node.mimetype
717 try:
718 mimetype, disposition = raw_mimetype_mapping[mimetype]
719 except KeyError:
720 # we don't know anything special about this, handle it safely
721 if file_node.is_binary:
722 # do same as download raw for binary files
723 mimetype, disposition = 'application/octet-stream', 'attachment'
724 else:
725 # do not just use the original mimetype, but force text/plain,
726 # otherwise it would serve text/html and that might be unsafe.
727 # Note: underlying vcs library fakes text/plain mimetype if the
728 # mimetype can not be determined and it thinks it is not
729 # binary.This might lead to erroneous text display in some
730 # cases, but helps in other cases, like with text files
731 # without extension.
732 mimetype, disposition = 'text/plain', 'inline'
733
734 if disposition == 'attachment':
735 disposition = self._get_attachement_disposition(f_path)
736
737 def stream_node():
738 yield file_node.raw_bytes
739
740 response = Response(app_iter=stream_node())
741 response.content_disposition = disposition
742 response.content_type = mimetype
743
744 charset = self._get_default_encoding(c)
745 if charset:
746 response.charset = charset
747
748 return response
749
750 @LoginRequired()
751 @HasRepoPermissionAnyDecorator(
752 'repository.read', 'repository.write', 'repository.admin')
753 @view_config(
754 route_name='repo_file_download', request_method='GET',
755 renderer=None)
756 @view_config(
757 route_name='repo_file_download:legacy', request_method='GET',
758 renderer=None)
759 def repo_file_download(self):
760 c = self.load_default_context()
761
762 commit_id, f_path = self._get_commit_and_path()
763 commit = self._get_commit_or_redirect(commit_id)
764 file_node = self._get_filenode_or_redirect(commit, f_path)
765
766 if self.request.GET.get('lf'):
767 # only if lf get flag is passed, we download this file
768 # as LFS/Largefile
769 lf_node = file_node.get_largefile_node()
770 if lf_node:
771 # overwrite our pointer with the REAL large-file
772 file_node = lf_node
773
774 disposition = self._get_attachement_disposition(f_path)
775
776 def stream_node():
777 yield file_node.raw_bytes
778
779 response = Response(app_iter=stream_node())
780 response.content_disposition = disposition
781 response.content_type = file_node.mimetype
782
783 charset = self._get_default_encoding(c)
784 if charset:
785 response.charset = charset
786
787 return response
788
789 def _get_nodelist_at_commit(self, repo_name, commit_id, f_path):
790 def _cached_nodes():
791 log.debug('Generating cached nodelist for %s, %s, %s',
792 repo_name, commit_id, f_path)
793 _d, _f = ScmModel().get_nodes(
794 repo_name, commit_id, f_path, flat=False)
795 return _d + _f
796
797 cache_manager = self._get_tree_cache_manager(caches.FILE_SEARCH_TREE_META)
798
799 cache_key = caches.compute_key_from_params(
800 repo_name, commit_id, f_path)
801 return cache_manager.get(cache_key, createfunc=_cached_nodes)
802
803 @LoginRequired()
804 @HasRepoPermissionAnyDecorator(
805 'repository.read', 'repository.write', 'repository.admin')
806 @view_config(
807 route_name='repo_files_nodelist', request_method='GET',
808 renderer='json_ext', xhr=True)
809 def repo_nodelist(self):
810 self.load_default_context()
811
812 commit_id, f_path = self._get_commit_and_path()
813 commit = self._get_commit_or_redirect(commit_id)
814
815 metadata = self._get_nodelist_at_commit(
816 self.db_repo_name, commit.raw_id, f_path)
817 return {'nodes': metadata}
818
819 def _create_references(
820 self, branches_or_tags, symbolic_reference, f_path):
821 items = []
822 for name, commit_id in branches_or_tags.items():
823 sym_ref = symbolic_reference(commit_id, name, f_path)
824 items.append((sym_ref, name))
825 return items
826
827 def _symbolic_reference(self, commit_id, name, f_path):
828 return commit_id
829
830 def _symbolic_reference_svn(self, commit_id, name, f_path):
831 new_f_path = vcspath.join(name, f_path)
832 return u'%s@%s' % (new_f_path, commit_id)
833
834 def _get_node_history(self, commit_obj, f_path, commits=None):
835 """
836 get commit history for given node
837
838 :param commit_obj: commit to calculate history
839 :param f_path: path for node to calculate history for
840 :param commits: if passed don't calculate history and take
841 commits defined in this list
842 """
843 _ = self.request.translate
844
845 # calculate history based on tip
846 tip = self.rhodecode_vcs_repo.get_commit()
847 if commits is None:
848 pre_load = ["author", "branch"]
849 try:
850 commits = tip.get_file_history(f_path, pre_load=pre_load)
851 except (NodeDoesNotExistError, CommitError):
852 # this node is not present at tip!
853 commits = commit_obj.get_file_history(f_path, pre_load=pre_load)
854
855 history = []
856 commits_group = ([], _("Changesets"))
857 for commit in commits:
858 branch = ' (%s)' % commit.branch if commit.branch else ''
859 n_desc = 'r%s:%s%s' % (commit.idx, commit.short_id, branch)
860 commits_group[0].append((commit.raw_id, n_desc,))
861 history.append(commits_group)
862
863 symbolic_reference = self._symbolic_reference
864
865 if self.rhodecode_vcs_repo.alias == 'svn':
866 adjusted_f_path = RepoFilesView.adjust_file_path_for_svn(
867 f_path, self.rhodecode_vcs_repo)
868 if adjusted_f_path != f_path:
869 log.debug(
870 'Recognized svn tag or branch in file "%s", using svn '
871 'specific symbolic references', f_path)
872 f_path = adjusted_f_path
873 symbolic_reference = self._symbolic_reference_svn
874
875 branches = self._create_references(
876 self.rhodecode_vcs_repo.branches, symbolic_reference, f_path)
877 branches_group = (branches, _("Branches"))
878
879 tags = self._create_references(
880 self.rhodecode_vcs_repo.tags, symbolic_reference, f_path)
881 tags_group = (tags, _("Tags"))
882
883 history.append(branches_group)
884 history.append(tags_group)
885
886 return history, commits
887
888 @LoginRequired()
889 @HasRepoPermissionAnyDecorator(
890 'repository.read', 'repository.write', 'repository.admin')
891 @view_config(
892 route_name='repo_file_history', request_method='GET',
893 renderer='json_ext')
894 def repo_file_history(self):
895 self.load_default_context()
896
897 commit_id, f_path = self._get_commit_and_path()
898 commit = self._get_commit_or_redirect(commit_id)
899 file_node = self._get_filenode_or_redirect(commit, f_path)
900
901 if file_node.is_file():
902 file_history, _hist = self._get_node_history(commit, f_path)
903
904 res = []
905 for obj in file_history:
906 res.append({
907 'text': obj[1],
908 'children': [{'id': o[0], 'text': o[1]} for o in obj[0]]
909 })
910
911 data = {
912 'more': False,
913 'results': res
914 }
915 return data
916
917 log.warning('Cannot fetch history for directory')
918 raise HTTPBadRequest()
919
920 @LoginRequired()
921 @HasRepoPermissionAnyDecorator(
922 'repository.read', 'repository.write', 'repository.admin')
923 @view_config(
924 route_name='repo_file_authors', request_method='GET',
925 renderer='rhodecode:templates/files/file_authors_box.mako')
926 def repo_file_authors(self):
927 c = self.load_default_context()
928
929 commit_id, f_path = self._get_commit_and_path()
930 commit = self._get_commit_or_redirect(commit_id)
931 file_node = self._get_filenode_or_redirect(commit, f_path)
932
933 if not file_node.is_file():
934 raise HTTPBadRequest()
935
936 c.file_last_commit = file_node.last_commit
937 if self.request.GET.get('annotate') == '1':
938 # use _hist from annotation if annotation mode is on
939 commit_ids = set(x[1] for x in file_node.annotate)
940 _hist = (
941 self.rhodecode_vcs_repo.get_commit(commit_id)
942 for commit_id in commit_ids)
943 else:
944 _f_history, _hist = self._get_node_history(commit, f_path)
945 c.file_author = False
946
947 unique = collections.OrderedDict()
948 for commit in _hist:
949 author = commit.author
950 if author not in unique:
951 unique[commit.author] = [
952 h.email(author),
953 h.person(author, 'username_or_name_or_email'),
954 1 # counter
955 ]
956
957 else:
958 # increase counter
959 unique[commit.author][2] += 1
960
961 c.authors = [val for val in unique.values()]
962
963 return self._get_template_context(c)
964
965 @LoginRequired()
966 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
967 @view_config(
968 route_name='repo_files_remove_file', request_method='GET',
969 renderer='rhodecode:templates/files/files_delete.mako')
970 def repo_files_remove_file(self):
971 _ = self.request.translate
972 c = self.load_default_context()
973 commit_id, f_path = self._get_commit_and_path()
974
975 self._ensure_not_locked()
976
977 if not self._is_valid_head(commit_id, self.rhodecode_vcs_repo):
978 h.flash(_('You can only delete files with commit '
979 'being a valid branch '), category='warning')
980 raise HTTPFound(
981 h.route_path('repo_files',
982 repo_name=self.db_repo_name, commit_id='tip',
983 f_path=f_path))
984
985 c.commit = self._get_commit_or_redirect(commit_id)
986 c.file = self._get_filenode_or_redirect(c.commit, f_path)
987
988 c.default_message = _(
989 'Deleted file {} via RhodeCode Enterprise').format(f_path)
990 c.f_path = f_path
991
992 return self._get_template_context(c)
993
994 @LoginRequired()
995 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
996 @CSRFRequired()
997 @view_config(
998 route_name='repo_files_delete_file', request_method='POST',
999 renderer=None)
1000 def repo_files_delete_file(self):
1001 _ = self.request.translate
1002
1003 c = self.load_default_context()
1004 commit_id, f_path = self._get_commit_and_path()
1005
1006 self._ensure_not_locked()
1007
1008 if not self._is_valid_head(commit_id, self.rhodecode_vcs_repo):
1009 h.flash(_('You can only delete files with commit '
1010 'being a valid branch '), category='warning')
1011 raise HTTPFound(
1012 h.route_path('repo_files',
1013 repo_name=self.db_repo_name, commit_id='tip',
1014 f_path=f_path))
1015
1016 c.commit = self._get_commit_or_redirect(commit_id)
1017 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1018
1019 c.default_message = _(
1020 'Deleted file {} via RhodeCode Enterprise').format(f_path)
1021 c.f_path = f_path
1022 node_path = f_path
1023 author = self._rhodecode_db_user.full_contact
1024 message = self.request.POST.get('message') or c.default_message
1025 try:
1026 nodes = {
1027 node_path: {
1028 'content': ''
1029 }
1030 }
1031 ScmModel().delete_nodes(
1032 user=self._rhodecode_db_user.user_id, repo=self.db_repo,
1033 message=message,
1034 nodes=nodes,
1035 parent_commit=c.commit,
1036 author=author,
1037 )
1038
1039 h.flash(
1040 _('Successfully deleted file `{}`').format(
1041 h.escape(f_path)), category='success')
1042 except Exception:
1043 log.exception('Error during commit operation')
1044 h.flash(_('Error occurred during commit'), category='error')
1045 raise HTTPFound(
1046 h.route_path('changeset_home', repo_name=self.db_repo_name,
1047 revision='tip'))
1048
1049 @LoginRequired()
1050 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1051 @view_config(
1052 route_name='repo_files_edit_file', request_method='GET',
1053 renderer='rhodecode:templates/files/files_edit.mako')
1054 def repo_files_edit_file(self):
1055 _ = self.request.translate
1056 c = self.load_default_context()
1057 commit_id, f_path = self._get_commit_and_path()
1058
1059 self._ensure_not_locked()
1060
1061 if not self._is_valid_head(commit_id, self.rhodecode_vcs_repo):
1062 h.flash(_('You can only edit files with commit '
1063 'being a valid branch '), category='warning')
1064 raise HTTPFound(
1065 h.route_path('repo_files',
1066 repo_name=self.db_repo_name, commit_id='tip',
1067 f_path=f_path))
1068
1069 c.commit = self._get_commit_or_redirect(commit_id)
1070 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1071
1072 if c.file.is_binary:
1073 files_url = h.route_path(
1074 'repo_files',
1075 repo_name=self.db_repo_name,
1076 commit_id=c.commit.raw_id, f_path=f_path)
1077 raise HTTPFound(files_url)
1078
1079 c.default_message = _(
1080 'Edited file {} via RhodeCode Enterprise').format(f_path)
1081 c.f_path = f_path
1082
1083 return self._get_template_context(c)
1084
1085 @LoginRequired()
1086 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1087 @CSRFRequired()
1088 @view_config(
1089 route_name='repo_files_update_file', request_method='POST',
1090 renderer=None)
1091 def repo_files_update_file(self):
1092 _ = self.request.translate
1093 c = self.load_default_context()
1094 commit_id, f_path = self._get_commit_and_path()
1095
1096 self._ensure_not_locked()
1097
1098 if not self._is_valid_head(commit_id, self.rhodecode_vcs_repo):
1099 h.flash(_('You can only edit files with commit '
1100 'being a valid branch '), category='warning')
1101 raise HTTPFound(
1102 h.route_path('repo_files',
1103 repo_name=self.db_repo_name, commit_id='tip',
1104 f_path=f_path))
1105
1106 c.commit = self._get_commit_or_redirect(commit_id)
1107 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1108
1109 if c.file.is_binary:
1110 raise HTTPFound(
1111 h.route_path('repo_files',
1112 repo_name=self.db_repo_name,
1113 commit_id=c.commit.raw_id,
1114 f_path=f_path))
1115
1116 c.default_message = _(
1117 'Edited file {} via RhodeCode Enterprise').format(f_path)
1118 c.f_path = f_path
1119 old_content = c.file.content
1120 sl = old_content.splitlines(1)
1121 first_line = sl[0] if sl else ''
1122
1123 r_post = self.request.POST
1124 # modes: 0 - Unix, 1 - Mac, 2 - DOS
1125 mode = detect_mode(first_line, 0)
1126 content = convert_line_endings(r_post.get('content', ''), mode)
1127
1128 message = r_post.get('message') or c.default_message
1129 org_f_path = c.file.unicode_path
1130 filename = r_post['filename']
1131 org_filename = c.file.name
1132
1133 if content == old_content and filename == org_filename:
1134 h.flash(_('No changes'), category='warning')
1135 raise HTTPFound(
1136 h.route_path('changeset_home', repo_name=self.db_repo_name,
1137 revision='tip'))
1138 try:
1139 mapping = {
1140 org_f_path: {
1141 'org_filename': org_f_path,
1142 'filename': os.path.join(c.file.dir_path, filename),
1143 'content': content,
1144 'lexer': '',
1145 'op': 'mod',
1146 }
1147 }
1148
1149 ScmModel().update_nodes(
1150 user=self._rhodecode_db_user.user_id,
1151 repo=self.db_repo,
1152 message=message,
1153 nodes=mapping,
1154 parent_commit=c.commit,
1155 )
1156
1157 h.flash(
1158 _('Successfully committed changes to file `{}`').format(
1159 h.escape(f_path)), category='success')
1160 except Exception:
1161 log.exception('Error occurred during commit')
1162 h.flash(_('Error occurred during commit'), category='error')
1163 raise HTTPFound(
1164 h.route_path('changeset_home', repo_name=self.db_repo_name,
1165 revision='tip'))
1166
1167 @LoginRequired()
1168 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1169 @view_config(
1170 route_name='repo_files_add_file', request_method='GET',
1171 renderer='rhodecode:templates/files/files_add.mako')
1172 def repo_files_add_file(self):
1173 _ = self.request.translate
1174 c = self.load_default_context()
1175 commit_id, f_path = self._get_commit_and_path()
1176
1177 self._ensure_not_locked()
1178
1179 c.commit = self._get_commit_or_redirect(commit_id, redirect_after=False)
1180 if c.commit is None:
1181 c.commit = EmptyCommit(alias=self.rhodecode_vcs_repo.alias)
1182 c.default_message = (_('Added file via RhodeCode Enterprise'))
1183 c.f_path = f_path
1184
1185 return self._get_template_context(c)
1186
1187 @LoginRequired()
1188 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1189 @CSRFRequired()
1190 @view_config(
1191 route_name='repo_files_create_file', request_method='POST',
1192 renderer=None)
1193 def repo_files_create_file(self):
1194 _ = self.request.translate
1195 c = self.load_default_context()
1196 commit_id, f_path = self._get_commit_and_path()
1197
1198 self._ensure_not_locked()
1199
1200 r_post = self.request.POST
1201
1202 c.commit = self._get_commit_or_redirect(
1203 commit_id, redirect_after=False)
1204 if c.commit is None:
1205 c.commit = EmptyCommit(alias=self.rhodecode_vcs_repo.alias)
1206 c.default_message = (_('Added file via RhodeCode Enterprise'))
1207 c.f_path = f_path
1208 unix_mode = 0
1209 content = convert_line_endings(r_post.get('content', ''), unix_mode)
1210
1211 message = r_post.get('message') or c.default_message
1212 filename = r_post.get('filename')
1213 location = r_post.get('location', '') # dir location
1214 file_obj = r_post.get('upload_file', None)
1215
1216 if file_obj is not None and hasattr(file_obj, 'filename'):
1217 filename = r_post.get('filename_upload')
1218 content = file_obj.file
1219
1220 if hasattr(content, 'file'):
1221 # non posix systems store real file under file attr
1222 content = content.file
1223
1224 default_redirect_url = h.route_path(
1225 'changeset_home', repo_name=self.db_repo_name, revision='tip')
1226
1227 # If there's no commit, redirect to repo summary
1228 if type(c.commit) is EmptyCommit:
1229 redirect_url = h.route_path(
1230 'repo_summary', repo_name=self.db_repo_name)
1231 else:
1232 redirect_url = default_redirect_url
1233
1234 if not filename:
1235 h.flash(_('No filename'), category='warning')
1236 raise HTTPFound(redirect_url)
1237
1238 # extract the location from filename,
1239 # allows using foo/bar.txt syntax to create subdirectories
1240 subdir_loc = filename.rsplit('/', 1)
1241 if len(subdir_loc) == 2:
1242 location = os.path.join(location, subdir_loc[0])
1243
1244 # strip all crap out of file, just leave the basename
1245 filename = os.path.basename(filename)
1246 node_path = os.path.join(location, filename)
1247 author = self._rhodecode_db_user.full_contact
1248
1249 try:
1250 nodes = {
1251 node_path: {
1252 'content': content
1253 }
1254 }
1255 ScmModel().create_nodes(
1256 user=self._rhodecode_db_user.user_id,
1257 repo=self.db_repo,
1258 message=message,
1259 nodes=nodes,
1260 parent_commit=c.commit,
1261 author=author,
1262 )
1263
1264 h.flash(
1265 _('Successfully committed new file `{}`').format(
1266 h.escape(node_path)), category='success')
1267 except NonRelativePathError:
1268 h.flash(_(
1269 'The location specified must be a relative path and must not '
1270 'contain .. in the path'), category='warning')
1271 raise HTTPFound(default_redirect_url)
1272 except (NodeError, NodeAlreadyExistsError) as e:
1273 h.flash(_(h.escape(e)), category='error')
1274 except Exception:
1275 log.exception('Error occurred during commit')
1276 h.flash(_('Error occurred during commit'), category='error')
1277
1278 raise HTTPFound(default_redirect_url)
@@ -250,12 +250,21 b' class BaseReferencesView(RepoAppView):'
250
250
251 # TODO: johbo: Unify generation of reference links
251 # TODO: johbo: Unify generation of reference links
252 use_commit_id = '/' in ref_name or is_svn
252 use_commit_id = '/' in ref_name or is_svn
253 files_url = h.url(
253
254 'files_home',
254 if use_commit_id:
255 repo_name=self.db_repo_name,
255 files_url = h.route_path(
256 f_path=ref_name if is_svn else '',
256 'repo_files',
257 revision=commit_id if use_commit_id else ref_name,
257 repo_name=self.db_repo_name,
258 at=ref_name)
258 f_path=ref_name if is_svn else '',
259 commit_id=commit_id)
260
261 else:
262 files_url = h.route_path(
263 'repo_files',
264 repo_name=self.db_repo_name,
265 f_path=ref_name if is_svn else '',
266 commit_id=ref_name,
267 _query=dict(at=ref_name))
259
268
260 data.append({
269 data.append({
261 "name": _render('name', ref_name, files_url, closed),
270 "name": _render('name', ref_name, files_url, closed),
@@ -37,6 +37,95 b' def includeme(config):'
37 name='repo_commit',
37 name='repo_commit',
38 pattern='/{repo_name:.*?[^/]}/changeset/{commit_id}', repo_route=True)
38 pattern='/{repo_name:.*?[^/]}/changeset/{commit_id}', repo_route=True)
39
39
40 # repo files
41 config.add_route(
42 name='repo_archivefile',
43 pattern='/{repo_name:.*?[^/]}/archive/{fname}', repo_route=True)
44
45 config.add_route(
46 name='repo_files_diff',
47 pattern='/{repo_name:.*?[^/]}/diff/{f_path:.*}', repo_route=True)
48 config.add_route( # legacy route to make old links work
49 name='repo_files_diff_2way_redirect',
50 pattern='/{repo_name:.*?[^/]}/diff-2way/{f_path:.*}', repo_route=True)
51
52 config.add_route(
53 name='repo_files',
54 pattern='/{repo_name:.*?[^/]}/files/{commit_id}/{f_path:.*}', repo_route=True)
55 config.add_route(
56 name='repo_files:default_path',
57 pattern='/{repo_name:.*?[^/]}/files/{commit_id}/', repo_route=True)
58 config.add_route(
59 name='repo_files:default_commit',
60 pattern='/{repo_name:.*?[^/]}/files', repo_route=True)
61
62 config.add_route(
63 name='repo_files:rendered',
64 pattern='/{repo_name:.*?[^/]}/render/{commit_id}/{f_path:.*}', repo_route=True)
65
66 config.add_route(
67 name='repo_files:annotated',
68 pattern='/{repo_name:.*?[^/]}/annotate/{commit_id}/{f_path:.*}', repo_route=True)
69 config.add_route(
70 name='repo_files:annotated_previous',
71 pattern='/{repo_name:.*?[^/]}/annotate-previous/{commit_id}/{f_path:.*}', repo_route=True)
72
73 config.add_route(
74 name='repo_nodetree_full',
75 pattern='/{repo_name:.*?[^/]}/nodetree_full/{commit_id}/{f_path:.*}', repo_route=True)
76 config.add_route(
77 name='repo_nodetree_full:default_path',
78 pattern='/{repo_name:.*?[^/]}/nodetree_full/{commit_id}/', repo_route=True)
79
80 config.add_route(
81 name='repo_files_nodelist',
82 pattern='/{repo_name:.*?[^/]}/nodelist/{commit_id}/{f_path:.*}', repo_route=True)
83
84 config.add_route(
85 name='repo_file_raw',
86 pattern='/{repo_name:.*?[^/]}/raw/{commit_id}/{f_path:.*}', repo_route=True)
87
88 config.add_route(
89 name='repo_file_download',
90 pattern='/{repo_name:.*?[^/]}/download/{commit_id}/{f_path:.*}', repo_route=True)
91 config.add_route( # backward compat to keep old links working
92 name='repo_file_download:legacy',
93 pattern='/{repo_name:.*?[^/]}/rawfile/{commit_id}/{f_path:.*}',
94 repo_route=True)
95
96 config.add_route(
97 name='repo_file_history',
98 pattern='/{repo_name:.*?[^/]}/history/{commit_id}/{f_path:.*}', repo_route=True)
99
100 config.add_route(
101 name='repo_file_authors',
102 pattern='/{repo_name:.*?[^/]}/authors/{commit_id}/{f_path:.*}', repo_route=True)
103
104 config.add_route(
105 name='repo_files_remove_file',
106 pattern='/{repo_name:.*?[^/]}/remove_file/{commit_id}/{f_path:.*}',
107 repo_route=True)
108 config.add_route(
109 name='repo_files_delete_file',
110 pattern='/{repo_name:.*?[^/]}/delete_file/{commit_id}/{f_path:.*}',
111 repo_route=True)
112 config.add_route(
113 name='repo_files_edit_file',
114 pattern='/{repo_name:.*?[^/]}/edit_file/{commit_id}/{f_path:.*}',
115 repo_route=True)
116 config.add_route(
117 name='repo_files_update_file',
118 pattern='/{repo_name:.*?[^/]}/update_file/{commit_id}/{f_path:.*}',
119 repo_route=True)
120 config.add_route(
121 name='repo_files_add_file',
122 pattern='/{repo_name:.*?[^/]}/add_file/{commit_id}/{f_path:.*}',
123 repo_route=True)
124 config.add_route(
125 name='repo_files_create_file',
126 pattern='/{repo_name:.*?[^/]}/create_file/{commit_id}/{f_path:.*}',
127 repo_route=True)
128
40 # refs data
129 # refs data
41 config.add_route(
130 config.add_route(
42 name='repo_refs_data',
131 name='repo_refs_data',
This diff has been collapsed as it changes many lines, (959 lines changed) Show them Hide them
@@ -23,15 +23,14 b' import os'
23 import mock
23 import mock
24 import pytest
24 import pytest
25
25
26 from rhodecode.controllers.files import FilesController
26 from rhodecode.apps.repository.views.repo_files import RepoFilesView
27 from rhodecode.lib import helpers as h
27 from rhodecode.lib import helpers as h
28 from rhodecode.lib.compat import OrderedDict
28 from rhodecode.lib.compat import OrderedDict
29 from rhodecode.lib.ext_json import json
29 from rhodecode.lib.ext_json import json
30 from rhodecode.lib.vcs import nodes
30 from rhodecode.lib.vcs import nodes
31
31
32 from rhodecode.lib.vcs.conf import settings
32 from rhodecode.lib.vcs.conf import settings
33 from rhodecode.tests import (
33 from rhodecode.tests import assert_session_flash
34 url, assert_session_flash, assert_not_in_session_flash)
35 from rhodecode.tests.fixture import Fixture
34 from rhodecode.tests.fixture import Fixture
36
35
37 fixture = Fixture()
36 fixture = Fixture()
@@ -43,14 +42,71 b' NODE_HISTORY = {'
43 }
42 }
44
43
45
44
45 def route_path(name, params=None, **kwargs):
46 import urllib
47
48 base_url = {
49 'repo_archivefile': '/{repo_name}/archive/{fname}',
50 'repo_files_diff': '/{repo_name}/diff/{f_path}',
51 'repo_files_diff_2way_redirect': '/{repo_name}/diff-2way/{f_path}',
52 'repo_files': '/{repo_name}/files/{commit_id}/{f_path}',
53 'repo_files:default_path': '/{repo_name}/files/{commit_id}/',
54 'repo_files:default_commit': '/{repo_name}/files',
55 'repo_files:rendered': '/{repo_name}/render/{commit_id}/{f_path}',
56 'repo_files:annotated': '/{repo_name}/annotate/{commit_id}/{f_path}',
57 'repo_files:annotated_previous': '/{repo_name}/annotate-previous/{commit_id}/{f_path}',
58 'repo_files_nodelist': '/{repo_name}/nodelist/{commit_id}/{f_path}',
59 'repo_file_raw': '/{repo_name}/raw/{commit_id}/{f_path}',
60 'repo_file_download': '/{repo_name}/download/{commit_id}/{f_path}',
61 'repo_file_history': '/{repo_name}/history/{commit_id}/{f_path}',
62 'repo_file_authors': '/{repo_name}/authors/{commit_id}/{f_path}',
63 'repo_files_remove_file': '/{repo_name}/remove_file/{commit_id}/{f_path}',
64 'repo_files_delete_file': '/{repo_name}/delete_file/{commit_id}/{f_path}',
65 'repo_files_edit_file': '/{repo_name}/edit_file/{commit_id}/{f_path}',
66 'repo_files_update_file': '/{repo_name}/update_file/{commit_id}/{f_path}',
67 'repo_files_add_file': '/{repo_name}/add_file/{commit_id}/{f_path}',
68 'repo_files_create_file': '/{repo_name}/create_file/{commit_id}/{f_path}',
69 'repo_nodetree_full': '/{repo_name}/nodetree_full/{commit_id}/{f_path}',
70 'repo_nodetree_full:default_path': '/{repo_name}/nodetree_full/{commit_id}/',
71 }[name].format(**kwargs)
72
73 if params:
74 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
75 return base_url
76
77
78 def assert_files_in_response(response, files, params):
79 template = (
80 'href="/%(repo_name)s/files/%(commit_id)s/%(name)s"')
81 _assert_items_in_response(response, files, template, params)
82
83
84 def assert_dirs_in_response(response, dirs, params):
85 template = (
86 'href="/%(repo_name)s/files/%(commit_id)s/%(name)s"')
87 _assert_items_in_response(response, dirs, template, params)
88
89
90 def _assert_items_in_response(response, items, template, params):
91 for item in items:
92 item_params = {'name': item}
93 item_params.update(params)
94 response.mustcontain(template % item_params)
95
96
97 def assert_timeago_in_response(response, items, params):
98 for item in items:
99 response.mustcontain(h.age_component(params['date']))
100
46
101
47 @pytest.mark.usefixtures("app")
102 @pytest.mark.usefixtures("app")
48 class TestFilesController:
103 class TestFilesViews(object):
49
104
50 def test_index(self, backend):
105 def test_show_files(self, backend):
51 response = self.app.get(url(
106 response = self.app.get(
52 controller='files', action='index',
107 route_path('repo_files',
53 repo_name=backend.repo_name, revision='tip', f_path='/'))
108 repo_name=backend.repo_name,
109 commit_id='tip', f_path='/'))
54 commit = backend.repo.get_commit()
110 commit = backend.repo.get_commit()
55
111
56 params = {
112 params = {
@@ -77,21 +133,23 b' class TestFilesController:'
77 assert_files_in_response(response, files, params)
133 assert_files_in_response(response, files, params)
78 assert_timeago_in_response(response, files, params)
134 assert_timeago_in_response(response, files, params)
79
135
80 def test_index_links_submodules_with_absolute_url(self, backend_hg):
136 def test_show_files_links_submodules_with_absolute_url(self, backend_hg):
81 repo = backend_hg['subrepos']
137 repo = backend_hg['subrepos']
82 response = self.app.get(url(
138 response = self.app.get(
83 controller='files', action='index',
139 route_path('repo_files',
84 repo_name=repo.repo_name, revision='tip', f_path='/'))
140 repo_name=repo.repo_name,
141 commit_id='tip', f_path='/'))
85 assert_response = response.assert_response()
142 assert_response = response.assert_response()
86 assert_response.contains_one_link(
143 assert_response.contains_one_link(
87 'absolute-path @ 000000000000', 'http://example.com/absolute-path')
144 'absolute-path @ 000000000000', 'http://example.com/absolute-path')
88
145
89 def test_index_links_submodules_with_absolute_url_subpaths(
146 def test_show_files_links_submodules_with_absolute_url_subpaths(
90 self, backend_hg):
147 self, backend_hg):
91 repo = backend_hg['subrepos']
148 repo = backend_hg['subrepos']
92 response = self.app.get(url(
149 response = self.app.get(
93 controller='files', action='index',
150 route_path('repo_files',
94 repo_name=repo.repo_name, revision='tip', f_path='/'))
151 repo_name=repo.repo_name,
152 commit_id='tip', f_path='/'))
95 assert_response = response.assert_response()
153 assert_response = response.assert_response()
96 assert_response.contains_one_link(
154 assert_response.contains_one_link(
97 'subpaths-path @ 000000000000',
155 'subpaths-path @ 000000000000',
@@ -108,29 +166,29 b' class TestFilesController:'
108
166
109 backend.repo.landing_rev = "branch:%s" % new_branch
167 backend.repo.landing_rev = "branch:%s" % new_branch
110
168
111 # get response based on tip and not new revision
169 # get response based on tip and not new commit
112 response = self.app.get(url(
170 response = self.app.get(
113 controller='files', action='index',
171 route_path('repo_files',
114 repo_name=backend.repo_name, revision='tip', f_path='/'),
172 repo_name=backend.repo_name,
115 status=200)
173 commit_id='tip', f_path='/'))
116
174
117 # make sure Files menu url is not tip but new revision
175 # make sure Files menu url is not tip but new commit
118 landing_rev = backend.repo.landing_rev[1]
176 landing_rev = backend.repo.landing_rev[1]
119 files_url = url('files_home', repo_name=backend.repo_name,
177 files_url = route_path('repo_files:default_path',
120 revision=landing_rev)
178 repo_name=backend.repo_name,
179 commit_id=landing_rev)
121
180
122 assert landing_rev != 'tip'
181 assert landing_rev != 'tip'
123 response.mustcontain('<li class="active"><a class="menulink" href="%s">' % files_url)
182 response.mustcontain(
183 '<li class="active"><a class="menulink" href="%s">' % files_url)
124
184
125 def test_index_commit(self, backend):
185 def test_show_files_commit(self, backend):
126 commit = backend.repo.get_commit(commit_idx=32)
186 commit = backend.repo.get_commit(commit_idx=32)
127
187
128 response = self.app.get(url(
188 response = self.app.get(
129 controller='files', action='index',
189 route_path('repo_files',
130 repo_name=backend.repo_name,
190 repo_name=backend.repo_name,
131 revision=commit.raw_id,
191 commit_id=commit.raw_id, f_path='/'))
132 f_path='/')
133 )
134
192
135 dirs = ['docs', 'tests']
193 dirs = ['docs', 'tests']
136 files = ['README.rst']
194 files = ['README.rst']
@@ -141,7 +199,7 b' class TestFilesController:'
141 assert_dirs_in_response(response, dirs, params)
199 assert_dirs_in_response(response, dirs, params)
142 assert_files_in_response(response, files, params)
200 assert_files_in_response(response, files, params)
143
201
144 def test_index_different_branch(self, backend):
202 def test_show_files_different_branch(self, backend):
145 branches = dict(
203 branches = dict(
146 hg=(150, ['git']),
204 hg=(150, ['git']),
147 # TODO: Git test repository does not contain other branches
205 # TODO: Git test repository does not contain other branches
@@ -151,104 +209,79 b' class TestFilesController:'
151 )
209 )
152 idx, branches = branches[backend.alias]
210 idx, branches = branches[backend.alias]
153 commit = backend.repo.get_commit(commit_idx=idx)
211 commit = backend.repo.get_commit(commit_idx=idx)
154 response = self.app.get(url(
212 response = self.app.get(
155 controller='files', action='index',
213 route_path('repo_files',
156 repo_name=backend.repo_name,
214 repo_name=backend.repo_name,
157 revision=commit.raw_id,
215 commit_id=commit.raw_id, f_path='/'))
158 f_path='/'))
216
159 assert_response = response.assert_response()
217 assert_response = response.assert_response()
160 for branch in branches:
218 for branch in branches:
161 assert_response.element_contains('.tags .branchtag', branch)
219 assert_response.element_contains('.tags .branchtag', branch)
162
220
163 def test_index_paging(self, backend):
221 def test_show_files_paging(self, backend):
164 repo = backend.repo
222 repo = backend.repo
165 indexes = [73, 92, 109, 1, 0]
223 indexes = [73, 92, 109, 1, 0]
166 idx_map = [(rev, repo.get_commit(commit_idx=rev).raw_id)
224 idx_map = [(rev, repo.get_commit(commit_idx=rev).raw_id)
167 for rev in indexes]
225 for rev in indexes]
168
226
169 for idx in idx_map:
227 for idx in idx_map:
170 response = self.app.get(url(
228 response = self.app.get(
171 controller='files', action='index',
229 route_path('repo_files',
172 repo_name=backend.repo_name,
230 repo_name=backend.repo_name,
173 revision=idx[1],
231 commit_id=idx[1], f_path='/'))
174 f_path='/'))
175
232
176 response.mustcontain("""r%s:%s""" % (idx[0], idx[1][:8]))
233 response.mustcontain("""r%s:%s""" % (idx[0], idx[1][:8]))
177
234
178 def test_file_source(self, backend):
235 def test_file_source(self, backend):
179 commit = backend.repo.get_commit(commit_idx=167)
236 commit = backend.repo.get_commit(commit_idx=167)
180 response = self.app.get(url(
237 response = self.app.get(
181 controller='files', action='index',
238 route_path('repo_files',
182 repo_name=backend.repo_name,
239 repo_name=backend.repo_name,
183 revision=commit.raw_id,
240 commit_id=commit.raw_id, f_path='vcs/nodes.py'))
184 f_path='vcs/nodes.py'))
185
241
186 msgbox = """<div class="commit right-content">%s</div>"""
242 msgbox = """<div class="commit right-content">%s</div>"""
187 response.mustcontain(msgbox % (commit.message, ))
243 response.mustcontain(msgbox % (commit.message, ))
188
244
189 assert_response = response.assert_response()
245 assert_response = response.assert_response()
190 if commit.branch:
246 if commit.branch:
191 assert_response.element_contains('.tags.tags-main .branchtag', commit.branch)
247 assert_response.element_contains(
248 '.tags.tags-main .branchtag', commit.branch)
192 if commit.tags:
249 if commit.tags:
193 for tag in commit.tags:
250 for tag in commit.tags:
194 assert_response.element_contains('.tags.tags-main .tagtag', tag)
251 assert_response.element_contains('.tags.tags-main .tagtag', tag)
195
252
196 def test_file_source_history(self, backend):
253 def test_file_source_annotated(self, backend):
197 response = self.app.get(
198 url(
199 controller='files', action='history',
200 repo_name=backend.repo_name,
201 revision='tip',
202 f_path='vcs/nodes.py'),
203 extra_environ={'HTTP_X_PARTIAL_XHR': '1'})
204 assert NODE_HISTORY[backend.alias] == json.loads(response.body)
205
206 def test_file_source_history_svn(self, backend_svn):
207 simple_repo = backend_svn['svn-simple-layout']
208 response = self.app.get(
254 response = self.app.get(
209 url(
255 route_path('repo_files:annotated',
210 controller='files', action='history',
256 repo_name=backend.repo_name,
211 repo_name=simple_repo.repo_name,
257 commit_id='tip', f_path='vcs/nodes.py'))
212 revision='tip',
258 expected_commits = {
213 f_path='trunk/example.py'),
214 extra_environ={'HTTP_X_PARTIAL_XHR': '1'})
215
216 expected_data = json.loads(
217 fixture.load_resource('svn_node_history_branches.json'))
218 assert expected_data == response.json
219
220 def test_file_annotation_history(self, backend):
221 response = self.app.get(
222 url(
223 controller='files', action='history',
224 repo_name=backend.repo_name,
225 revision='tip',
226 f_path='vcs/nodes.py',
227 annotate=True),
228 extra_environ={'HTTP_X_PARTIAL_XHR': '1'})
229 assert NODE_HISTORY[backend.alias] == json.loads(response.body)
230
231 def test_file_annotation(self, backend):
232 response = self.app.get(url(
233 controller='files', action='index',
234 repo_name=backend.repo_name, revision='tip', f_path='vcs/nodes.py',
235 annotate=True))
236
237 expected_revisions = {
238 'hg': 'r356',
259 'hg': 'r356',
239 'git': 'r345',
260 'git': 'r345',
240 'svn': 'r208',
261 'svn': 'r208',
241 }
262 }
242 response.mustcontain(expected_revisions[backend.alias])
263 response.mustcontain(expected_commits[backend.alias])
243
264
244 def test_file_authors(self, backend):
265 def test_file_source_authors(self, backend):
245 response = self.app.get(url(
266 response = self.app.get(
246 controller='files', action='authors',
267 route_path('repo_file_authors',
247 repo_name=backend.repo_name,
268 repo_name=backend.repo_name,
248 revision='tip',
269 commit_id='tip', f_path='vcs/nodes.py'))
249 f_path='vcs/nodes.py',
270 expected_authors = {
250 annotate=True))
271 'hg': ('Marcin Kuzminski', 'Lukasz Balcerzak'),
272 'git': ('Marcin Kuzminski', 'Lukasz Balcerzak'),
273 'svn': ('marcin', 'lukasz'),
274 }
251
275
276 for author in expected_authors[backend.alias]:
277 response.mustcontain(author)
278
279 def test_file_source_authors_with_annotation(self, backend):
280 response = self.app.get(
281 route_path('repo_file_authors',
282 repo_name=backend.repo_name,
283 commit_id='tip', f_path='vcs/nodes.py',
284 params=dict(annotate=1)))
252 expected_authors = {
285 expected_authors = {
253 'hg': ('Marcin Kuzminski', 'Lukasz Balcerzak'),
286 'hg': ('Marcin Kuzminski', 'Lukasz Balcerzak'),
254 'git': ('Marcin Kuzminski', 'Lukasz Balcerzak'),
287 'git': ('Marcin Kuzminski', 'Lukasz Balcerzak'),
@@ -258,59 +291,89 b' class TestFilesController:'
258 for author in expected_authors[backend.alias]:
291 for author in expected_authors[backend.alias]:
259 response.mustcontain(author)
292 response.mustcontain(author)
260
293
294 def test_file_source_history(self, backend, xhr_header):
295 response = self.app.get(
296 route_path('repo_file_history',
297 repo_name=backend.repo_name,
298 commit_id='tip', f_path='vcs/nodes.py'),
299 extra_environ=xhr_header)
300 assert NODE_HISTORY[backend.alias] == json.loads(response.body)
301
302 def test_file_source_history_svn(self, backend_svn, xhr_header):
303 simple_repo = backend_svn['svn-simple-layout']
304 response = self.app.get(
305 route_path('repo_file_history',
306 repo_name=simple_repo.repo_name,
307 commit_id='tip', f_path='trunk/example.py'),
308 extra_environ=xhr_header)
309
310 expected_data = json.loads(
311 fixture.load_resource('svn_node_history_branches.json'))
312 assert expected_data == response.json
313
314 def test_file_source_history_with_annotation(self, backend, xhr_header):
315 response = self.app.get(
316 route_path('repo_file_history',
317 repo_name=backend.repo_name,
318 commit_id='tip', f_path='vcs/nodes.py',
319 params=dict(annotate=1)),
320
321 extra_environ=xhr_header)
322 assert NODE_HISTORY[backend.alias] == json.loads(response.body)
323
261 def test_tree_search_top_level(self, backend, xhr_header):
324 def test_tree_search_top_level(self, backend, xhr_header):
262 commit = backend.repo.get_commit(commit_idx=173)
325 commit = backend.repo.get_commit(commit_idx=173)
263 response = self.app.get(
326 response = self.app.get(
264 url('files_nodelist_home', repo_name=backend.repo_name,
327 route_path('repo_files_nodelist',
265 revision=commit.raw_id, f_path='/'),
328 repo_name=backend.repo_name,
329 commit_id=commit.raw_id, f_path='/'),
266 extra_environ=xhr_header)
330 extra_environ=xhr_header)
267 assert 'nodes' in response.json
331 assert 'nodes' in response.json
268 assert {'name': 'docs', 'type': 'dir'} in response.json['nodes']
332 assert {'name': 'docs', 'type': 'dir'} in response.json['nodes']
269
333
334 def test_tree_search_missing_xhr(self, backend):
335 self.app.get(
336 route_path('repo_files_nodelist',
337 repo_name=backend.repo_name,
338 commit_id='tip', f_path='/'),
339 status=404)
340
270 def test_tree_search_at_path(self, backend, xhr_header):
341 def test_tree_search_at_path(self, backend, xhr_header):
271 commit = backend.repo.get_commit(commit_idx=173)
342 commit = backend.repo.get_commit(commit_idx=173)
272 response = self.app.get(
343 response = self.app.get(
273 url('files_nodelist_home', repo_name=backend.repo_name,
344 route_path('repo_files_nodelist',
274 revision=commit.raw_id, f_path='/docs'),
345 repo_name=backend.repo_name,
346 commit_id=commit.raw_id, f_path='/docs'),
275 extra_environ=xhr_header)
347 extra_environ=xhr_header)
276 assert 'nodes' in response.json
348 assert 'nodes' in response.json
277 nodes = response.json['nodes']
349 nodes = response.json['nodes']
278 assert {'name': 'docs/api', 'type': 'dir'} in nodes
350 assert {'name': 'docs/api', 'type': 'dir'} in nodes
279 assert {'name': 'docs/index.rst', 'type': 'file'} in nodes
351 assert {'name': 'docs/index.rst', 'type': 'file'} in nodes
280
352
281 def test_tree_search_at_path_missing_xhr(self, backend):
353 def test_tree_search_at_path_2nd_level(self, backend, xhr_header):
282 self.app.get(
283 url('files_nodelist_home', repo_name=backend.repo_name,
284 revision='tip', f_path=''), status=400)
285
286 def test_tree_view_list(self, backend, xhr_header):
287 commit = backend.repo.get_commit(commit_idx=173)
288 response = self.app.get(
289 url('files_nodelist_home', repo_name=backend.repo_name,
290 f_path='/', revision=commit.raw_id),
291 extra_environ=xhr_header,
292 )
293 response.mustcontain("vcs/web/simplevcs/views/repository.py")
294
295 def test_tree_view_list_at_path(self, backend, xhr_header):
296 commit = backend.repo.get_commit(commit_idx=173)
354 commit = backend.repo.get_commit(commit_idx=173)
297 response = self.app.get(
355 response = self.app.get(
298 url('files_nodelist_home', repo_name=backend.repo_name,
356 route_path('repo_files_nodelist',
299 f_path='/docs', revision=commit.raw_id),
357 repo_name=backend.repo_name,
300 extra_environ=xhr_header,
358 commit_id=commit.raw_id, f_path='/docs/api'),
301 )
359 extra_environ=xhr_header)
302 response.mustcontain("docs/index.rst")
360 assert 'nodes' in response.json
361 nodes = response.json['nodes']
362 assert {'name': 'docs/api/index.rst', 'type': 'file'} in nodes
303
363
304 def test_tree_view_list_missing_xhr(self, backend):
364 def test_tree_search_at_path_missing_xhr(self, backend):
305 self.app.get(
365 self.app.get(
306 url('files_nodelist_home', repo_name=backend.repo_name,
366 route_path('repo_files_nodelist',
307 f_path='/', revision='tip'), status=400)
367 repo_name=backend.repo_name,
368 commit_id='tip', f_path='/docs'),
369 status=404)
308
370
309 def test_nodetree_full_success(self, backend, xhr_header):
371 def test_nodetree(self, backend, xhr_header):
310 commit = backend.repo.get_commit(commit_idx=173)
372 commit = backend.repo.get_commit(commit_idx=173)
311 response = self.app.get(
373 response = self.app.get(
312 url('files_nodetree_full', repo_name=backend.repo_name,
374 route_path('repo_nodetree_full',
313 f_path='/', commit_id=commit.raw_id),
375 repo_name=backend.repo_name,
376 commit_id=commit.raw_id, f_path='/'),
314 extra_environ=xhr_header)
377 extra_environ=xhr_header)
315
378
316 assert_response = response.assert_response()
379 assert_response = response.assert_response()
@@ -322,79 +385,130 b' class TestFilesController:'
322 for element in elements:
385 for element in elements:
323 assert element.get(attr)
386 assert element.get(attr)
324
387
325 def test_nodetree_full_if_file(self, backend, xhr_header):
388 def test_nodetree_if_file(self, backend, xhr_header):
326 commit = backend.repo.get_commit(commit_idx=173)
389 commit = backend.repo.get_commit(commit_idx=173)
327 response = self.app.get(
390 response = self.app.get(
328 url('files_nodetree_full', repo_name=backend.repo_name,
391 route_path('repo_nodetree_full',
329 f_path='README.rst', commit_id=commit.raw_id),
392 repo_name=backend.repo_name,
393 commit_id=commit.raw_id, f_path='README.rst'),
330 extra_environ=xhr_header)
394 extra_environ=xhr_header)
331 assert response.body == ''
395 assert response.body == ''
332
396
333 def test_tree_metadata_list_missing_xhr(self, backend):
397 def test_nodetree_wrong_path(self, backend, xhr_header):
334 self.app.get(
398 commit = backend.repo.get_commit(commit_idx=173)
335 url('files_nodetree_full', repo_name=backend.repo_name,
399 response = self.app.get(
336 f_path='/', commit_id='tip'), status=400)
400 route_path('repo_nodetree_full',
401 repo_name=backend.repo_name,
402 commit_id=commit.raw_id, f_path='/dont-exist'),
403 extra_environ=xhr_header)
337
404
338 def test_access_empty_repo_redirect_to_summary_with_alert_write_perms(
405 err = 'error: There is no file nor ' \
339 self, app, backend_stub, autologin_regular_user, user_regular,
406 'directory at the given path'
340 user_util):
407 assert err in response.body
341 repo = backend_stub.create_repo()
342 user_util.grant_user_permission_to_repo(
343 repo, user_regular, 'repository.write')
344 response = self.app.get(url(
345 controller='files', action='index',
346 repo_name=repo.repo_name, revision='tip', f_path='/'))
347 assert_session_flash(
348 response,
349 'There are no files yet. <a class="alert-link" '
350 'href="/%s/add/0/#edit">Click here to add a new file.</a>'
351 % (repo.repo_name))
352
408
353 def test_access_empty_repo_redirect_to_summary_with_alert_no_write_perms(
409 def test_nodetree_missing_xhr(self, backend):
354 self, backend_stub, user_util):
410 self.app.get(
355 repo = backend_stub.create_repo()
411 route_path('repo_nodetree_full',
356 repo_file_url = url(
412 repo_name=backend.repo_name,
357 'files_add_home',
413 commit_id='tip', f_path='/'),
358 repo_name=repo.repo_name,
414 status=404)
359 revision=0, f_path='', anchor='edit')
360 response = self.app.get(url(
361 controller='files', action='index',
362 repo_name=repo.repo_name, revision='tip', f_path='/'))
363 assert_not_in_session_flash(response, repo_file_url)
364
415
365
416
366 # TODO: johbo: Think about a better place for these tests. Either controller
417 @pytest.mark.usefixtures("app", "autologin_user")
367 # specific unit tests or we move down the whole logic further towards the vcs
418 class TestRawFileHandling(object):
368 # layer
419
369 class TestAdjustFilePathForSvn(object):
420 def test_download_file(self, backend):
370 """SVN specific adjustments of node history in FileController."""
421 commit = backend.repo.get_commit(commit_idx=173)
422 response = self.app.get(
423 route_path('repo_file_download',
424 repo_name=backend.repo_name,
425 commit_id=commit.raw_id, f_path='vcs/nodes.py'),)
426
427 assert response.content_disposition == "attachment; filename=nodes.py"
428 assert response.content_type == "text/x-python"
429
430 def test_download_file_wrong_cs(self, backend):
431 raw_id = u'ERRORce30c96924232dffcd24178a07ffeb5dfc'
432
433 response = self.app.get(
434 route_path('repo_file_download',
435 repo_name=backend.repo_name,
436 commit_id=raw_id, f_path='vcs/nodes.svg'),
437 status=404)
371
438
372 def test_returns_path_relative_to_matched_reference(self):
439 msg = """No such commit exists for this repository"""
373 repo = self._repo(branches=['trunk'])
440 response.mustcontain(msg)
374 self.assert_file_adjustment('trunk/file', 'file', repo)
441
442 def test_download_file_wrong_f_path(self, backend):
443 commit = backend.repo.get_commit(commit_idx=173)
444 f_path = 'vcs/ERRORnodes.py'
375
445
376 def test_does_not_modify_file_if_no_reference_matches(self):
446 response = self.app.get(
377 repo = self._repo(branches=['trunk'])
447 route_path('repo_file_download',
378 self.assert_file_adjustment('notes/file', 'notes/file', repo)
448 repo_name=backend.repo_name,
449 commit_id=commit.raw_id, f_path=f_path),
450 status=404)
451
452 msg = (
453 "There is no file nor directory at the given path: "
454 "`%s` at commit %s" % (f_path, commit.short_id))
455 response.mustcontain(msg)
456
457 def test_file_raw(self, backend):
458 commit = backend.repo.get_commit(commit_idx=173)
459 response = self.app.get(
460 route_path('repo_file_raw',
461 repo_name=backend.repo_name,
462 commit_id=commit.raw_id, f_path='vcs/nodes.py'),)
379
463
380 def test_does_not_adjust_partial_directory_names(self):
464 assert response.content_type == "text/plain"
381 repo = self._repo(branches=['trun'])
465
382 self.assert_file_adjustment('trunk/file', 'trunk/file', repo)
466 def test_file_raw_binary(self, backend):
467 commit = backend.repo.get_commit()
468 response = self.app.get(
469 route_path('repo_file_raw',
470 repo_name=backend.repo_name,
471 commit_id=commit.raw_id,
472 f_path='docs/theme/ADC/static/breadcrumb_background.png'),)
473
474 assert response.content_disposition == 'inline'
383
475
384 def test_is_robust_to_patterns_which_prefix_other_patterns(self):
476 def test_raw_file_wrong_cs(self, backend):
385 repo = self._repo(branches=['trunk', 'trunk/new', 'trunk/old'])
477 raw_id = u'ERRORcce30c96924232dffcd24178a07ffeb5dfc'
386 self.assert_file_adjustment('trunk/new/file', 'file', repo)
478
479 response = self.app.get(
480 route_path('repo_file_raw',
481 repo_name=backend.repo_name,
482 commit_id=raw_id, f_path='vcs/nodes.svg'),
483 status=404)
484
485 msg = """No such commit exists for this repository"""
486 response.mustcontain(msg)
387
487
388 def assert_file_adjustment(self, f_path, expected, repo):
488 def test_raw_wrong_f_path(self, backend):
389 controller = FilesController()
489 commit = backend.repo.get_commit(commit_idx=173)
390 result = controller._adjust_file_path_for_svn(f_path, repo)
490 f_path = 'vcs/ERRORnodes.py'
391 assert result == expected
491 response = self.app.get(
492 route_path('repo_file_raw',
493 repo_name=backend.repo_name,
494 commit_id=commit.raw_id, f_path=f_path),
495 status=404)
392
496
393 def _repo(self, branches=None):
497 msg = (
394 repo = mock.Mock()
498 "There is no file nor directory at the given path: "
395 repo.branches = OrderedDict((name, '0') for name in branches or [])
499 "`%s` at commit %s" % (f_path, commit.short_id))
396 repo.tags = {}
500 response.mustcontain(msg)
397 return repo
501
502 def test_raw_svg_should_not_be_rendered(self, backend):
503 backend.create_repo()
504 backend.ensure_file("xss.svg")
505 response = self.app.get(
506 route_path('repo_file_raw',
507 repo_name=backend.repo_name,
508 commit_id='tip', f_path='xss.svg'),)
509 # If the content type is image/svg+xml then it allows to render HTML
510 # and malicious SVG.
511 assert response.content_type == "text/plain"
398
512
399
513
400 @pytest.mark.usefixtures("app")
514 @pytest.mark.usefixtures("app")
@@ -408,133 +522,50 b' class TestRepositoryArchival(object):'
408 short = commit.short_id + arch_ext
522 short = commit.short_id + arch_ext
409 fname = commit.raw_id + arch_ext
523 fname = commit.raw_id + arch_ext
410 filename = '%s-%s' % (backend.repo_name, short)
524 filename = '%s-%s' % (backend.repo_name, short)
411 response = self.app.get(url(controller='files',
525 response = self.app.get(
412 action='archivefile',
526 route_path('repo_archivefile',
413 repo_name=backend.repo_name,
527 repo_name=backend.repo_name,
414 fname=fname))
528 fname=fname))
415
529
416 assert response.status == '200 OK'
530 assert response.status == '200 OK'
417 headers = [
531 headers = [
418 ('Pragma', 'no-cache'),
419 ('Cache-Control', 'no-cache'),
420 ('Content-Disposition', 'attachment; filename=%s' % filename),
532 ('Content-Disposition', 'attachment; filename=%s' % filename),
421 ('Content-Type', '%s' % mime_type),
533 ('Content-Type', '%s' % mime_type),
422 ]
534 ]
423 if 'Set-Cookie' in response.response.headers:
535
424 del response.response.headers['Set-Cookie']
536 for header in headers:
425 assert response.response.headers.items() == headers
537 assert header in response.headers.items()
426
538
427 def test_archival_wrong_ext(self, backend):
539 @pytest.mark.parametrize('arch_ext',[
540 'tar', 'rar', 'x', '..ax', '.zipz', 'tar.gz.tar'])
541 def test_archival_wrong_ext(self, backend, arch_ext):
428 backend.enable_downloads()
542 backend.enable_downloads()
429 commit = backend.repo.get_commit(commit_idx=173)
543 commit = backend.repo.get_commit(commit_idx=173)
430 for arch_ext in ['tar', 'rar', 'x', '..ax', '.zipz']:
431 fname = commit.raw_id + arch_ext
432
544
433 response = self.app.get(url(controller='files',
545 fname = commit.raw_id + '.' + arch_ext
434 action='archivefile',
435 repo_name=backend.repo_name,
436 fname=fname))
437 response.mustcontain('Unknown archive type')
438
439 def test_archival_wrong_commit_id(self, backend):
440 backend.enable_downloads()
441 for commit_id in ['00x000000', 'tar', 'wrong', '@##$@$42413232',
442 '232dffcd']:
443 fname = '%s.zip' % commit_id
444
445 response = self.app.get(url(controller='files',
446 action='archivefile',
447 repo_name=backend.repo_name,
448 fname=fname))
449 response.mustcontain('Unknown revision')
450
451
546
452 @pytest.mark.usefixtures("app", "autologin_user")
547 response = self.app.get(
453 class TestRawFileHandling(object):
548 route_path('repo_archivefile',
454
549 repo_name=backend.repo_name,
455 def test_raw_file_ok(self, backend):
550 fname=fname))
456 commit = backend.repo.get_commit(commit_idx=173)
551 response.mustcontain(
457 response = self.app.get(url(controller='files', action='rawfile',
552 'Unknown archive type for: `{}`'.format(fname))
458 repo_name=backend.repo_name,
459 revision=commit.raw_id,
460 f_path='vcs/nodes.py'))
461
462 assert response.content_disposition == "attachment; filename=nodes.py"
463 assert response.content_type == "text/x-python"
464
465 def test_raw_file_wrong_cs(self, backend):
466 commit_id = u'ERRORce30c96924232dffcd24178a07ffeb5dfc'
467 f_path = 'vcs/nodes.py'
468
469 response = self.app.get(url(controller='files', action='rawfile',
470 repo_name=backend.repo_name,
471 revision=commit_id,
472 f_path=f_path), status=404)
473
474 msg = """No such commit exists for this repository"""
475 response.mustcontain(msg)
476
553
477 def test_raw_file_wrong_f_path(self, backend):
554 @pytest.mark.parametrize('commit_id', [
478 commit = backend.repo.get_commit(commit_idx=173)
555 '00x000000', 'tar', 'wrong', '@$@$42413232', '232dffcd'])
479 f_path = 'vcs/ERRORnodes.py'
556 def test_archival_wrong_commit_id(self, backend, commit_id):
480 response = self.app.get(url(controller='files', action='rawfile',
557 backend.enable_downloads()
481 repo_name=backend.repo_name,
558 fname = '%s.zip' % commit_id
482 revision=commit.raw_id,
483 f_path=f_path), status=404)
484
485 msg = (
486 "There is no file nor directory at the given path: "
487 "`%s` at commit %s" % (f_path, commit.short_id))
488 response.mustcontain(msg)
489
490 def test_raw_ok(self, backend):
491 commit = backend.repo.get_commit(commit_idx=173)
492 response = self.app.get(url(controller='files', action='raw',
493 repo_name=backend.repo_name,
494 revision=commit.raw_id,
495 f_path='vcs/nodes.py'))
496
497 assert response.content_type == "text/plain"
498
499 def test_raw_wrong_cs(self, backend):
500 commit_id = u'ERRORcce30c96924232dffcd24178a07ffeb5dfc'
501 f_path = 'vcs/nodes.py'
502
559
503 response = self.app.get(url(controller='files', action='raw',
560 response = self.app.get(
504 repo_name=backend.repo_name,
561 route_path('repo_archivefile',
505 revision=commit_id,
562 repo_name=backend.repo_name,
506 f_path=f_path), status=404)
563 fname=fname))
507
564 response.mustcontain('Unknown commit_id')
508 msg = """No such commit exists for this repository"""
509 response.mustcontain(msg)
510
511 def test_raw_wrong_f_path(self, backend):
512 commit = backend.repo.get_commit(commit_idx=173)
513 f_path = 'vcs/ERRORnodes.py'
514 response = self.app.get(url(controller='files', action='raw',
515 repo_name=backend.repo_name,
516 revision=commit.raw_id,
517 f_path=f_path), status=404)
518 msg = (
519 "There is no file nor directory at the given path: "
520 "`%s` at commit %s" % (f_path, commit.short_id))
521 response.mustcontain(msg)
522
523 def test_raw_svg_should_not_be_rendered(self, backend):
524 backend.create_repo()
525 backend.ensure_file("xss.svg")
526 response = self.app.get(url(controller='files', action='raw',
527 repo_name=backend.repo_name,
528 revision='tip',
529 f_path='xss.svg'))
530
531 # If the content type is image/svg+xml then it allows to render HTML
532 # and malicious SVG.
533 assert response.content_type == "text/plain"
534
565
535
566
536 @pytest.mark.usefixtures("app")
567 @pytest.mark.usefixtures("app")
537 class TestFilesDiff:
568 class TestFilesDiff(object):
538
569
539 @pytest.mark.parametrize("diff", ['diff', 'download', 'raw'])
570 @pytest.mark.parametrize("diff", ['diff', 'download', 'raw'])
540 def test_file_full_diff(self, backend, diff):
571 def test_file_full_diff(self, backend, diff):
@@ -542,11 +573,9 b' class TestFilesDiff:'
542 commit2 = backend.repo.get_commit(commit_idx=-2)
573 commit2 = backend.repo.get_commit(commit_idx=-2)
543
574
544 response = self.app.get(
575 response = self.app.get(
545 url(
576 route_path('repo_files_diff',
546 controller='files',
577 repo_name=backend.repo_name,
547 action='diff',
578 f_path='README'),
548 repo_name=backend.repo_name,
549 f_path='README'),
550 params={
579 params={
551 'diff1': commit2.raw_id,
580 'diff1': commit2.raw_id,
552 'diff2': commit1.raw_id,
581 'diff2': commit1.raw_id,
@@ -571,11 +600,9 b' class TestFilesDiff:'
571 repo = backend.create_repo(commits=commits)
600 repo = backend.create_repo(commits=commits)
572
601
573 response = self.app.get(
602 response = self.app.get(
574 url(
603 route_path('repo_files_diff',
575 controller='files',
604 repo_name=backend.repo_name,
576 action='diff',
605 f_path='file.bin'),
577 repo_name=backend.repo_name,
578 f_path='file.bin'),
579 params={
606 params={
580 'diff1': repo.get_commit(commit_idx=0).raw_id,
607 'diff1': repo.get_commit(commit_idx=0).raw_id,
581 'diff2': repo.get_commit(commit_idx=1).raw_id,
608 'diff2': repo.get_commit(commit_idx=1).raw_id,
@@ -598,11 +625,9 b' class TestFilesDiff:'
598 commit1 = backend.repo.get_commit(commit_idx=-1)
625 commit1 = backend.repo.get_commit(commit_idx=-1)
599 commit2 = backend.repo.get_commit(commit_idx=-2)
626 commit2 = backend.repo.get_commit(commit_idx=-2)
600 response = self.app.get(
627 response = self.app.get(
601 url(
628 route_path('repo_files_diff_2way_redirect',
602 controller='files',
629 repo_name=backend.repo_name,
603 action='diff_2way',
630 f_path='README'),
604 repo_name=backend.repo_name,
605 f_path='README'),
606 params={
631 params={
607 'diff1': commit2.raw_id,
632 'diff1': commit2.raw_id,
608 'diff2': commit1.raw_id,
633 'diff2': commit1.raw_id,
@@ -616,11 +641,9 b' class TestFilesDiff:'
616
641
617 def test_requires_one_commit_id(self, backend, autologin_user):
642 def test_requires_one_commit_id(self, backend, autologin_user):
618 response = self.app.get(
643 response = self.app.get(
619 url(
644 route_path('repo_files_diff',
620 controller='files',
645 repo_name=backend.repo_name,
621 action='diff',
646 f_path='README.rst'),
622 repo_name=backend.repo_name,
623 f_path='README.rst'),
624 status=400)
647 status=400)
625 response.mustcontain(
648 response.mustcontain(
626 'Need query parameter', 'diff1', 'diff2', 'to generate a diff.')
649 'Need query parameter', 'diff1', 'diff2', 'to generate a diff.')
@@ -628,30 +651,28 b' class TestFilesDiff:'
628 def test_returns_no_files_if_file_does_not_exist(self, vcsbackend):
651 def test_returns_no_files_if_file_does_not_exist(self, vcsbackend):
629 repo = vcsbackend.repo
652 repo = vcsbackend.repo
630 response = self.app.get(
653 response = self.app.get(
631 url(
654 route_path('repo_files_diff',
632 controller='files',
655 repo_name=repo.name,
633 action='diff',
656 f_path='does-not-exist-in-any-commit'),
634 repo_name=repo.name,
657 params={
635 f_path='does-not-exist-in-any-commit',
658 'diff1': repo[0].raw_id,
636 diff1=repo[0].raw_id,
659 'diff2': repo[1].raw_id
637 diff2=repo[1].raw_id),)
660 })
638
661
639 response = response.follow()
662 response = response.follow()
640 response.mustcontain('No files')
663 response.mustcontain('No files')
641
664
642 def test_returns_redirect_if_file_not_changed(self, backend):
665 def test_returns_redirect_if_file_not_changed(self, backend):
643 commit = backend.repo.get_commit(commit_idx=-1)
666 commit = backend.repo.get_commit(commit_idx=-1)
644 f_path = 'README'
645 response = self.app.get(
667 response = self.app.get(
646 url(
668 route_path('repo_files_diff_2way_redirect',
647 controller='files',
669 repo_name=backend.repo_name,
648 action='diff_2way',
670 f_path='README'),
649 repo_name=backend.repo_name,
671 params={
650 f_path=f_path,
672 'diff1': commit.raw_id,
651 diff1=commit.raw_id,
673 'diff2': commit.raw_id,
652 diff2=commit.raw_id,
674 })
653 ),
675
654 )
655 response = response.follow()
676 response = response.follow()
656 response.mustcontain('No files')
677 response.mustcontain('No files')
657 response.mustcontain('No commits in this compare')
678 response.mustcontain('No commits in this compare')
@@ -664,23 +685,14 b' class TestFilesDiff:'
664 commit_id_1 = '24'
685 commit_id_1 = '24'
665 commit_id_2 = '26'
686 commit_id_2 = '26'
666
687
667
668 print( url(
669 controller='files',
670 action='diff',
671 repo_name=repo.name,
672 f_path='trunk/example.py',
673 diff1='tags/v0.2/example.py@' + commit_id_1,
674 diff2=commit_id_2))
675
676 response = self.app.get(
688 response = self.app.get(
677 url(
689 route_path('repo_files_diff',
678 controller='files',
690 repo_name=backend_svn.repo_name,
679 action='diff',
691 f_path='trunk/example.py'),
680 repo_name=repo.name,
692 params={
681 f_path='trunk/example.py',
693 'diff1': 'tags/v0.2/example.py@' + commit_id_1,
682 diff1='tags/v0.2/example.py@' + commit_id_1,
694 'diff2': commit_id_2,
683 diff2=commit_id_2))
695 })
684
696
685 response = response.follow()
697 response = response.follow()
686 response.mustcontain(
698 response.mustcontain(
@@ -697,16 +709,17 b' class TestFilesDiff:'
697
709
698 repo = backend_svn['svn-simple-layout'].scm_instance()
710 repo = backend_svn['svn-simple-layout'].scm_instance()
699 commit_id = repo[-1].raw_id
711 commit_id = repo[-1].raw_id
712
700 response = self.app.get(
713 response = self.app.get(
701 url(
714 route_path('repo_files_diff',
702 controller='files',
715 repo_name=backend_svn.repo_name,
703 action='diff',
716 f_path='trunk/example.py'),
704 repo_name=repo.name,
717 params={
705 f_path='trunk/example.py',
718 'diff1': 'branches/argparse/example.py@' + commit_id,
706 diff1='branches/argparse/example.py@' + commit_id,
719 'diff2': commit_id,
707 diff2=commit_id),
720 },
708 params={'show_rev': 'Show at Revision'},
709 status=302)
721 status=302)
722 response = response.follow()
710 assert response.headers['Location'].endswith(
723 assert response.headers['Location'].endswith(
711 'svn-svn-simple-layout/files/26/branches/argparse/example.py')
724 'svn-svn-simple-layout/files/26/branches/argparse/example.py')
712
725
@@ -717,40 +730,39 b' class TestFilesDiff:'
717 repo = backend_svn['svn-simple-layout'].scm_instance()
730 repo = backend_svn['svn-simple-layout'].scm_instance()
718 commit_id = repo[-1].raw_id
731 commit_id = repo[-1].raw_id
719 response = self.app.get(
732 response = self.app.get(
720 url(
733 route_path('repo_files_diff',
721 controller='files',
734 repo_name=backend_svn.repo_name,
722 action='diff',
735 f_path='trunk/example.py'),
723 repo_name=repo.name,
724 f_path='trunk/example.py',
725 diff1='branches/argparse/example.py@' + commit_id,
726 diff2=commit_id),
727 params={
736 params={
737 'diff1': 'branches/argparse/example.py@' + commit_id,
738 'diff2': commit_id,
728 'show_rev': 'Show at Revision',
739 'show_rev': 'Show at Revision',
729 'annotate': 'true',
740 'annotate': 'true',
730 },
741 },
731 status=302)
742 status=302)
743 response = response.follow()
732 assert response.headers['Location'].endswith(
744 assert response.headers['Location'].endswith(
733 'svn-svn-simple-layout/annotate/26/branches/argparse/example.py')
745 'svn-svn-simple-layout/annotate/26/branches/argparse/example.py')
734
746
735
747
736 @pytest.mark.usefixtures("app", "autologin_user")
748 @pytest.mark.usefixtures("app", "autologin_user")
737 class TestChangingFiles:
749 class TestModifyFilesWithWebInterface(object):
738
750
739 def test_add_file_view(self, backend):
751 def test_add_file_view(self, backend):
740 self.app.get(url(
752 self.app.get(
741 'files_add_home',
753 route_path('repo_files_add_file',
742 repo_name=backend.repo_name,
754 repo_name=backend.repo_name,
743 revision='tip', f_path='/'))
755 commit_id='tip', f_path='/')
756 )
744
757
745 @pytest.mark.xfail_backends("svn", reason="Depends on online editing")
758 @pytest.mark.xfail_backends("svn", reason="Depends on online editing")
746 def test_add_file_into_repo_missing_content(self, backend, csrf_token):
759 def test_add_file_into_repo_missing_content(self, backend, csrf_token):
747 repo = backend.create_repo()
760 repo = backend.create_repo()
748 filename = 'init.py'
761 filename = 'init.py'
749 response = self.app.post(
762 response = self.app.post(
750 url(
763 route_path('repo_files_create_file',
751 'files_add',
764 repo_name=backend.repo_name,
752 repo_name=repo.repo_name,
765 commit_id='tip', f_path='/'),
753 revision='tip', f_path='/'),
754 params={
766 params={
755 'content': "",
767 'content': "",
756 'filename': filename,
768 'filename': filename,
@@ -759,14 +771,14 b' class TestChangingFiles:'
759 },
771 },
760 status=302)
772 status=302)
761 assert_session_flash(response,
773 assert_session_flash(response,
762 'Successfully committed new file `{}`'.format(os.path.join(filename)))
774 'Successfully committed new file `{}`'.format(
775 os.path.join(filename)))
763
776
764 def test_add_file_into_repo_missing_filename(self, backend, csrf_token):
777 def test_add_file_into_repo_missing_filename(self, backend, csrf_token):
765 response = self.app.post(
778 response = self.app.post(
766 url(
779 route_path('repo_files_create_file',
767 'files_add',
780 repo_name=backend.repo_name,
768 repo_name=backend.repo_name,
781 commit_id='tip', f_path='/'),
769 revision='tip', f_path='/'),
770 params={
782 params={
771 'content': "foo",
783 'content': "foo",
772 'csrf_token': csrf_token,
784 'csrf_token': csrf_token,
@@ -781,10 +793,9 b' class TestChangingFiles:'
781 # Create a file with no filename, it will display an error but
793 # Create a file with no filename, it will display an error but
782 # the repo has no commits yet
794 # the repo has no commits yet
783 response = self.app.post(
795 response = self.app.post(
784 url(
796 route_path('repo_files_create_file',
785 'files_add',
797 repo_name=repo.repo_name,
786 repo_name=repo.repo_name,
798 commit_id='tip', f_path='/'),
787 revision='tip', f_path='/'),
788 params={
799 params={
789 'content': "foo",
800 'content': "foo",
790 'csrf_token': csrf_token,
801 'csrf_token': csrf_token,
@@ -810,10 +821,9 b' class TestChangingFiles:'
810 def test_add_file_into_repo_bad_filenames(
821 def test_add_file_into_repo_bad_filenames(
811 self, location, filename, backend, csrf_token):
822 self, location, filename, backend, csrf_token):
812 response = self.app.post(
823 response = self.app.post(
813 url(
824 route_path('repo_files_create_file',
814 'files_add',
825 repo_name=backend.repo_name,
815 repo_name=backend.repo_name,
826 commit_id='tip', f_path='/'),
816 revision='tip', f_path='/'),
817 params={
827 params={
818 'content': "foo",
828 'content': "foo",
819 'filename': filename,
829 'filename': filename,
@@ -836,10 +846,9 b' class TestChangingFiles:'
836 csrf_token):
846 csrf_token):
837 repo = backend.create_repo()
847 repo = backend.create_repo()
838 response = self.app.post(
848 response = self.app.post(
839 url(
849 route_path('repo_files_create_file',
840 'files_add',
850 repo_name=repo.repo_name,
841 repo_name=repo.repo_name,
851 commit_id='tip', f_path='/'),
842 revision='tip', f_path='/'),
843 params={
852 params={
844 'content': "foo",
853 'content': "foo",
845 'filename': filename,
854 'filename': filename,
@@ -853,11 +862,10 b' class TestChangingFiles:'
853
862
854 def test_edit_file_view(self, backend):
863 def test_edit_file_view(self, backend):
855 response = self.app.get(
864 response = self.app.get(
856 url(
865 route_path('repo_files_edit_file',
857 'files_edit_home',
866 repo_name=backend.repo_name,
858 repo_name=backend.repo_name,
867 commit_id=backend.default_head_id,
859 revision=backend.default_head_id,
868 f_path='vcs/nodes.py'),
860 f_path='vcs/nodes.py'),
861 status=200)
869 status=200)
862 response.mustcontain("Module holding everything related to vcs nodes.")
870 response.mustcontain("Module holding everything related to vcs nodes.")
863
871
@@ -866,25 +874,24 b' class TestChangingFiles:'
866 backend.ensure_file("vcs/nodes.py")
874 backend.ensure_file("vcs/nodes.py")
867
875
868 response = self.app.get(
876 response = self.app.get(
869 url(
877 route_path('repo_files_edit_file',
870 'files_edit_home',
878 repo_name=repo.repo_name,
871 repo_name=repo.repo_name,
879 commit_id='tip',
872 revision='tip', f_path='vcs/nodes.py'),
880 f_path='vcs/nodes.py'),
873 status=302)
881 status=302)
874 assert_session_flash(
882 assert_session_flash(
875 response,
883 response,
876 'You can only edit files with revision being a valid branch')
884 'You can only edit files with commit being a valid branch')
877
885
878 def test_edit_file_view_commit_changes(self, backend, csrf_token):
886 def test_edit_file_view_commit_changes(self, backend, csrf_token):
879 repo = backend.create_repo()
887 repo = backend.create_repo()
880 backend.ensure_file("vcs/nodes.py", content="print 'hello'")
888 backend.ensure_file("vcs/nodes.py", content="print 'hello'")
881
889
882 response = self.app.post(
890 response = self.app.post(
883 url(
891 route_path('repo_files_update_file',
884 'files_edit',
892 repo_name=repo.repo_name,
885 repo_name=repo.repo_name,
893 commit_id=backend.default_head_id,
886 revision=backend.default_head_id,
894 f_path='vcs/nodes.py'),
887 f_path='vcs/nodes.py'),
888 params={
895 params={
889 'content': "print 'hello world'",
896 'content': "print 'hello world'",
890 'message': 'I committed',
897 'message': 'I committed',
@@ -907,11 +914,10 b' class TestChangingFiles:'
907 backend.repo.scm_instance().commit_ids[-1])
914 backend.repo.scm_instance().commit_ids[-1])
908
915
909 response = self.app.post(
916 response = self.app.post(
910 url(
917 route_path('repo_files_update_file',
911 'files_edit',
918 repo_name=repo.repo_name,
912 repo_name=repo.repo_name,
919 commit_id=commit_id,
913 revision=commit_id,
920 f_path='vcs/nodes.py'),
914 f_path='vcs/nodes.py'),
915 params={
921 params={
916 'content': "print 'hello world'",
922 'content': "print 'hello world'",
917 'message': '',
923 'message': '',
@@ -925,35 +931,36 b' class TestChangingFiles:'
925 assert tip.message == 'Edited file vcs/nodes.py via RhodeCode Enterprise'
931 assert tip.message == 'Edited file vcs/nodes.py via RhodeCode Enterprise'
926
932
927 def test_delete_file_view(self, backend):
933 def test_delete_file_view(self, backend):
928 self.app.get(url(
934 self.app.get(
929 'files_delete_home',
935 route_path('repo_files_remove_file',
930 repo_name=backend.repo_name,
936 repo_name=backend.repo_name,
931 revision='tip', f_path='vcs/nodes.py'))
937 commit_id=backend.default_head_id,
938 f_path='vcs/nodes.py'),
939 status=200)
932
940
933 def test_delete_file_view_not_on_branch(self, backend):
941 def test_delete_file_view_not_on_branch(self, backend):
934 repo = backend.create_repo()
942 repo = backend.create_repo()
935 backend.ensure_file('vcs/nodes.py')
943 backend.ensure_file('vcs/nodes.py')
936
944
937 response = self.app.get(
945 response = self.app.get(
938 url(
946 route_path('repo_files_remove_file',
939 'files_delete_home',
947 repo_name=repo.repo_name,
940 repo_name=repo.repo_name,
948 commit_id='tip',
941 revision='tip', f_path='vcs/nodes.py'),
949 f_path='vcs/nodes.py'),
942 status=302)
950 status=302)
943 assert_session_flash(
951 assert_session_flash(
944 response,
952 response,
945 'You can only delete files with revision being a valid branch')
953 'You can only delete files with commit being a valid branch')
946
954
947 def test_delete_file_view_commit_changes(self, backend, csrf_token):
955 def test_delete_file_view_commit_changes(self, backend, csrf_token):
948 repo = backend.create_repo()
956 repo = backend.create_repo()
949 backend.ensure_file("vcs/nodes.py")
957 backend.ensure_file("vcs/nodes.py")
950
958
951 response = self.app.post(
959 response = self.app.post(
952 url(
960 route_path('repo_files_delete_file',
953 'files_delete_home',
961 repo_name=repo.repo_name,
954 repo_name=repo.repo_name,
962 commit_id=backend.default_head_id,
955 revision=backend.default_head_id,
963 f_path='vcs/nodes.py'),
956 f_path='vcs/nodes.py'),
957 params={
964 params={
958 'message': 'i commited',
965 'message': 'i commited',
959 'csrf_token': csrf_token,
966 'csrf_token': csrf_token,
@@ -963,25 +970,93 b' class TestChangingFiles:'
963 response, 'Successfully deleted file `vcs/nodes.py`')
970 response, 'Successfully deleted file `vcs/nodes.py`')
964
971
965
972
966 def assert_files_in_response(response, files, params):
973 @pytest.mark.usefixtures("app")
967 template = (
974 class TestFilesViewOtherCases(object):
968 'href="/%(repo_name)s/files/%(commit_id)s/%(name)s"')
975
969 _assert_items_in_response(response, files, template, params)
976 def test_access_empty_repo_redirect_to_summary_with_alert_write_perms(
977 self, backend_stub, autologin_regular_user, user_regular,
978 user_util):
979
980 repo = backend_stub.create_repo()
981 user_util.grant_user_permission_to_repo(
982 repo, user_regular, 'repository.write')
983 response = self.app.get(
984 route_path('repo_files',
985 repo_name=repo.repo_name,
986 commit_id='tip', f_path='/'))
987
988 repo_file_add_url = route_path(
989 'repo_files_add_file',
990 repo_name=repo.repo_name,
991 commit_id=0, f_path='') + '#edit'
992
993 assert_session_flash(
994 response,
995 'There are no files yet. <a class="alert-link" '
996 'href="{}">Click here to add a new file.</a>'
997 .format(repo_file_add_url))
998
999 def test_access_empty_repo_redirect_to_summary_with_alert_no_write_perms(
1000 self, backend_stub, user_util):
1001 repo = backend_stub.create_repo()
1002 repo_file_add_url = route_path(
1003 'repo_files_add_file',
1004 repo_name=repo.repo_name,
1005 commit_id=0, f_path='') + '#edit'
1006
1007 response = self.app.get(
1008 route_path('repo_files',
1009 repo_name=repo.repo_name,
1010 commit_id='tip', f_path='/'))
1011
1012 assert_session_flash(response, no_=repo_file_add_url)
1013
1014 @pytest.mark.parametrize('file_node', [
1015 'archive/file.zip',
1016 'diff/my-file.txt',
1017 'render.py',
1018 'render',
1019 'remove_file',
1020 'remove_file/to-delete.txt',
1021 ])
1022 def test_file_names_equal_to_routes_parts(self, backend, file_node):
1023 backend.create_repo()
1024 backend.ensure_file(file_node)
1025
1026 self.app.get(
1027 route_path('repo_files',
1028 repo_name=backend.repo_name,
1029 commit_id='tip', f_path=file_node),
1030 status=200)
970
1031
971
1032
972 def assert_dirs_in_response(response, dirs, params):
1033 class TestAdjustFilePathForSvn(object):
973 template = (
1034 """
974 'href="/%(repo_name)s/files/%(commit_id)s/%(name)s"')
1035 SVN specific adjustments of node history in RepoFilesView.
975 _assert_items_in_response(response, dirs, template, params)
1036 """
976
1037
1038 def test_returns_path_relative_to_matched_reference(self):
1039 repo = self._repo(branches=['trunk'])
1040 self.assert_file_adjustment('trunk/file', 'file', repo)
1041
1042 def test_does_not_modify_file_if_no_reference_matches(self):
1043 repo = self._repo(branches=['trunk'])
1044 self.assert_file_adjustment('notes/file', 'notes/file', repo)
977
1045
978 def _assert_items_in_response(response, items, template, params):
1046 def test_does_not_adjust_partial_directory_names(self):
979 for item in items:
1047 repo = self._repo(branches=['trun'])
980 item_params = {'name': item}
1048 self.assert_file_adjustment('trunk/file', 'trunk/file', repo)
981 item_params.update(params)
1049
982 response.mustcontain(template % item_params)
1050 def test_is_robust_to_patterns_which_prefix_other_patterns(self):
1051 repo = self._repo(branches=['trunk', 'trunk/new', 'trunk/old'])
1052 self.assert_file_adjustment('trunk/new/file', 'file', repo)
983
1053
1054 def assert_file_adjustment(self, f_path, expected, repo):
1055 result = RepoFilesView.adjust_file_path_for_svn(f_path, repo)
1056 assert result == expected
984
1057
985 def assert_timeago_in_response(response, items, params):
1058 def _repo(self, branches=None):
986 for item in items:
1059 repo = mock.Mock()
987 response.mustcontain(h.age_component(params['date']))
1060 repo.branches = OrderedDict((name, '0') for name in branches or [])
1061 repo.tags = {}
1062 return repo
@@ -384,7 +384,7 b' class TestCreateReferenceData(object):'
384
384
385 class TestCreateFilesUrl(object):
385 class TestCreateFilesUrl(object):
386
386
387 def test_creates_non_svn_url(self, summary_view):
387 def test_creates_non_svn_url(self, app, summary_view):
388 repo = mock.Mock()
388 repo = mock.Mock()
389 repo.name = 'abcde'
389 repo.name = 'abcde'
390 full_repo_name = 'test-repo-group/' + repo.name
390 full_repo_name = 'test-repo-group/' + repo.name
@@ -392,15 +392,15 b' class TestCreateFilesUrl(object):'
392 raw_id = 'deadbeef0123456789'
392 raw_id = 'deadbeef0123456789'
393 is_svn = False
393 is_svn = False
394
394
395 with mock.patch('rhodecode.lib.helpers.url') as url_mock:
395 with mock.patch('rhodecode.lib.helpers.route_path') as url_mock:
396 result = summary_view._create_files_url(
396 result = summary_view._create_files_url(
397 repo, full_repo_name, ref_name, raw_id, is_svn)
397 repo, full_repo_name, ref_name, raw_id, is_svn)
398 url_mock.assert_called_once_with(
398 url_mock.assert_called_once_with(
399 'files_home', repo_name=full_repo_name, f_path='',
399 'repo_files', repo_name=full_repo_name, commit_id=ref_name,
400 revision=ref_name, at=ref_name)
400 f_path='', _query=dict(at=ref_name))
401 assert result == url_mock.return_value
401 assert result == url_mock.return_value
402
402
403 def test_creates_svn_url(self, summary_view):
403 def test_creates_svn_url(self, app, summary_view):
404 repo = mock.Mock()
404 repo = mock.Mock()
405 repo.name = 'abcde'
405 repo.name = 'abcde'
406 full_repo_name = 'test-repo-group/' + repo.name
406 full_repo_name = 'test-repo-group/' + repo.name
@@ -408,15 +408,15 b' class TestCreateFilesUrl(object):'
408 raw_id = 'deadbeef0123456789'
408 raw_id = 'deadbeef0123456789'
409 is_svn = True
409 is_svn = True
410
410
411 with mock.patch('rhodecode.lib.helpers.url') as url_mock:
411 with mock.patch('rhodecode.lib.helpers.route_path') as url_mock:
412 result = summary_view._create_files_url(
412 result = summary_view._create_files_url(
413 repo, full_repo_name, ref_name, raw_id, is_svn)
413 repo, full_repo_name, ref_name, raw_id, is_svn)
414 url_mock.assert_called_once_with(
414 url_mock.assert_called_once_with(
415 'files_home', repo_name=full_repo_name, f_path=ref_name,
415 'repo_files', repo_name=full_repo_name, f_path=ref_name,
416 revision=raw_id, at=ref_name)
416 commit_id=raw_id, _query=dict(at=ref_name))
417 assert result == url_mock.return_value
417 assert result == url_mock.return_value
418
418
419 def test_name_has_slashes(self, summary_view):
419 def test_name_has_slashes(self, app, summary_view):
420 repo = mock.Mock()
420 repo = mock.Mock()
421 repo.name = 'abcde'
421 repo.name = 'abcde'
422 full_repo_name = 'test-repo-group/' + repo.name
422 full_repo_name = 'test-repo-group/' + repo.name
@@ -424,12 +424,12 b' class TestCreateFilesUrl(object):'
424 raw_id = 'deadbeef0123456789'
424 raw_id = 'deadbeef0123456789'
425 is_svn = False
425 is_svn = False
426
426
427 with mock.patch('rhodecode.lib.helpers.url') as url_mock:
427 with mock.patch('rhodecode.lib.helpers.route_path') as url_mock:
428 result = summary_view._create_files_url(
428 result = summary_view._create_files_url(
429 repo, full_repo_name, ref_name, raw_id, is_svn)
429 repo, full_repo_name, ref_name, raw_id, is_svn)
430 url_mock.assert_called_once_with(
430 url_mock.assert_called_once_with(
431 'files_home', repo_name=full_repo_name, f_path='', revision=raw_id,
431 'repo_files', repo_name=full_repo_name, commit_id=raw_id,
432 at=ref_name)
432 f_path='', _query=dict(at=ref_name))
433 assert result == url_mock.return_value
433 assert result == url_mock.return_value
434
434
435
435
@@ -74,10 +74,9 b' class RepoSummaryView(RepoAppView):'
74 log.debug("Searching for a README file.")
74 log.debug("Searching for a README file.")
75 readme_node = ReadmeFinder(default_renderer).search(commit)
75 readme_node = ReadmeFinder(default_renderer).search(commit)
76 if readme_node:
76 if readme_node:
77 relative_url = h.url('files_raw_home',
77 relative_url = h.route_path(
78 repo_name=repo_name,
78 'repo_file_raw', repo_name=repo_name,
79 revision=commit.raw_id,
79 commit_id=commit.raw_id, f_path=readme_node.path)
80 f_path=readme_node.path)
81 readme_data = self._render_readme_or_none(
80 readme_data = self._render_readme_or_none(
82 commit, readme_node, relative_url)
81 commit, readme_node, relative_url)
83 readme_filename = readme_node.path
82 readme_filename = readme_node.path
@@ -360,9 +359,9 b' class RepoSummaryView(RepoAppView):'
360
359
361 def _create_files_url(self, repo, full_repo_name, ref_name, raw_id, is_svn):
360 def _create_files_url(self, repo, full_repo_name, ref_name, raw_id, is_svn):
362 use_commit_id = '/' in ref_name or is_svn
361 use_commit_id = '/' in ref_name or is_svn
363 return h.url(
362 return h.route_path(
364 'files_home',
363 'repo_files',
365 repo_name=full_repo_name,
364 repo_name=full_repo_name,
366 f_path=ref_name if is_svn else '',
365 f_path=ref_name if is_svn else '',
367 revision=raw_id if use_commit_id else ref_name,
366 commit_id=raw_id if use_commit_id else ref_name,
368 at=ref_name)
367 _query=dict(at=ref_name))
@@ -153,6 +153,7 b' def make_pyramid_app(global_config, **se'
153
153
154 includeme_first(config)
154 includeme_first(config)
155 includeme(config)
155 includeme(config)
156
156 pyramid_app = config.make_wsgi_app()
157 pyramid_app = config.make_wsgi_app()
157 pyramid_app = wrap_app_in_wsgi_middlewares(pyramid_app, config)
158 pyramid_app = wrap_app_in_wsgi_middlewares(pyramid_app, config)
158 pyramid_app.config = config
159 pyramid_app.config = config
@@ -726,132 +726,6 b' def make_map(config):'
726 conditions={'function': check_repo},
726 conditions={'function': check_repo},
727 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
727 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
728
728
729 rmap.connect('files_home', '/{repo_name}/files/{revision}/{f_path}',
730 controller='files', revision='tip', f_path='',
731 conditions={'function': check_repo},
732 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
733
734 rmap.connect('files_home_simple_catchrev',
735 '/{repo_name}/files/{revision}',
736 controller='files', revision='tip', f_path='',
737 conditions={'function': check_repo},
738 requirements=URL_NAME_REQUIREMENTS)
739
740 rmap.connect('files_home_simple_catchall',
741 '/{repo_name}/files',
742 controller='files', revision='tip', f_path='',
743 conditions={'function': check_repo},
744 requirements=URL_NAME_REQUIREMENTS)
745
746 rmap.connect('files_history_home',
747 '/{repo_name}/history/{revision}/{f_path}',
748 controller='files', action='history', revision='tip', f_path='',
749 conditions={'function': check_repo},
750 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
751
752 rmap.connect('files_authors_home',
753 '/{repo_name}/authors/{revision}/{f_path}',
754 controller='files', action='authors', revision='tip', f_path='',
755 conditions={'function': check_repo},
756 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
757
758 rmap.connect('files_diff_home', '/{repo_name}/diff/{f_path}',
759 controller='files', action='diff', f_path='',
760 conditions={'function': check_repo},
761 requirements=URL_NAME_REQUIREMENTS)
762
763 rmap.connect('files_diff_2way_home',
764 '/{repo_name}/diff-2way/{f_path}',
765 controller='files', action='diff_2way', f_path='',
766 conditions={'function': check_repo},
767 requirements=URL_NAME_REQUIREMENTS)
768
769 rmap.connect('files_rawfile_home',
770 '/{repo_name}/rawfile/{revision}/{f_path}',
771 controller='files', action='rawfile', revision='tip',
772 f_path='', conditions={'function': check_repo},
773 requirements=URL_NAME_REQUIREMENTS)
774
775 rmap.connect('files_raw_home',
776 '/{repo_name}/raw/{revision}/{f_path}',
777 controller='files', action='raw', revision='tip', f_path='',
778 conditions={'function': check_repo},
779 requirements=URL_NAME_REQUIREMENTS)
780
781 rmap.connect('files_render_home',
782 '/{repo_name}/render/{revision}/{f_path}',
783 controller='files', action='index', revision='tip', f_path='',
784 rendered=True, conditions={'function': check_repo},
785 requirements=URL_NAME_REQUIREMENTS)
786
787 rmap.connect('files_annotate_home',
788 '/{repo_name}/annotate/{revision}/{f_path}',
789 controller='files', action='index', revision='tip',
790 f_path='', annotate=True, conditions={'function': check_repo},
791 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
792
793 rmap.connect('files_annotate_previous',
794 '/{repo_name}/annotate-previous/{revision}/{f_path}',
795 controller='files', action='annotate_previous', revision='tip',
796 f_path='', annotate=True, conditions={'function': check_repo},
797 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
798
799 rmap.connect('files_edit',
800 '/{repo_name}/edit/{revision}/{f_path}',
801 controller='files', action='edit', revision='tip',
802 f_path='',
803 conditions={'function': check_repo, 'method': ['POST']},
804 requirements=URL_NAME_REQUIREMENTS)
805
806 rmap.connect('files_edit_home',
807 '/{repo_name}/edit/{revision}/{f_path}',
808 controller='files', action='edit_home', revision='tip',
809 f_path='', conditions={'function': check_repo},
810 requirements=URL_NAME_REQUIREMENTS)
811
812 rmap.connect('files_add',
813 '/{repo_name}/add/{revision}/{f_path}',
814 controller='files', action='add', revision='tip',
815 f_path='',
816 conditions={'function': check_repo, 'method': ['POST']},
817 requirements=URL_NAME_REQUIREMENTS)
818
819 rmap.connect('files_add_home',
820 '/{repo_name}/add/{revision}/{f_path}',
821 controller='files', action='add_home', revision='tip',
822 f_path='', conditions={'function': check_repo},
823 requirements=URL_NAME_REQUIREMENTS)
824
825 rmap.connect('files_delete',
826 '/{repo_name}/delete/{revision}/{f_path}',
827 controller='files', action='delete', revision='tip',
828 f_path='',
829 conditions={'function': check_repo, 'method': ['POST']},
830 requirements=URL_NAME_REQUIREMENTS)
831
832 rmap.connect('files_delete_home',
833 '/{repo_name}/delete/{revision}/{f_path}',
834 controller='files', action='delete_home', revision='tip',
835 f_path='', conditions={'function': check_repo},
836 requirements=URL_NAME_REQUIREMENTS)
837
838 rmap.connect('files_archive_home', '/{repo_name}/archive/{fname}',
839 controller='files', action='archivefile',
840 conditions={'function': check_repo},
841 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
842
843 rmap.connect('files_nodelist_home',
844 '/{repo_name}/nodelist/{revision}/{f_path}',
845 controller='files', action='nodelist',
846 conditions={'function': check_repo},
847 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
848
849 rmap.connect('files_nodetree_full',
850 '/{repo_name}/nodetree_full/{commit_id}/{f_path}',
851 controller='files', action='nodetree_full',
852 conditions={'function': check_repo},
853 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
854
855 rmap.connect('repo_fork_create_home', '/{repo_name}/fork',
729 rmap.connect('repo_fork_create_home', '/{repo_name}/fork',
856 controller='forks', action='fork_create',
730 controller='forks', action='fork_create',
857 conditions={'function': check_repo, 'method': ['POST']},
731 conditions={'function': check_repo, 'method': ['POST']},
@@ -258,9 +258,10 b' def files_breadcrumbs(repo_name, commit_'
258 url_segments = [
258 url_segments = [
259 link_to(
259 link_to(
260 repo_name_html,
260 repo_name_html,
261 url('files_home',
261 route_path(
262 'repo_files',
262 repo_name=repo_name,
263 repo_name=repo_name,
263 revision=commit_id,
264 commit_id=commit_id,
264 f_path=''),
265 f_path=''),
265 class_='pjax-link')]
266 class_='pjax-link')]
266
267
@@ -274,9 +275,10 b' def files_breadcrumbs(repo_name, commit_'
274 url_segments.append(
275 url_segments.append(
275 link_to(
276 link_to(
276 segment_html,
277 segment_html,
277 url('files_home',
278 route_path(
279 'repo_files',
278 repo_name=repo_name,
280 repo_name=repo_name,
279 revision=commit_id,
281 commit_id=commit_id,
280 f_path='/'.join(path_segments[:cnt + 1])),
282 f_path='/'.join(path_segments[:cnt + 1])),
281 class_='pjax-link'))
283 class_='pjax-link'))
282 else:
284 else:
@@ -1541,6 +1543,9 b' def format_byte_size_binary(file_size):'
1541 """
1543 """
1542 Formats file/folder sizes to standard.
1544 Formats file/folder sizes to standard.
1543 """
1545 """
1546 if file_size is None:
1547 file_size = 0
1548
1544 formatted_size = format_byte_size(file_size, binary=True)
1549 formatted_size = format_byte_size(file_size, binary=True)
1545 return formatted_size
1550 return formatted_size
1546
1551
@@ -1740,8 +1745,9 b' def render_binary(repo_name, file_obj):'
1740 for ext in ['*.png', '*.jpg', '*.ico', '*.gif']:
1745 for ext in ['*.png', '*.jpg', '*.ico', '*.gif']:
1741 if fnmatch.fnmatch(filename, pat=ext):
1746 if fnmatch.fnmatch(filename, pat=ext):
1742 alt = filename
1747 alt = filename
1743 src = url('files_raw_home', repo_name=repo_name,
1748 src = route_path(
1744 revision=file_obj.commit.raw_id, f_path=file_obj.path)
1749 'repo_file_raw', repo_name=repo_name,
1750 commit_id=file_obj.commit.raw_id, f_path=file_obj.path)
1745 return literal('<img class="rendered-binary" alt="{}" src="{}">'.format(alt, src))
1751 return literal('<img class="rendered-binary" alt="{}" src="{}">'.format(alt, src))
1746
1752
1747
1753
@@ -70,20 +70,20 b' function setRCMouseBindings(repoName, re'
70 });
70 });
71 Mousetrap.bind(['g F'], function(e) {
71 Mousetrap.bind(['g F'], function(e) {
72 window.location = pyroutes.url(
72 window.location = pyroutes.url(
73 'files_home',
73 'repo_files',
74 {
74 {
75 'repo_name': repoName,
75 'repo_name': repoName,
76 'revision': repoLandingRev,
76 'commit_id': repoLandingRev,
77 'f_path': '',
77 'f_path': '',
78 'search': '1'
78 'search': '1'
79 });
79 });
80 });
80 });
81 Mousetrap.bind(['g f'], function(e) {
81 Mousetrap.bind(['g f'], function(e) {
82 window.location = pyroutes.url(
82 window.location = pyroutes.url(
83 'files_home',
83 'repo_files',
84 {
84 {
85 'repo_name': repoName,
85 'repo_name': repoName,
86 'revision': repoLandingRev,
86 'commit_id': repoLandingRev,
87 'f_path': ''
87 'f_path': ''
88 });
88 });
89 });
89 });
@@ -33,14 +33,6 b' function registerRCRoutes() {'
33 pyroutes.register('changelog_home', '/%(repo_name)s/changelog', ['repo_name']);
33 pyroutes.register('changelog_home', '/%(repo_name)s/changelog', ['repo_name']);
34 pyroutes.register('changelog_file_home', '/%(repo_name)s/changelog/%(revision)s/%(f_path)s', ['repo_name', 'revision', 'f_path']);
34 pyroutes.register('changelog_file_home', '/%(repo_name)s/changelog/%(revision)s/%(f_path)s', ['repo_name', 'revision', 'f_path']);
35 pyroutes.register('changelog_elements', '/%(repo_name)s/changelog_details', ['repo_name']);
35 pyroutes.register('changelog_elements', '/%(repo_name)s/changelog_details', ['repo_name']);
36 pyroutes.register('files_home', '/%(repo_name)s/files/%(revision)s/%(f_path)s', ['repo_name', 'revision', 'f_path']);
37 pyroutes.register('files_history_home', '/%(repo_name)s/history/%(revision)s/%(f_path)s', ['repo_name', 'revision', 'f_path']);
38 pyroutes.register('files_authors_home', '/%(repo_name)s/authors/%(revision)s/%(f_path)s', ['repo_name', 'revision', 'f_path']);
39 pyroutes.register('files_annotate_home', '/%(repo_name)s/annotate/%(revision)s/%(f_path)s', ['repo_name', 'revision', 'f_path']);
40 pyroutes.register('files_annotate_previous', '/%(repo_name)s/annotate-previous/%(revision)s/%(f_path)s', ['repo_name', 'revision', 'f_path']);
41 pyroutes.register('files_archive_home', '/%(repo_name)s/archive/%(fname)s', ['repo_name', 'fname']);
42 pyroutes.register('files_nodelist_home', '/%(repo_name)s/nodelist/%(revision)s/%(f_path)s', ['repo_name', 'revision', 'f_path']);
43 pyroutes.register('files_nodetree_full', '/%(repo_name)s/nodetree_full/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
44 pyroutes.register('favicon', '/favicon.ico', []);
36 pyroutes.register('favicon', '/favicon.ico', []);
45 pyroutes.register('robots', '/robots.txt', []);
37 pyroutes.register('robots', '/robots.txt', []);
46 pyroutes.register('auth_home', '/_admin/auth*traverse', []);
38 pyroutes.register('auth_home', '/_admin/auth*traverse', []);
@@ -106,6 +98,29 b' function registerRCRoutes() {'
106 pyroutes.register('repo_summary_explicit', '/%(repo_name)s/summary', ['repo_name']);
98 pyroutes.register('repo_summary_explicit', '/%(repo_name)s/summary', ['repo_name']);
107 pyroutes.register('repo_summary_commits', '/%(repo_name)s/summary-commits', ['repo_name']);
99 pyroutes.register('repo_summary_commits', '/%(repo_name)s/summary-commits', ['repo_name']);
108 pyroutes.register('repo_commit', '/%(repo_name)s/changeset/%(commit_id)s', ['repo_name', 'commit_id']);
100 pyroutes.register('repo_commit', '/%(repo_name)s/changeset/%(commit_id)s', ['repo_name', 'commit_id']);
101 pyroutes.register('repo_archivefile', '/%(repo_name)s/archive/%(fname)s', ['repo_name', 'fname']);
102 pyroutes.register('repo_files_diff', '/%(repo_name)s/diff/%(f_path)s', ['repo_name', 'f_path']);
103 pyroutes.register('repo_files_diff_2way_redirect', '/%(repo_name)s/diff-2way/%(f_path)s', ['repo_name', 'f_path']);
104 pyroutes.register('repo_files', '/%(repo_name)s/files/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
105 pyroutes.register('repo_files:default_path', '/%(repo_name)s/files/%(commit_id)s/', ['repo_name', 'commit_id']);
106 pyroutes.register('repo_files:default_commit', '/%(repo_name)s/files', ['repo_name']);
107 pyroutes.register('repo_files:rendered', '/%(repo_name)s/render/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
108 pyroutes.register('repo_files:annotated', '/%(repo_name)s/annotate/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
109 pyroutes.register('repo_files:annotated_previous', '/%(repo_name)s/annotate-previous/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
110 pyroutes.register('repo_nodetree_full', '/%(repo_name)s/nodetree_full/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
111 pyroutes.register('repo_nodetree_full:default_path', '/%(repo_name)s/nodetree_full/%(commit_id)s/', ['repo_name', 'commit_id']);
112 pyroutes.register('repo_files_nodelist', '/%(repo_name)s/nodelist/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
113 pyroutes.register('repo_file_raw', '/%(repo_name)s/raw/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
114 pyroutes.register('repo_file_download', '/%(repo_name)s/download/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
115 pyroutes.register('repo_file_download:legacy', '/%(repo_name)s/rawfile/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
116 pyroutes.register('repo_file_history', '/%(repo_name)s/history/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
117 pyroutes.register('repo_file_authors', '/%(repo_name)s/authors/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
118 pyroutes.register('repo_files_remove_file', '/%(repo_name)s/remove_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
119 pyroutes.register('repo_files_delete_file', '/%(repo_name)s/delete_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
120 pyroutes.register('repo_files_edit_file', '/%(repo_name)s/edit_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
121 pyroutes.register('repo_files_update_file', '/%(repo_name)s/update_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
122 pyroutes.register('repo_files_add_file', '/%(repo_name)s/add_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
123 pyroutes.register('repo_files_create_file', '/%(repo_name)s/create_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
109 pyroutes.register('repo_refs_data', '/%(repo_name)s/refs-data', ['repo_name']);
124 pyroutes.register('repo_refs_data', '/%(repo_name)s/refs-data', ['repo_name']);
110 pyroutes.register('repo_refs_changelog_data', '/%(repo_name)s/refs-data-changelog', ['repo_name']);
125 pyroutes.register('repo_refs_changelog_data', '/%(repo_name)s/refs-data-changelog', ['repo_name']);
111 pyroutes.register('repo_stats', '/%(repo_name)s/repo_stats/%(commit_id)s', ['repo_name', 'commit_id']);
126 pyroutes.register('repo_stats', '/%(repo_name)s/repo_stats/%(commit_id)s', ['repo_name', 'commit_id']);
@@ -77,8 +77,8 b' var select2RefSwitcher = function(target'
77 };
77 };
78
78
79 var select2FileHistorySwitcher = function(targetElement, initialData, state) {
79 var select2FileHistorySwitcher = function(targetElement, initialData, state) {
80 var loadUrl = pyroutes.url('files_history_home',
80 var loadUrl = pyroutes.url('repo_file_history',
81 {'repo_name': templateContext.repo_name, 'revision': state.rev,
81 {'repo_name': templateContext.repo_name, 'commit_id': state.rev,
82 'f_path': state.f_path});
82 'f_path': state.f_path});
83 select2RefBaseSwitcher(targetElement, loadUrl, initialData);
83 select2RefBaseSwitcher(targetElement, loadUrl, initialData);
84 };
84 };
@@ -227,7 +227,7 b''
227 <ul id="context-pages" class="horizontal-list navigation">
227 <ul id="context-pages" class="horizontal-list navigation">
228 <li class="${is_active('summary')}"><a class="menulink" href="${h.route_path('repo_summary', repo_name=c.repo_name)}"><div class="menulabel">${_('Summary')}</div></a></li>
228 <li class="${is_active('summary')}"><a class="menulink" href="${h.route_path('repo_summary', repo_name=c.repo_name)}"><div class="menulabel">${_('Summary')}</div></a></li>
229 <li class="${is_active('changelog')}"><a class="menulink" href="${h.url('changelog_home', repo_name=c.repo_name)}"><div class="menulabel">${_('Changelog')}</div></a></li>
229 <li class="${is_active('changelog')}"><a class="menulink" href="${h.url('changelog_home', repo_name=c.repo_name)}"><div class="menulabel">${_('Changelog')}</div></a></li>
230 <li class="${is_active('files')}"><a class="menulink" href="${h.url('files_home', repo_name=c.repo_name, revision=c.rhodecode_db_repo.landing_rev[1])}"><div class="menulabel">${_('Files')}</div></a></li>
230 <li class="${is_active('files')}"><a class="menulink" href="${h.route_path('repo_files', repo_name=c.repo_name, commit_id=c.rhodecode_db_repo.landing_rev[1], f_path='')}"><div class="menulabel">${_('Files')}</div></a></li>
231 <li class="${is_active('compare')}">
231 <li class="${is_active('compare')}">
232 <a class="menulink" href="${h.url('compare_home',repo_name=c.repo_name)}"><div class="menulabel">${_('Compare')}</div></a>
232 <a class="menulink" href="${h.url('compare_home',repo_name=c.repo_name)}"><div class="menulabel">${_('Compare')}</div></a>
233 </li>
233 </li>
@@ -28,6 +28,6 b''
28
28
29 <%def name="commit(message, commit_id, commit_idx)">
29 <%def name="commit(message, commit_id, commit_idx)">
30 <div>
30 <div>
31 <pre><a title="${h.tooltip(message)}" href="${h.url('files_home',repo_name=c.repo_name,revision=commit_id)}">r${commit_idx}:${h.short_id(commit_id)}</a></pre>
31 <pre><a title="${h.tooltip(message)}" href="${h.route_path('repo_files:default_path',repo_name=c.repo_name,commit_id=commit_id)}">r${commit_idx}:${h.short_id(commit_id)}</a></pre>
32 </div>
32 </div>
33 </%def>
33 </%def>
@@ -28,6 +28,6 b''
28
28
29 <%def name="commit(message, commit_id, commit_idx)">
29 <%def name="commit(message, commit_id, commit_idx)">
30 <div>
30 <div>
31 <pre><a title="${h.tooltip(message)}" href="${h.url('files_home',repo_name=c.repo_name,revision=commit_id)}">r${commit_idx}:${h.short_id(commit_id)}</a></pre>
31 <pre><a title="${h.tooltip(message)}" href="${h.route_path('repo_files:default_path',repo_name=c.repo_name,commit_id=commit_id)}">r${commit_idx}:${h.short_id(commit_id)}</a></pre>
32 </div>
32 </div>
33 </%def>
33 </%def>
@@ -109,7 +109,7 b''
109 %if h.is_hg(c.rhodecode_repo):
109 %if h.is_hg(c.rhodecode_repo):
110 %for book in commit.bookmarks:
110 %for book in commit.bookmarks:
111 <span class="tag booktag" title="${h.tooltip(_('Bookmark %s') % book)}">
111 <span class="tag booktag" title="${h.tooltip(_('Bookmark %s') % book)}">
112 <a href="${h.url('files_home',repo_name=c.repo_name,revision=commit.raw_id)}"><i class="icon-bookmark"></i>${h.shorter(book)}</a>
112 <a href="${h.route_path('repo_files:default_path',repo_name=c.repo_name,commit_id=commit.raw_id, _query=dict(at=book))}"><i class="icon-bookmark"></i>${h.shorter(book)}</a>
113 </span>
113 </span>
114 %endfor
114 %endfor
115 %endif
115 %endif
@@ -117,7 +117,7 b''
117 ## tags
117 ## tags
118 %for tag in commit.tags:
118 %for tag in commit.tags:
119 <span class="tag tagtag" title="${h.tooltip(_('Tag %s') % tag)}">
119 <span class="tag tagtag" title="${h.tooltip(_('Tag %s') % tag)}">
120 <a href="${h.url('files_home',repo_name=c.repo_name,revision=commit.raw_id)}"><i class="icon-tag"></i>${h.shorter(tag)}</a>
120 <a href="${h.route_path('repo_files:default_path',repo_name=c.repo_name,commit_id=commit.raw_id, _query=dict(at=tag))}"><i class="icon-tag"></i>${h.shorter(tag)}</a>
121 </span>
121 </span>
122 %endfor
122 %endfor
123
123
@@ -29,7 +29,7 b''
29 </code>
29 </code>
30 </td>
30 </td>
31 <td class="td-actions">
31 <td class="td-actions">
32 <a href="${h.url('files_home',repo_name=c.repo_name,f_path=c.changelog_for_path,revision=cs.raw_id)}">
32 <a href="${h.route_path('repo_files',repo_name=c.repo_name,commit_id=cs.raw_id,f_path=c.changelog_for_path)}">
33 ${_('Show File')}
33 ${_('Show File')}
34 </a>
34 </a>
35 </td>
35 </td>
@@ -110,20 +110,20 b''
110 %if h.is_hg(c.rhodecode_repo):
110 %if h.is_hg(c.rhodecode_repo):
111 %for book in c.commit.bookmarks:
111 %for book in c.commit.bookmarks:
112 <span class="booktag tag" title="${h.tooltip(_('Bookmark %s') % book)}">
112 <span class="booktag tag" title="${h.tooltip(_('Bookmark %s') % book)}">
113 <a href="${h.url('files_home',repo_name=c.repo_name,revision=c.commit.raw_id)}"><i class="icon-bookmark"></i>${h.shorter(book)}</a>
113 <a href="${h.route_path('repo_files:default_path',repo_name=c.repo_name,commit_id=c.commit.raw_id,_query=dict(at=book))}"><i class="icon-bookmark"></i>${h.shorter(book)}</a>
114 </span>
114 </span>
115 %endfor
115 %endfor
116 %endif
116 %endif
117
117
118 %for tag in c.commit.tags:
118 %for tag in c.commit.tags:
119 <span class="tagtag tag" title="${h.tooltip(_('Tag %s') % tag)}">
119 <span class="tagtag tag" title="${h.tooltip(_('Tag %s') % tag)}">
120 <a href="${h.url('files_home',repo_name=c.repo_name,revision=c.commit.raw_id)}"><i class="icon-tag"></i>${tag}</a>
120 <a href="${h.route_path('repo_files:default_path',repo_name=c.repo_name,commit_id=c.commit.raw_id,_query=dict(at=tag))}"><i class="icon-tag"></i>${tag}</a>
121 </span>
121 </span>
122 %endfor
122 %endfor
123
123
124 %if c.commit.branch:
124 %if c.commit.branch:
125 <span class="branchtag tag" title="${h.tooltip(_('Branch %s') % c.commit.branch)}">
125 <span class="branchtag tag" title="${h.tooltip(_('Branch %s') % c.commit.branch)}">
126 <a href="${h.url('files_home',repo_name=c.repo_name,revision=c.commit.raw_id)}"><i class="icon-code-fork"></i>${h.shorter(c.commit.branch)}</a>
126 <a href="${h.route_path('repo_files:default_path',repo_name=c.repo_name,commit_id=c.commit.raw_id,_query=dict(at=c.commit.branch))}"><i class="icon-code-fork"></i>${h.shorter(c.commit.branch)}</a>
127 </span>
127 </span>
128 %endif
128 %endif
129 </div>
129 </div>
@@ -339,7 +339,7 b''
339
339
340 // browse tree @ revision
340 // browse tree @ revision
341 $('#files_link').on('click', function(e){
341 $('#files_link').on('click', function(e){
342 window.location = '${h.url('files_home',repo_name=c.repo_name, revision=c.commit.raw_id, f_path='')}';
342 window.location = '${h.route_path('repo_files:default_path',repo_name=c.repo_name, commit_id=c.commit.raw_id)}';
343 e.preventDefault();
343 e.preventDefault();
344 });
344 });
345
345
@@ -114,7 +114,7 b' collapse_all = len(diffset.files) > coll'
114 <div class="diffset-heading ${diffset.limited_diff and 'diffset-heading-warning' or ''}">
114 <div class="diffset-heading ${diffset.limited_diff and 'diffset-heading-warning' or ''}">
115 %if commit:
115 %if commit:
116 <div class="pull-right">
116 <div class="pull-right">
117 <a class="btn tooltip" title="${h.tooltip(_('Browse Files at revision {}').format(commit.raw_id))}" href="${h.url('files_home',repo_name=diffset.repo_name, revision=commit.raw_id, f_path='')}">
117 <a class="btn tooltip" title="${h.tooltip(_('Browse Files at revision {}').format(commit.raw_id))}" href="${h.route_path('repo_files',repo_name=diffset.repo_name, commit_id=commit.raw_id, f_path='')}">
118 ${_('Browse Files')}
118 ${_('Browse Files')}
119 </a>
119 </a>
120 </div>
120 </div>
@@ -410,7 +410,7 b' from rhodecode.lib.diffs import NEW_FILE'
410 %if filediff.operation in ['D', 'M']:
410 %if filediff.operation in ['D', 'M']:
411 <a
411 <a
412 class="tooltip"
412 class="tooltip"
413 href="${h.url('files_home',repo_name=filediff.diffset.repo_name,f_path=filediff.source_file_path,revision=filediff.diffset.source_ref)}"
413 href="${h.route_path('repo_files',repo_name=filediff.diffset.repo_name,commit_id=filediff.diffset.source_ref,f_path=filediff.source_file_path)}"
414 title="${h.tooltip(_('Show file at commit: %(commit_id)s') % {'commit_id': filediff.diffset.source_ref[:12]})}"
414 title="${h.tooltip(_('Show file at commit: %(commit_id)s') % {'commit_id': filediff.diffset.source_ref[:12]})}"
415 >
415 >
416 ${_('Show file before')}
416 ${_('Show file before')}
@@ -426,7 +426,7 b' from rhodecode.lib.diffs import NEW_FILE'
426 %if filediff.operation in ['A', 'M']:
426 %if filediff.operation in ['A', 'M']:
427 <a
427 <a
428 class="tooltip"
428 class="tooltip"
429 href="${h.url('files_home',repo_name=filediff.diffset.source_repo_name,f_path=filediff.target_file_path,revision=filediff.diffset.target_ref)}"
429 href="${h.route_path('repo_files',repo_name=filediff.diffset.source_repo_name,commit_id=filediff.diffset.target_ref,f_path=filediff.target_file_path)}"
430 title="${h.tooltip(_('Show file at commit: %(commit_id)s') % {'commit_id': filediff.diffset.target_ref[:12]})}"
430 title="${h.tooltip(_('Show file at commit: %(commit_id)s') % {'commit_id': filediff.diffset.target_ref[:12]})}"
431 >
431 >
432 ${_('Show file after')}
432 ${_('Show file after')}
@@ -442,14 +442,14 b' from rhodecode.lib.diffs import NEW_FILE'
442 <a
442 <a
443 class="tooltip"
443 class="tooltip"
444 title="${h.tooltip(_('Raw diff'))}"
444 title="${h.tooltip(_('Raw diff'))}"
445 href="${h.url('files_diff_home',repo_name=filediff.diffset.repo_name,f_path=filediff.target_file_path,diff2=filediff.diffset.target_ref,diff1=filediff.diffset.source_ref,diff='raw')}"
445 href="${h.route_path('repo_files_diff',repo_name=filediff.diffset.repo_name,f_path=filediff.target_file_path, _query=dict(diff2=filediff.diffset.target_ref,diff1=filediff.diffset.source_ref,diff='raw'))}"
446 >
446 >
447 ${_('Raw diff')}
447 ${_('Raw diff')}
448 </a> |
448 </a> |
449 <a
449 <a
450 class="tooltip"
450 class="tooltip"
451 title="${h.tooltip(_('Download diff'))}"
451 title="${h.tooltip(_('Download diff'))}"
452 href="${h.url('files_diff_home',repo_name=filediff.diffset.repo_name,f_path=filediff.target_file_path,diff2=filediff.diffset.target_ref,diff1=filediff.diffset.source_ref,diff='download')}"
452 href="${h.route_path('repo_files_diff',repo_name=filediff.diffset.repo_name,f_path=filediff.target_file_path, _query=dict(diff2=filediff.diffset.target_ref,diff1=filediff.diffset.source_ref,diff='download'))}"
453 >
453 >
454 ${_('Download diff')}
454 ${_('Download diff')}
455 </a>
455 </a>
@@ -22,7 +22,7 b''
22 <div class="cb-annotate-message truncate-wrap">${h.chop_at_smart(annotation.message, '\n', suffix_if_chopped='...')}</div>
22 <div class="cb-annotate-message truncate-wrap">${h.chop_at_smart(annotation.message, '\n', suffix_if_chopped='...')}</div>
23 </td>
23 </td>
24 <td class="cb-annotate-message-spacer">
24 <td class="cb-annotate-message-spacer">
25 <a class="tooltip" href="#show-previous-annotation" onclick="return annotationController.previousAnnotation('${annotation.raw_id}', '${c.f_path}')" title="${tooltip(_('view annotation from before this change'))}">
25 <a class="tooltip" href="#show-previous-annotation" onclick="return annotationController.previousAnnotation('${annotation.raw_id}', '${c.f_path}', ${line_num})" title="${tooltip(_('view annotation from before this change'))}">
26 <i class="icon-left"></i>
26 <i class="icon-left"></i>
27 </a>
27 </a>
28 </td>
28 </td>
@@ -76,13 +76,14 b''
76 var AnnotationController = function() {
76 var AnnotationController = function() {
77 var self = this;
77 var self = this;
78
78
79 this.previousAnnotation = function(commitId, fPath) {
79 this.previousAnnotation = function(commitId, fPath, lineNo) {
80 var params = {
80 var params = {
81 'repo_name': templateContext.repo_name,
81 'repo_name': templateContext.repo_name,
82 'revision': commitId,
82 'commit_id': commitId,
83 'f_path': fPath
83 'f_path': fPath,
84 'line_anchor': lineNo
84 };
85 };
85 window.location = pyroutes.url('files_annotate_previous', params);
86 window.location = pyroutes.url('repo_files:annotated_previous', params);
86 return false;
87 return false;
87 };
88 };
88 };
89 };
@@ -19,7 +19,7 b''
19 </a>
19 </a>
20 </li>
20 </li>
21 <li>
21 <li>
22 <a title="${_('Files')}" href="${h.url('files_home',repo_name=repo_name)}">
22 <a title="${_('Files')}" href="${h.route_path('repo_files:default_commit',repo_name=repo_name)}">
23 <span>${_('Files')}</span>
23 <span>${_('Files')}</span>
24 </a>
24 </a>
25 </li>
25 </li>
@@ -8,20 +8,20 b''
8 %if h.is_hg(c.rhodecode_repo):
8 %if h.is_hg(c.rhodecode_repo):
9 %for book in commit.bookmarks:
9 %for book in commit.bookmarks:
10 <span class="booktag tag" title="${h.tooltip(_('Bookmark %s') % book)}">
10 <span class="booktag tag" title="${h.tooltip(_('Bookmark %s') % book)}">
11 <a href="${h.url('files_home',repo_name=c.repo_name,revision=commit.raw_id)}"><i class="icon-bookmark"></i>${h.shorter(book)}</a>
11 <a href="${h.route_path('repo_files:default_path',repo_name=c.repo_name,commit_id=commit.raw_id,_query=dict(at=book))}"><i class="icon-bookmark"></i>${h.shorter(book)}</a>
12 </span>
12 </span>
13 %endfor
13 %endfor
14 %endif
14 %endif
15
15
16 %for tag in commit.tags:
16 %for tag in commit.tags:
17 <span class="tagtag tag" title="${h.tooltip(_('Tag %s') % tag)}">
17 <span class="tagtag tag" title="${h.tooltip(_('Tag %s') % tag)}">
18 <a href="${h.url('files_home',repo_name=c.repo_name,revision=commit.raw_id)}"><i class="icon-tag"></i>${tag}</a>
18 <a href="${h.route_path('repo_files:default_path',repo_name=c.repo_name,commit_id=commit.raw_id,_query=dict(at=tag))}"><i class="icon-tag"></i>${tag}</a>
19 </span>
19 </span>
20 %endfor
20 %endfor
21
21
22 %if commit.branch:
22 %if commit.branch:
23 <span class="branchtag tag" title="${h.tooltip(_('Branch %s') % commit.branch)}">
23 <span class="branchtag tag" title="${h.tooltip(_('Branch %s') % commit.branch)}">
24 <a href="${h.url('files_home',repo_name=c.repo_name,revision=commit.raw_id)}"><i class="icon-code-fork"></i>${h.shorter(commit.branch)}</a>
24 <a href="${h.route_path('repo_files:default_path',repo_name=c.repo_name,commit_id=commit.raw_id,_query=dict(at=commit.branch))}"><i class="icon-code-fork"></i>${h.shorter(commit.branch)}</a>
25 </span>
25 </span>
26 %endif
26 %endif
27
27
@@ -5,7 +5,7 b''
5 % if c.file_author:
5 % if c.file_author:
6 ${_('Last Author')}
6 ${_('Last Author')}
7 % else:
7 % else:
8 ${h.literal(ungettext(u'File Author (%s)',u'File Authors (%s)',len(c.authors)) % ('<b>%s</b>' % len(c.authors))) }
8 ${h.literal(_ungettext(u'File Author (%s)',u'File Authors (%s)',len(c.authors)) % ('<b>%s</b>' % len(c.authors))) }
9 % endif
9 % endif
10 </h4>
10 </h4>
11 <a href="#" id="show_authors" class="action_link">${_('Show All')}</a>
11 <a href="#" id="show_authors" class="action_link">${_('Show All')}</a>
@@ -13,17 +13,29 b''
13
13
14 % if c.authors:
14 % if c.authors:
15 <ul class="sidebar-right-content">
15 <ul class="sidebar-right-content">
16 % for email, user in sorted(c.authors, key=lambda e: c.file_last_commit.author_email!=e[0]):
16 % for email, user, commits in sorted(c.authors, key=lambda e: c.file_last_commit.author_email!=e[0]):
17 <li class="file_author">
17 <li class="file_author">
18 <div class="rc-user tooltip" title="${h.tooltip(h.author_string(email))}">
18 <div class="tooltip" title="${h.tooltip(h.author_string(email))}">
19 ${base.gravatar(email, 16)}
19 ${base.gravatar(email, 16)}
20 <span class="user">${h.link_to_user(user)}</span>
20 <div class="user">${h.link_to_user(user)}</div>
21
22 % if c.file_author:
23 <span>- ${h.age_component(c.file_last_commit.date)}</span>
24 % elif c.file_last_commit.author_email==email:
25 <span> (${_('last author')})</span>
26 % endif
27
28 % if not c.file_author:
29 <span>
30 % if commits == 1:
31 ${commits} ${_('Commit')}
32 % else:
33 ${commits} ${_('Commits')}
34 % endif
35 </span>
36 % endif
37
21 </div>
38 </div>
22 % if c.file_author:
23 <div class="user-inline-data">- ${h.age_component(c.file_last_commit.date)}</div>
24 % elif c.file_last_commit.author_email==email:
25 <div class="user-inline-data"> (${_('last author')})</div>
26 % endif
27 </li>
39 </li>
28 % endfor
40 % endfor
29 </ul>
41 </ul>
@@ -43,8 +43,8 b''
43
43
44 var getState = function(context) {
44 var getState = function(context) {
45 var url = $(location).attr('href');
45 var url = $(location).attr('href');
46 var _base_url = '${h.url("files_home",repo_name=c.repo_name,revision='',f_path='')}';
46 var _base_url = '${h.route_path("repo_files",repo_name=c.repo_name,commit_id='',f_path='')}';
47 var _annotate_url = '${h.url("files_annotate_home",repo_name=c.repo_name,revision='',f_path='')}';
47 var _annotate_url = '${h.route_path("repo_files:annotated",repo_name=c.repo_name,commit_id='',f_path='')}';
48 _base_url = _base_url.replace('//', '/');
48 _base_url = _base_url.replace('//', '/');
49 _annotate_url = _annotate_url.replace('//', '/');
49 _annotate_url = _annotate_url.replace('//', '/');
50
50
@@ -67,12 +67,12 b''
67 var f_path = parts2.join('/');
67 var f_path = parts2.join('/');
68 }
68 }
69
69
70 var _node_list_url = pyroutes.url('files_nodelist_home',
70 var _node_list_url = pyroutes.url('repo_files_nodelist',
71 {repo_name: templateContext.repo_name,
71 {repo_name: templateContext.repo_name,
72 revision: rev, f_path: f_path});
72 commit_id: rev, f_path: f_path});
73 var _url_base = pyroutes.url('files_home',
73 var _url_base = pyroutes.url('repo_files',
74 {repo_name: templateContext.repo_name,
74 {repo_name: templateContext.repo_name,
75 revision: rev, f_path:'__FPATH__'});
75 commit_id: rev, f_path:'__FPATH__'});
76 return {
76 return {
77 url: url,
77 url: url,
78 f_path: f_path,
78 f_path: f_path,
@@ -105,7 +105,7 b''
105 'f_path': state.f_path
105 'f_path': state.f_path
106 };
106 };
107
107
108 var url = pyroutes.url('files_nodetree_full', url_data);
108 var url = pyroutes.url('repo_nodetree_full', url_data);
109
109
110 metadataRequest = $.ajax({url: url});
110 metadataRequest = $.ajax({url: url});
111
111
@@ -182,13 +182,13 b''
182
182
183 var annotate = $('#annotate').val();
183 var annotate = $('#annotate').val();
184 if (annotate === "True") {
184 if (annotate === "True") {
185 var url = pyroutes.url('files_annotate_home',
185 var url = pyroutes.url('repo_files:annotated',
186 {'repo_name': templateContext.repo_name,
186 {'repo_name': templateContext.repo_name,
187 'revision': diff1, 'f_path': state.f_path});
187 'commit_id': diff1, 'f_path': state.f_path});
188 } else {
188 } else {
189 var url = pyroutes.url('files_home',
189 var url = pyroutes.url('repo_files',
190 {'repo_name': templateContext.repo_name,
190 {'repo_name': templateContext.repo_name,
191 'revision': diff1, 'f_path': state.f_path});
191 'commit_id': diff1, 'f_path': state.f_path});
192 }
192 }
193 window.location = url;
193 window.location = url;
194
194
@@ -197,9 +197,9 b''
197 // show more authors
197 // show more authors
198 $('#show_authors').on('click', function(e) {
198 $('#show_authors').on('click', function(e) {
199 e.preventDefault();
199 e.preventDefault();
200 var url = pyroutes.url('files_authors_home',
200 var url = pyroutes.url('repo_file_authors',
201 {'repo_name': templateContext.repo_name,
201 {'repo_name': templateContext.repo_name,
202 'revision': state.rev, 'f_path': state.f_path});
202 'commit_id': state.rev, 'f_path': state.f_path});
203
203
204 $.pjax({
204 $.pjax({
205 url: url,
205 url: url,
@@ -264,9 +264,9 b''
264 var rev = $('#at_rev').val();
264 var rev = $('#at_rev').val();
265 // explicit reload page here. with pjax entering bad input
265 // explicit reload page here. with pjax entering bad input
266 // produces not so nice results
266 // produces not so nice results
267 window.location = pyroutes.url('files_home',
267 window.location = pyroutes.url('repo_files',
268 {'repo_name': templateContext.repo_name,
268 {'repo_name': templateContext.repo_name,
269 'revision': rev, 'f_path': state.f_path});
269 'commit_id': rev, 'f_path': state.f_path});
270 }
270 }
271 });
271 });
272 }
272 }
@@ -27,7 +27,7 b''
27 <div class="edit-file-title">
27 <div class="edit-file-title">
28 ${self.breadcrumbs()}
28 ${self.breadcrumbs()}
29 </div>
29 </div>
30 ${h.secure_form(h.url.current(),method='post',id='eform',enctype="multipart/form-data", class_="form-horizontal")}
30 ${h.secure_form(h.route_path('repo_files_create_file', repo_name=c.repo_name, commit_id=c.commit.raw_id, f_path=c.f_path), id='eform', method='POST', enctype="multipart/form-data", class_="form-horizontal")}
31 <div class="edit-file-fieldset">
31 <div class="edit-file-fieldset">
32 <div class="fieldset">
32 <div class="fieldset">
33 <div id="destination-label" class="left-label">
33 <div id="destination-label" class="left-label">
@@ -169,7 +169,7 b''
169 hide_upload();
169 hide_upload();
170
170
171 var renderer = "";
171 var renderer = "";
172 var reset_url = "${h.url('files_home',repo_name=c.repo_name,revision=c.commit.raw_id,f_path=c.f_path)}";
172 var reset_url = "${h.route_path('repo_files',repo_name=c.repo_name,commit_id=c.commit.raw_id,f_path=c.f_path)}";
173 var myCodeMirror = initCodeMirror('editor', reset_url, false);
173 var myCodeMirror = initCodeMirror('editor', reset_url, false);
174
174
175 var modes_select = $('#set_mode');
175 var modes_select = $('#set_mode');
@@ -23,7 +23,7 b''
23 </div>
23 </div>
24 % if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name):
24 % if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name):
25 <div title="${_('Add New File')}" class="btn btn-primary new-file">
25 <div title="${_('Add New File')}" class="btn btn-primary new-file">
26 <a href="${h.url('files_add_home',repo_name=c.repo_name,revision=c.commit.raw_id,f_path=c.f_path, anchor='edit')}">
26 <a href="${h.route_path('repo_files_add_file',repo_name=c.repo_name,commit_id=c.commit.raw_id,f_path=c.f_path, _anchor='edit')}">
27 ${_('Add File')}</a>
27 ${_('Add File')}</a>
28 </div>
28 </div>
29 % endif
29 % endif
@@ -43,7 +43,7 b''
43 </div>
43 </div>
44 ## file tree is computed from caches, and filled in
44 ## file tree is computed from caches, and filled in
45 <div id="file-tree">
45 <div id="file-tree">
46 ${c.file_tree}
46 ${c.file_tree |n}
47 </div>
47 </div>
48
48
49 </div>
49 </div>
@@ -14,7 +14,7 b''
14 %if c.file.parent:
14 %if c.file.parent:
15 <tr class="parity0">
15 <tr class="parity0">
16 <td class="td-componentname">
16 <td class="td-componentname">
17 <a href="${h.url('files_home',repo_name=c.repo_name,revision=c.commit.raw_id,f_path=c.file.parent.path)}" class="pjax-link">
17 <a href="${h.route_path('repo_files',repo_name=c.repo_name,commit_id=c.commit.raw_id,f_path=c.file.parent.path)}" class="pjax-link">
18 <i class="icon-folder"></i>..
18 <i class="icon-folder"></i>..
19 </a>
19 </a>
20 </td>
20 </td>
@@ -38,7 +38,7 b''
38 % endif
38 % endif
39 </span>
39 </span>
40 % else:
40 % else:
41 <a href="${h.url('files_home',repo_name=c.repo_name,revision=c.commit.raw_id,f_path=h.safe_unicode(node.path))}" class="pjax-link">
41 <a href="${h.route_path('repo_files',repo_name=c.repo_name,commit_id=c.commit.raw_id,f_path=h.safe_unicode(node.path))}" class="pjax-link">
42 <i class="${'icon-file browser-file' if node.is_file() else 'icon-folder browser-dir'}"></i>${node.name}
42 <i class="${'icon-file browser-file' if node.is_file() else 'icon-folder browser-dir'}"></i>${node.name}
43 </a>
43 </a>
44 % endif
44 % endif
@@ -27,7 +27,7 b''
27 <div class="edit-file-title">
27 <div class="edit-file-title">
28 ${self.breadcrumbs()}
28 ${self.breadcrumbs()}
29 </div>
29 </div>
30 ${h.secure_form(h.url.current(),method='post',class_="form-horizontal")}
30 ${h.secure_form(h.route_path('repo_files_delete_file', repo_name=c.repo_name, commit_id=c.commit.raw_id, f_path=c.f_path), id='eform', method='POST', class_="form-horizontal")}
31 <div class="edit-file-fieldset">
31 <div class="edit-file-fieldset">
32 <div class="fieldset">
32 <div class="fieldset">
33 <div id="destination-label" class="left-label">
33 <div id="destination-label" class="left-label">
@@ -44,11 +44,11 b''
44 %if c.file.is_binary:
44 %if c.file.is_binary:
45 ${_('Binary file (%s)') % c.file.mimetype}
45 ${_('Binary file (%s)') % c.file.mimetype}
46 %else:
46 %else:
47 %if c.file.size < c.cut_off_limit:
47 %if c.file.size < c.visual.cut_off_limit_file:
48 ${h.pygmentize(c.file,linenos=True,anchorlinenos=False,cssclass="code-highlight")}
48 ${h.pygmentize(c.file,linenos=True,anchorlinenos=False,cssclass="code-highlight")}
49 %else:
49 %else:
50 ${_('File is too big to display')} ${h.link_to(_('Show as raw'),
50 ${_('File size {} is bigger then allowed limit {}. ').format(h.format_byte_size_binary(c.file.size), h.format_byte_size_binary(c.visual.cut_off_limit_file))} ${h.link_to(_('Show as raw'),
51 h.url('files_raw_home',repo_name=c.repo_name,revision=c.commit.raw_id,f_path=c.f_path))}
51 h.route_path('repo_file_raw',repo_name=c.repo_name,commit_id=c.commit.raw_id,f_path=c.f_path))}
52 %endif
52 %endif
53 %endif
53 %endif
54 </div>
54 </div>
@@ -42,7 +42,7 b''
42 </div>
42 </div>
43
43
44 <div class="table">
44 <div class="table">
45 ${h.secure_form(h.url.current(),method='post',id='eform')}
45 ${h.secure_form(h.route_path('repo_files_update_file', repo_name=c.repo_name, commit_id=c.commit.raw_id, f_path=c.f_path), id='eform', method='POST')}
46 <div id="codeblock" class="codeblock" >
46 <div id="codeblock" class="codeblock" >
47 <div class="code-header">
47 <div class="code-header">
48 <div class="stats">
48 <div class="stats">
@@ -58,12 +58,15 b''
58 % if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name):
58 % if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name):
59 % if not c.file.is_binary:
59 % if not c.file.is_binary:
60 %if True:
60 %if True:
61 ${h.link_to(_('source'), h.url('files_home', repo_name=c.repo_name,revision=c.commit.raw_id,f_path=c.f_path),class_="btn btn-mini")}
61 ${h.link_to(_('source'), h.route_path('repo_files', repo_name=c.repo_name,commit_id=c.commit.raw_id,f_path=c.f_path),class_="btn btn-mini")}
62 %else:
62 %else:
63 ${h.link_to(_('annotation'),h.url('files_annotate_home',repo_name=c.repo_name,revision=c.commit.raw_id,f_path=c.f_path),class_="btn btn-mini")}
63 ${h.link_to(_('annotation'),h.route_path('repo_files:annotated',repo_name=c.repo_name,commit_id=c.commit.raw_id,f_path=c.f_path),class_="btn btn-mini")}
64 %endif
64 %endif
65 ${h.link_to(_('raw'),h.url('files_raw_home',repo_name=c.repo_name,revision=c.commit.raw_id,f_path=c.f_path),class_="btn btn-mini")}
65
66 <a class="btn btn-mini" href="${h.url('files_rawfile_home',repo_name=c.repo_name,revision=c.commit.raw_id,f_path=c.f_path)}">
66 <a class="btn btn-mini" href="${h.route_path('repo_file_raw',repo_name=c.repo_name,commit_id=c.commit.raw_id,f_path=c.f_path)}">
67 ${_('raw')}
68 </a>
69 <a class="btn btn-mini" href="${h.route_path('repo_file_download',repo_name=c.repo_name,commit_id=c.commit.raw_id,f_path=c.f_path)}">
67 <i class="icon-archive"></i> ${_('download')}
70 <i class="icon-archive"></i> ${_('download')}
68 </a>
71 </a>
69 % endif
72 % endif
@@ -112,7 +115,7 b''
112 <script type="text/javascript">
115 <script type="text/javascript">
113 $(document).ready(function(){
116 $(document).ready(function(){
114 var renderer = "${renderer}";
117 var renderer = "${renderer}";
115 var reset_url = "${h.url('files_home',repo_name=c.repo_name,revision=c.commit.raw_id,f_path=c.file.path)}";
118 var reset_url = "${h.route_path('repo_files',repo_name=c.repo_name,commit_id=c.commit.raw_id,f_path=c.file.path)}";
116 var myCodeMirror = initCodeMirror('editor', reset_url);
119 var myCodeMirror = initCodeMirror('editor', reset_url);
117
120
118 var modes_select = $('#set_mode');
121 var modes_select = $('#set_mode');
@@ -7,7 +7,7 b''
7 % if c.lf_node:
7 % if c.lf_node:
8 <span title="${_('This file is a pointer to large binary file')}"> | ${_('LargeFile')} ${h.format_byte_size_binary(c.lf_node.size)} </span>
8 <span title="${_('This file is a pointer to large binary file')}"> | ${_('LargeFile')} ${h.format_byte_size_binary(c.lf_node.size)} </span>
9 % endif
9 % endif
10 <span> | ${c.file.lines()[0]} ${ungettext('line', 'lines', c.file.lines()[0])}</span>
10 <span> | ${c.file.lines()[0]} ${_ungettext('line', 'lines', c.file.lines()[0])}</span>
11 <span> | ${h.format_byte_size_binary(c.file.size)}</span>
11 <span> | ${h.format_byte_size_binary(c.file.size)}</span>
12 <span> | ${c.file.mimetype} </span>
12 <span> | ${c.file.mimetype} </span>
13 <span class="item last"> | ${h.get_lexer_for_filenode(c.file).__class__.__name__}</span>
13 <span class="item last"> | ${h.get_lexer_for_filenode(c.file).__class__.__name__}</span>
@@ -20,18 +20,18 b''
20 ${_('Show Full History')}
20 ${_('Show Full History')}
21 </a> |
21 </a> |
22 %if c.annotate:
22 %if c.annotate:
23 ${h.link_to(_('Source'), h.url('files_home', repo_name=c.repo_name,revision=c.commit.raw_id,f_path=c.f_path))}
23 ${h.link_to(_('Source'), h.route_path('repo_files', repo_name=c.repo_name,commit_id=c.commit.raw_id,f_path=c.f_path))}
24 %else:
24 %else:
25 ${h.link_to(_('Annotation'), h.url('files_annotate_home',repo_name=c.repo_name,revision=c.commit.raw_id,f_path=c.f_path))}
25 ${h.link_to(_('Annotation'), h.route_path('repo_files:annotated',repo_name=c.repo_name,commit_id=c.commit.raw_id,f_path=c.f_path))}
26 %endif
26 %endif
27 | ${h.link_to(_('Raw'), h.url('files_raw_home',repo_name=c.repo_name,revision=c.commit.raw_id,f_path=c.f_path))}
27 | ${h.link_to(_('Raw'), h.route_path('repo_file_raw',repo_name=c.repo_name,commit_id=c.commit.raw_id,f_path=c.f_path))}
28 |
28 |
29 % if c.lf_node:
29 % if c.lf_node:
30 <a href="${h.url('files_rawfile_home',repo_name=c.repo_name,revision=c.commit.raw_id,f_path=c.f_path, lf=1)}">
30 <a href="${h.route_path('repo_file_download',repo_name=c.repo_name,commit_id=c.commit.raw_id,f_path=c.f_path, _query=dict(lf=1))}">
31 ${_('Download largefile')}
31 ${_('Download largefile')}
32 </a>
32 </a>
33 % else:
33 % else:
34 <a href="${h.url('files_rawfile_home',repo_name=c.repo_name,revision=c.commit.raw_id,f_path=c.f_path)}">
34 <a href="${h.route_path('repo_file_download',repo_name=c.repo_name,commit_id=c.commit.raw_id,f_path=c.f_path)}">
35 ${_('Download')}
35 ${_('Download')}
36 </a>
36 </a>
37 % endif
37 % endif
@@ -39,14 +39,14 b''
39 %if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name):
39 %if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name):
40 |
40 |
41 %if c.on_branch_head and c.branch_or_raw_id and not c.file.is_binary:
41 %if c.on_branch_head and c.branch_or_raw_id and not c.file.is_binary:
42 <a href="${h.url('files_edit_home',repo_name=c.repo_name,revision=c.branch_or_raw_id,f_path=c.f_path, anchor='edit')}">
42 <a href="${h.route_path('repo_files_edit_file',repo_name=c.repo_name,commit_id=c.branch_or_raw_id,f_path=c.f_path, _anchor='edit')}">
43 ${_('Edit on Branch:%s') % c.branch_name}
43 ${_('Edit on Branch:{}').format(c.branch_name)}
44 </a>
44 </a>
45 | <a class="btn-danger btn-link" href="${h.url('files_delete_home',repo_name=c.repo_name,revision=c.branch_or_raw_id,f_path=c.f_path, anchor='edit')}">${_('Delete')}
45 | <a class="btn-danger btn-link" href="${h.route_path('repo_files_remove_file',repo_name=c.repo_name,commit_id=c.branch_or_raw_id,f_path=c.f_path, _anchor='edit')}">${_('Delete')}
46 </a>
46 </a>
47 %elif c.on_branch_head and c.branch_or_raw_id and c.file.is_binary:
47 %elif c.on_branch_head and c.branch_or_raw_id and c.file.is_binary:
48 ${h.link_to(_('Edit'), '#', class_="btn btn-link disabled tooltip", title=_('Editing binary files not allowed'))}
48 ${h.link_to(_('Edit'), '#', class_="btn btn-link disabled tooltip", title=_('Editing binary files not allowed'))}
49 | ${h.link_to(_('Delete'), h.url('files_delete_home',repo_name=c.repo_name,revision=c.branch_or_raw_id,f_path=c.f_path, anchor='edit'),class_="btn-danger btn-link")}
49 | ${h.link_to(_('Delete'), h.route_path('repo_files_remove_file',repo_name=c.repo_name,commit_id=c.branch_or_raw_id,f_path=c.f_path, _anchor='edit'),class_="btn-danger btn-link")}
50 %else:
50 %else:
51 ${h.link_to(_('Edit'), '#', class_="btn btn-link disabled tooltip", title=_('Editing files allowed only when on branch head commit'))}
51 ${h.link_to(_('Edit'), '#', class_="btn btn-link disabled tooltip", title=_('Editing files allowed only when on branch head commit'))}
52 | ${h.link_to(_('Delete'), '#', class_="btn btn-danger btn-link disabled tooltip", title=_('Deleting files allowed only when on branch head commit'))}
52 | ${h.link_to(_('Delete'), '#', class_="btn btn-danger btn-link disabled tooltip", title=_('Deleting files allowed only when on branch head commit'))}
@@ -66,9 +66,9 b''
66 </div>
66 </div>
67 % endif
67 % endif
68 %else:
68 %else:
69 % if c.file.size < c.cut_off_limit:
69 % if c.file.size < c.visual.cut_off_limit_file:
70 %if c.renderer and not c.annotate:
70 %if c.renderer and not c.annotate:
71 ${h.render(c.file.content, renderer=c.renderer, relative_url=h.url('files_raw_home',repo_name=c.repo_name,revision=c.commit.raw_id,f_path=c.f_path))}
71 ${h.render(c.file.content, renderer=c.renderer, relative_url=h.route_path('repo_file_raw',repo_name=c.repo_name,commit_id=c.commit.raw_id,f_path=c.f_path))}
72 %else:
72 %else:
73 <table class="cb codehilite">
73 <table class="cb codehilite">
74 %if c.annotate:
74 %if c.annotate:
@@ -84,8 +84,8 b''
84 </table>
84 </table>
85 %endif
85 %endif
86 %else:
86 %else:
87 ${_('File is too big to display')} ${h.link_to(_('Show as raw'),
87 ${_('File size {} is bigger then allowed limit {}. ').format(h.format_byte_size_binary(c.file.size), h.format_byte_size_binary(c.visual.cut_off_limit_file))} ${h.link_to(_('Show as raw'),
88 h.url('files_raw_home',repo_name=c.repo_name,revision=c.commit.raw_id,f_path=c.f_path))}
88 h.route_path('repo_file_raw',repo_name=c.repo_name,commit_id=c.commit.raw_id,f_path=c.f_path))}
89 %endif
89 %endif
90 %endif
90 %endif
91 </div>
91 </div>
@@ -28,6 +28,7 b' for line_number in matching_lines:'
28 query_terms=terms,
28 query_terms=terms,
29 only_line_numbers=lines_of_interest
29 only_line_numbers=lines_of_interest
30 ))|n}
30 ))|n}
31
31 %if len(matching_lines) > shown_matching_lines:
32 %if len(matching_lines) > shown_matching_lines:
32 <a href="${url}">
33 <a href="${url}">
33 ${len(matching_lines) - shown_matching_lines} ${_('more matches in this file')}
34 ${len(matching_lines) - shown_matching_lines} ${_('more matches in this file')}
@@ -52,7 +53,7 b' for line_number in matching_lines:'
52 ${h.link_to(entry['repository'], h.route_path('repo_summary',repo_name=entry['repository']))}
53 ${h.link_to(entry['repository'], h.route_path('repo_summary',repo_name=entry['repository']))}
53 </h2>
54 </h2>
54 <div class="stats">
55 <div class="stats">
55 ${h.link_to(h.literal(entry['f_path']), h.url('files_home',repo_name=entry['repository'],revision=entry.get('commit_id', 'tip'),f_path=entry['f_path']))}
56 ${h.link_to(h.literal(entry['f_path']), h.route_path('repo_files',repo_name=entry['repository'],commit_id=entry.get('commit_id', 'tip'),f_path=entry['f_path']))}
56 %if entry.get('lines'):
57 %if entry.get('lines'):
57 | ${entry.get('lines', 0.)} ${_ungettext('line', 'lines', entry.get('lines', 0.))}
58 | ${entry.get('lines', 0.)} ${_ungettext('line', 'lines', entry.get('lines', 0.))}
58 %endif
59 %endif
@@ -66,17 +67,15 b' for line_number in matching_lines:'
66 <div class="buttons">
67 <div class="buttons">
67 <a id="file_history_overview_full" href="${h.url('changelog_file_home',repo_name=entry.get('repository',''),revision=entry.get('commit_id', 'tip'),f_path=entry.get('f_path',''))}">
68 <a id="file_history_overview_full" href="${h.url('changelog_file_home',repo_name=entry.get('repository',''),revision=entry.get('commit_id', 'tip'),f_path=entry.get('f_path',''))}">
68 ${_('Show Full History')}
69 ${_('Show Full History')}
69 </a> |
70 </a>
70 ${h.link_to(_('Annotation'), h.url('files_annotate_home', repo_name=entry.get('repository',''),revision=entry.get('commit_id', 'tip'),f_path=entry.get('f_path','')))}
71 | ${h.link_to(_('Annotation'), h.route_path('repo_files:annotated', repo_name=entry.get('repository',''),commit_id=entry.get('commit_id', 'tip'),f_path=entry.get('f_path','')))}
71 | ${h.link_to(_('Raw'), h.url('files_raw_home', repo_name=entry.get('repository',''),revision=entry.get('commit_id', 'tip'),f_path=entry.get('f_path','')))}
72 | ${h.link_to(_('Raw'), h.route_path('repo_file_raw', repo_name=entry.get('repository',''),commit_id=entry.get('commit_id', 'tip'),f_path=entry.get('f_path','')))}
72 | <a href="${h.url('files_rawfile_home',repo_name=entry.get('repository',''),revision=entry.get('commit_id', 'tip'),f_path=entry.get('f_path',''))}">
73 | ${h.link_to(_('Download'), h.route_path('repo_file_download',repo_name=entry.get('repository',''),commit_id=entry.get('commit_id', 'tip'),f_path=entry.get('f_path','')))}
73 ${_('Download')}
74 </a>
75 </div>
74 </div>
76 </div>
75 </div>
77 <div class="code-body search-code-body">
76 <div class="code-body search-code-body">
78 ${highlight_text_file(c.cur_query, entry['content'],
77 ${highlight_text_file(c.cur_query, entry['content'],
79 url=h.url('files_home',repo_name=entry['repository'],revision=entry.get('commit_id', 'tip'),f_path=entry['f_path']),
78 url=h.route_path('repo_files',repo_name=entry['repository'],commit_id=entry.get('commit_id', 'tip'),f_path=entry['f_path']),
80 mimetype=entry.get('mimetype'), filepath=entry.get('path'))}
79 mimetype=entry.get('mimetype'), filepath=entry.get('path'))}
81 </div>
80 </div>
82 </div>
81 </div>
@@ -20,7 +20,7 b''
20 </td>
20 </td>
21 <td class="td-componentname">
21 <td class="td-componentname">
22 ${h.link_to(h.literal(entry['f_path']),
22 ${h.link_to(h.literal(entry['f_path']),
23 h.url('files_home',repo_name=entry['repository'],revision='tip',f_path=entry['f_path']))}
23 h.route_path('repo_files',repo_name=entry['repository'],commit_id='tip',f_path=entry['f_path']))}
24 </td>
24 </td>
25 </tr>
25 </tr>
26 % endif
26 % endif
@@ -173,7 +173,7 b''
173 % endif
173 % endif
174 % else:
174 % else:
175 <span class="enabled">
175 <span class="enabled">
176 <a id="archive_link" class="btn btn-small" href="${h.url('files_archive_home',repo_name=c.rhodecode_db_repo.repo_name,fname='tip.zip')}">
176 <a id="archive_link" class="btn btn-small" href="${h.route_path('repo_archivefile',repo_name=c.rhodecode_db_repo.repo_name,fname='tip.zip')}">
177 <i class="icon-archive"></i> tip.zip
177 <i class="icon-archive"></i> tip.zip
178 ## replaced by some JS on select
178 ## replaced by some JS on select
179 </a>
179 </a>
@@ -46,7 +46,7 b''
46 <div class="box" >
46 <div class="box" >
47 <div class="title" title="${h.tooltip(_('Readme file from commit %s:%s') % (c.rhodecode_db_repo.landing_rev[0], c.rhodecode_db_repo.landing_rev[1]))}">
47 <div class="title" title="${h.tooltip(_('Readme file from commit %s:%s') % (c.rhodecode_db_repo.landing_rev[0], c.rhodecode_db_repo.landing_rev[1]))}">
48 <h3 class="breadcrumbs">
48 <h3 class="breadcrumbs">
49 <a href="${h.url('files_home',repo_name=c.repo_name,revision='tip',f_path=c.readme_file)}">${c.readme_file}</a>
49 <a href="${h.route_path('repo_files',repo_name=c.repo_name,commit_id=c.rhodecode_db_repo.landing_rev[1],f_path=c.readme_file)}">${c.readme_file}</a>
50 </h3>
50 </h3>
51 </div>
51 </div>
52 <div class="readme codeblock">
52 <div class="readme codeblock">
@@ -96,7 +96,7 b''
96 // format of Object {text: "v0.0.3", type: "tag", id: "rev"}
96 // format of Object {text: "v0.0.3", type: "tag", id: "rev"}
97 var selected_cs = e.added;
97 var selected_cs = e.added;
98 var fname= e.added.raw_id + ".zip";
98 var fname= e.added.raw_id + ".zip";
99 var href = pyroutes.url('files_archive_home', {'repo_name': templateContext.repo_name, 'fname':fname});
99 var href = pyroutes.url('repo_archivefile', {'repo_name': templateContext.repo_name, 'fname':fname});
100 // set new label
100 // set new label
101 $('#archive_link').html('<i class="icon-archive"></i> '+ e.added.text+".zip");
101 $('#archive_link').html('<i class="icon-archive"></i> '+ e.added.text+".zip");
102
102
@@ -60,14 +60,14 b''
60 %if h.is_hg(c.rhodecode_repo):
60 %if h.is_hg(c.rhodecode_repo):
61 %for book in cs.bookmarks:
61 %for book in cs.bookmarks:
62 <span class="booktag tag" title="${h.tooltip(_('Bookmark %s') % book)}">
62 <span class="booktag tag" title="${h.tooltip(_('Bookmark %s') % book)}">
63 <a href="${h.url('files_home',repo_name=c.repo_name,revision=cs.raw_id)}"><i class="icon-bookmark"></i>${h.shorter(book)}</a>
63 <a href="${h.route_path('repo_files:default_path',repo_name=c.repo_name,commit_id=cs.raw_id, _query=dict(at=book))}"><i class="icon-bookmark"></i>${h.shorter(book)}</a>
64 </span>
64 </span>
65 %endfor
65 %endfor
66 %endif
66 %endif
67 ## tags
67 ## tags
68 %for tag in cs.tags:
68 %for tag in cs.tags:
69 <span class="tagtag tag" title="${h.tooltip(_('Tag %s') % tag)}">
69 <span class="tagtag tag" title="${h.tooltip(_('Tag %s') % tag)}">
70 <a href="${h.url('files_home',repo_name=c.repo_name,revision=cs.raw_id)}"><i class="icon-tag"></i>${h.shorter(tag)}</a>
70 <a href="${h.route_path('repo_files:default_path',repo_name=c.repo_name,commit_id=cs.raw_id, _query=dict(at=tag))}"><i class="icon-tag"></i>${h.shorter(tag)}</a>
71 </span>
71 </span>
72 %endfor
72 %endfor
73
73
@@ -100,7 +100,7 b''
100 <div class="left-label">${_('Add or upload files directly via RhodeCode:')}</div>
100 <div class="left-label">${_('Add or upload files directly via RhodeCode:')}</div>
101 <div class="right-content">
101 <div class="right-content">
102 <div id="add_node_id" class="add_node">
102 <div id="add_node_id" class="add_node">
103 <a href="${h.url('files_add_home',repo_name=c.repo_name,revision=0,f_path='', anchor='edit')}" class="btn btn-default">${_('Add New File')}</a>
103 <a href="${h.route_path('repo_files_add_file',repo_name=c.repo_name,commit_id=0, f_path='', _anchor='edit')}" class="btn btn-default">${_('Add New File')}</a>
104 </div>
104 </div>
105 </div>
105 </div>
106 %endif
106 %endif
@@ -24,6 +24,6 b''
24
24
25 <%def name="commit(message, commit_id, commit_idx)">
25 <%def name="commit(message, commit_id, commit_idx)">
26 <div>
26 <div>
27 <pre><a title="${h.tooltip(message)}" href="${h.url('files_home',repo_name=c.repo_name,revision=commit_id)}">r${commit_idx}:${h.short_id(commit_id)}</a></pre>
27 <pre><a title="${h.tooltip(message)}" href="${h.route_path('repo_files:default_path',repo_name=c.repo_name,commit_id=commit_id)}">r${commit_idx}:${h.short_id(commit_id)}</a></pre>
28 </div>
28 </div>
29 </%def>
29 </%def>
@@ -18,23 +18,10 b''
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import os
22
23 import mock
24 import pytest
21 import pytest
25
22
26 from rhodecode.controllers.files import FilesController
27 from rhodecode.lib import helpers as h
28 from rhodecode.lib.compat import OrderedDict
29 from rhodecode.lib.ext_json import json
30 from rhodecode.lib.vcs import nodes
23 from rhodecode.lib.vcs import nodes
31 from rhodecode.lib.vcs.backends.base import EmptyCommit
24 from rhodecode.tests import url
32 from rhodecode.lib.vcs.conf import settings
33 from rhodecode.lib.vcs.nodes import FileNode
34 from rhodecode.model.db import Repository
35 from rhodecode.model.scm import ScmModel
36 from rhodecode.tests import (
37 url, TEST_USER_ADMIN_LOGIN, assert_session_flash, assert_not_in_session_flash)
38 from rhodecode.tests.fixture import Fixture
25 from rhodecode.tests.fixture import Fixture
39 from rhodecode.tests.utils import commit_change
26 from rhodecode.tests.utils import commit_change
40
27
@@ -59,7 +59,7 b' def test_urlify_text(url, expected_url):'
59 '</a>/bX&#34;X'),
59 '</a>/bX&#34;X'),
60 ], ids=['simple', 'one_segment', 'empty_path', 'simple_quote'])
60 ], ids=['simple', 'one_segment', 'empty_path', 'simple_quote'])
61 def test_files_breadcrumbs_xss(
61 def test_files_breadcrumbs_xss(
62 repo_name, commit_id, path, pylonsapp, expected_result):
62 repo_name, commit_id, path, app, expected_result):
63 result = helpers.files_breadcrumbs(repo_name, commit_id, path)
63 result = helpers.files_breadcrumbs(repo_name, commit_id, path)
64 # Expect it to encode all path fragments properly. This is important
64 # Expect it to encode all path fragments properly. This is important
65 # because it returns an instance of `literal`.
65 # because it returns an instance of `literal`.
@@ -409,7 +409,6 b' def commit_change('
409 def add_test_routes(config):
409 def add_test_routes(config):
410 """
410 """
411 Adds test routing that can be used in different functional tests
411 Adds test routing that can be used in different functional tests
412
413 """
412 """
414 config.add_route(name='home', pattern='/')
413 config.add_route(name='home', pattern='/')
415 config.add_route(name='repo_summary', pattern='/{repo_name}')
414 config.add_route(name='repo_summary', pattern='/{repo_name}')
@@ -422,3 +421,6 b' def add_test_routes(config):'
422 pattern='/pull-request/{pull_request_id}')
421 pattern='/pull-request/{pull_request_id}')
423 config.add_route(name='repo_commit',
422 config.add_route(name='repo_commit',
424 pattern='/{repo_name}/changeset/{commit_id}')
423 pattern='/{repo_name}/changeset/{commit_id}')
424
425 config.add_route(name='repo_files',
426 pattern='/{repo_name}/files/{commit_id}/{f_path}')
This diff has been collapsed as it changes many lines, (1109 lines changed) Show them Hide them
@@ -1,1109 +0,0 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21 """
22 Files controller for RhodeCode Enterprise
23 """
24
25 import itertools
26 import logging
27 import os
28 import shutil
29 import tempfile
30
31 from pylons import request, response, tmpl_context as c, url
32 from pylons.i18n.translation import _
33 from pylons.controllers.util import redirect
34 from webob.exc import HTTPNotFound, HTTPBadRequest
35
36 from rhodecode.controllers.utils import parse_path_ref
37 from rhodecode.lib import diffs, helpers as h, caches
38 from rhodecode.lib import audit_logger
39 from rhodecode.lib.codeblocks import (
40 filenode_as_lines_tokens, filenode_as_annotated_lines_tokens)
41 from rhodecode.lib.utils import jsonify
42 from rhodecode.lib.utils2 import (
43 convert_line_endings, detect_mode, safe_str, str2bool)
44 from rhodecode.lib.auth import (
45 LoginRequired, HasRepoPermissionAnyDecorator, CSRFRequired, XHRRequired)
46 from rhodecode.lib.base import BaseRepoController, render
47 from rhodecode.lib.vcs import path as vcspath
48 from rhodecode.lib.vcs.backends.base import EmptyCommit
49 from rhodecode.lib.vcs.conf import settings
50 from rhodecode.lib.vcs.exceptions import (
51 RepositoryError, CommitDoesNotExistError, EmptyRepositoryError,
52 ImproperArchiveTypeError, VCSError, NodeAlreadyExistsError,
53 NodeDoesNotExistError, CommitError, NodeError)
54 from rhodecode.lib.vcs.nodes import FileNode
55
56 from rhodecode.model.repo import RepoModel
57 from rhodecode.model.scm import ScmModel
58 from rhodecode.model.db import Repository
59
60 from rhodecode.controllers.changeset import (
61 _ignorews_url, _context_url, get_line_ctx, get_ignore_ws)
62 from rhodecode.lib.exceptions import NonRelativePathError
63
64 log = logging.getLogger(__name__)
65
66
67 class FilesController(BaseRepoController):
68
69 def __before__(self):
70 super(FilesController, self).__before__()
71 c.cut_off_limit = self.cut_off_limit_file
72
73 def _get_default_encoding(self):
74 enc_list = getattr(c, 'default_encodings', [])
75 return enc_list[0] if enc_list else 'UTF-8'
76
77 def __get_commit_or_redirect(self, commit_id, repo_name,
78 redirect_after=True):
79 """
80 This is a safe way to get commit. If an error occurs it redirects to
81 tip with proper message
82
83 :param commit_id: id of commit to fetch
84 :param repo_name: repo name to redirect after
85 :param redirect_after: toggle redirection
86 """
87 try:
88 return c.rhodecode_repo.get_commit(commit_id)
89 except EmptyRepositoryError:
90 if not redirect_after:
91 return None
92 url_ = url('files_add_home',
93 repo_name=c.repo_name,
94 revision=0, f_path='', anchor='edit')
95 if h.HasRepoPermissionAny(
96 'repository.write', 'repository.admin')(c.repo_name):
97 add_new = h.link_to(
98 _('Click here to add a new file.'),
99 url_, class_="alert-link")
100 else:
101 add_new = ""
102 h.flash(h.literal(
103 _('There are no files yet. %s') % add_new), category='warning')
104 redirect(h.route_path('repo_summary', repo_name=repo_name))
105 except (CommitDoesNotExistError, LookupError):
106 msg = _('No such commit exists for this repository')
107 h.flash(msg, category='error')
108 raise HTTPNotFound()
109 except RepositoryError as e:
110 h.flash(safe_str(h.escape(e)), category='error')
111 raise HTTPNotFound()
112
113 def __get_filenode_or_redirect(self, repo_name, commit, path):
114 """
115 Returns file_node, if error occurs or given path is directory,
116 it'll redirect to top level path
117
118 :param repo_name: repo_name
119 :param commit: given commit
120 :param path: path to lookup
121 """
122 try:
123 file_node = commit.get_node(path)
124 if file_node.is_dir():
125 raise RepositoryError('The given path is a directory')
126 except CommitDoesNotExistError:
127 log.exception('No such commit exists for this repository')
128 h.flash(_('No such commit exists for this repository'), category='error')
129 raise HTTPNotFound()
130 except RepositoryError as e:
131 h.flash(safe_str(h.escape(e)), category='error')
132 raise HTTPNotFound()
133
134 return file_node
135
136 def __get_tree_cache_manager(self, repo_name, namespace_type):
137 _namespace = caches.get_repo_namespace_key(namespace_type, repo_name)
138 return caches.get_cache_manager('repo_cache_long', _namespace)
139
140 def _get_tree_at_commit(self, repo_name, commit_id, f_path,
141 full_load=False, force=False):
142 def _cached_tree():
143 log.debug('Generating cached file tree for %s, %s, %s',
144 repo_name, commit_id, f_path)
145 c.full_load = full_load
146 return render('files/files_browser_tree.mako')
147
148 cache_manager = self.__get_tree_cache_manager(
149 repo_name, caches.FILE_TREE)
150
151 cache_key = caches.compute_key_from_params(
152 repo_name, commit_id, f_path)
153
154 if force:
155 # we want to force recompute of caches
156 cache_manager.remove_value(cache_key)
157
158 return cache_manager.get(cache_key, createfunc=_cached_tree)
159
160 def _get_nodelist_at_commit(self, repo_name, commit_id, f_path):
161 def _cached_nodes():
162 log.debug('Generating cached nodelist for %s, %s, %s',
163 repo_name, commit_id, f_path)
164 _d, _f = ScmModel().get_nodes(
165 repo_name, commit_id, f_path, flat=False)
166 return _d + _f
167
168 cache_manager = self.__get_tree_cache_manager(
169 repo_name, caches.FILE_SEARCH_TREE_META)
170
171 cache_key = caches.compute_key_from_params(
172 repo_name, commit_id, f_path)
173 return cache_manager.get(cache_key, createfunc=_cached_nodes)
174
175 @LoginRequired()
176 @HasRepoPermissionAnyDecorator(
177 'repository.read', 'repository.write', 'repository.admin')
178 def index(
179 self, repo_name, revision, f_path, annotate=False, rendered=False):
180 commit_id = revision
181
182 # redirect to given commit_id from form if given
183 get_commit_id = request.GET.get('at_rev', None)
184 if get_commit_id:
185 self.__get_commit_or_redirect(get_commit_id, repo_name)
186
187 c.commit = self.__get_commit_or_redirect(commit_id, repo_name)
188 c.branch = request.GET.get('branch', None)
189 c.f_path = f_path
190 c.annotate = annotate
191 # default is false, but .rst/.md files later are autorendered, we can
192 # overwrite autorendering by setting this GET flag
193 c.renderer = rendered or not request.GET.get('no-render', False)
194
195 # prev link
196 try:
197 prev_commit = c.commit.prev(c.branch)
198 c.prev_commit = prev_commit
199 c.url_prev = url('files_home', repo_name=c.repo_name,
200 revision=prev_commit.raw_id, f_path=f_path)
201 if c.branch:
202 c.url_prev += '?branch=%s' % c.branch
203 except (CommitDoesNotExistError, VCSError):
204 c.url_prev = '#'
205 c.prev_commit = EmptyCommit()
206
207 # next link
208 try:
209 next_commit = c.commit.next(c.branch)
210 c.next_commit = next_commit
211 c.url_next = url('files_home', repo_name=c.repo_name,
212 revision=next_commit.raw_id, f_path=f_path)
213 if c.branch:
214 c.url_next += '?branch=%s' % c.branch
215 except (CommitDoesNotExistError, VCSError):
216 c.url_next = '#'
217 c.next_commit = EmptyCommit()
218
219 # files or dirs
220 try:
221 c.file = c.commit.get_node(f_path)
222 c.file_author = True
223 c.file_tree = ''
224 if c.file.is_file():
225 c.lf_node = c.file.get_largefile_node()
226
227 c.file_source_page = 'true'
228 c.file_last_commit = c.file.last_commit
229 if c.file.size < self.cut_off_limit_file:
230 if c.annotate: # annotation has precedence over renderer
231 c.annotated_lines = filenode_as_annotated_lines_tokens(
232 c.file
233 )
234 else:
235 c.renderer = (
236 c.renderer and h.renderer_from_filename(c.file.path)
237 )
238 if not c.renderer:
239 c.lines = filenode_as_lines_tokens(c.file)
240
241 c.on_branch_head = self._is_valid_head(
242 commit_id, c.rhodecode_repo)
243
244 branch = c.commit.branch if (
245 c.commit.branch and '/' not in c.commit.branch) else None
246 c.branch_or_raw_id = branch or c.commit.raw_id
247 c.branch_name = c.commit.branch or h.short_id(c.commit.raw_id)
248
249 author = c.file_last_commit.author
250 c.authors = [(h.email(author),
251 h.person(author, 'username_or_name_or_email'))]
252 else:
253 c.file_source_page = 'false'
254 c.authors = []
255 c.file_tree = self._get_tree_at_commit(
256 repo_name, c.commit.raw_id, f_path)
257
258 except RepositoryError as e:
259 h.flash(safe_str(h.escape(e)), category='error')
260 raise HTTPNotFound()
261
262 if request.environ.get('HTTP_X_PJAX'):
263 return render('files/files_pjax.mako')
264
265 return render('files/files.mako')
266
267 @LoginRequired()
268 @HasRepoPermissionAnyDecorator(
269 'repository.read', 'repository.write', 'repository.admin')
270 def annotate_previous(self, repo_name, revision, f_path):
271
272 commit_id = revision
273 commit = self.__get_commit_or_redirect(commit_id, repo_name)
274 prev_commit_id = commit.raw_id
275
276 f_path = f_path
277 is_file = False
278 try:
279 _file = commit.get_node(f_path)
280 is_file = _file.is_file()
281 except (NodeDoesNotExistError, CommitDoesNotExistError, VCSError):
282 pass
283
284 if is_file:
285 history = commit.get_file_history(f_path)
286 prev_commit_id = history[1].raw_id \
287 if len(history) > 1 else prev_commit_id
288
289 return redirect(h.url(
290 'files_annotate_home', repo_name=repo_name,
291 revision=prev_commit_id, f_path=f_path))
292
293 @LoginRequired()
294 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
295 'repository.admin')
296 @jsonify
297 def history(self, repo_name, revision, f_path):
298 commit = self.__get_commit_or_redirect(revision, repo_name)
299 f_path = f_path
300 _file = commit.get_node(f_path)
301 if _file.is_file():
302 file_history, _hist = self._get_node_history(commit, f_path)
303
304 res = []
305 for obj in file_history:
306 res.append({
307 'text': obj[1],
308 'children': [{'id': o[0], 'text': o[1]} for o in obj[0]]
309 })
310
311 data = {
312 'more': False,
313 'results': res
314 }
315 return data
316
317 @LoginRequired()
318 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
319 'repository.admin')
320 def authors(self, repo_name, revision, f_path):
321 commit = self.__get_commit_or_redirect(revision, repo_name)
322 file_node = commit.get_node(f_path)
323 if file_node.is_file():
324 c.file_last_commit = file_node.last_commit
325 if request.GET.get('annotate') == '1':
326 # use _hist from annotation if annotation mode is on
327 commit_ids = set(x[1] for x in file_node.annotate)
328 _hist = (
329 c.rhodecode_repo.get_commit(commit_id)
330 for commit_id in commit_ids)
331 else:
332 _f_history, _hist = self._get_node_history(commit, f_path)
333 c.file_author = False
334 c.authors = []
335 for author in set(commit.author for commit in _hist):
336 c.authors.append((
337 h.email(author),
338 h.person(author, 'username_or_name_or_email')))
339 return render('files/file_authors_box.mako')
340
341 @LoginRequired()
342 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
343 'repository.admin')
344 def rawfile(self, repo_name, revision, f_path):
345 """
346 Action for download as raw
347 """
348 commit = self.__get_commit_or_redirect(revision, repo_name)
349 file_node = self.__get_filenode_or_redirect(repo_name, commit, f_path)
350
351 if request.GET.get('lf'):
352 # only if lf get flag is passed, we download this file
353 # as LFS/Largefile
354 lf_node = file_node.get_largefile_node()
355 if lf_node:
356 # overwrite our pointer with the REAL large-file
357 file_node = lf_node
358
359 response.content_disposition = 'attachment; filename=%s' % \
360 safe_str(f_path.split(Repository.NAME_SEP)[-1])
361
362 response.content_type = file_node.mimetype
363 charset = self._get_default_encoding()
364 if charset:
365 response.charset = charset
366
367 return file_node.content
368
369 @LoginRequired()
370 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
371 'repository.admin')
372 def raw(self, repo_name, revision, f_path):
373 """
374 Action for show as raw, some mimetypes are "rendered",
375 those include images, icons.
376 """
377 commit = self.__get_commit_or_redirect(revision, repo_name)
378 file_node = self.__get_filenode_or_redirect(repo_name, commit, f_path)
379
380 raw_mimetype_mapping = {
381 # map original mimetype to a mimetype used for "show as raw"
382 # you can also provide a content-disposition to override the
383 # default "attachment" disposition.
384 # orig_type: (new_type, new_dispo)
385
386 # show images inline:
387 # Do not re-add SVG: it is unsafe and permits XSS attacks. One can
388 # for example render an SVG with javascript inside or even render
389 # HTML.
390 'image/x-icon': ('image/x-icon', 'inline'),
391 'image/png': ('image/png', 'inline'),
392 'image/gif': ('image/gif', 'inline'),
393 'image/jpeg': ('image/jpeg', 'inline'),
394 'application/pdf': ('application/pdf', 'inline'),
395 }
396
397 mimetype = file_node.mimetype
398 try:
399 mimetype, dispo = raw_mimetype_mapping[mimetype]
400 except KeyError:
401 # we don't know anything special about this, handle it safely
402 if file_node.is_binary:
403 # do same as download raw for binary files
404 mimetype, dispo = 'application/octet-stream', 'attachment'
405 else:
406 # do not just use the original mimetype, but force text/plain,
407 # otherwise it would serve text/html and that might be unsafe.
408 # Note: underlying vcs library fakes text/plain mimetype if the
409 # mimetype can not be determined and it thinks it is not
410 # binary.This might lead to erroneous text display in some
411 # cases, but helps in other cases, like with text files
412 # without extension.
413 mimetype, dispo = 'text/plain', 'inline'
414
415 if dispo == 'attachment':
416 dispo = 'attachment; filename=%s' % safe_str(
417 f_path.split(os.sep)[-1])
418
419 response.content_disposition = dispo
420 response.content_type = mimetype
421 charset = self._get_default_encoding()
422 if charset:
423 response.charset = charset
424 return file_node.content
425
426 @CSRFRequired()
427 @LoginRequired()
428 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
429 def delete(self, repo_name, revision, f_path):
430 commit_id = revision
431
432 repo = c.rhodecode_db_repo
433 if repo.enable_locking and repo.locked[0]:
434 h.flash(_('This repository has been locked by %s on %s')
435 % (h.person_by_id(repo.locked[0]),
436 h.format_date(h.time_to_datetime(repo.locked[1]))),
437 'warning')
438 return redirect(h.url('files_home',
439 repo_name=repo_name, revision='tip'))
440
441 if not self._is_valid_head(commit_id, repo.scm_instance()):
442 h.flash(_('You can only delete files with revision '
443 'being a valid branch '), category='warning')
444 return redirect(h.url('files_home',
445 repo_name=repo_name, revision='tip',
446 f_path=f_path))
447
448 c.commit = self.__get_commit_or_redirect(commit_id, repo_name)
449 c.file = self.__get_filenode_or_redirect(repo_name, c.commit, f_path)
450
451 c.default_message = _(
452 'Deleted file {} via RhodeCode Enterprise').format(f_path)
453 c.f_path = f_path
454 node_path = f_path
455 author = c.rhodecode_user.full_contact
456 message = request.POST.get('message') or c.default_message
457 try:
458 nodes = {
459 node_path: {
460 'content': ''
461 }
462 }
463 self.scm_model.delete_nodes(
464 user=c.rhodecode_user.user_id, repo=c.rhodecode_db_repo,
465 message=message,
466 nodes=nodes,
467 parent_commit=c.commit,
468 author=author,
469 )
470
471 h.flash(
472 _('Successfully deleted file `{}`').format(
473 h.escape(f_path)), category='success')
474 except Exception:
475 log.exception('Error during commit operation')
476 h.flash(_('Error occurred during commit'), category='error')
477 return redirect(url('changeset_home',
478 repo_name=c.repo_name, revision='tip'))
479
480 @LoginRequired()
481 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
482 def delete_home(self, repo_name, revision, f_path):
483 commit_id = revision
484
485 repo = c.rhodecode_db_repo
486 if repo.enable_locking and repo.locked[0]:
487 h.flash(_('This repository has been locked by %s on %s')
488 % (h.person_by_id(repo.locked[0]),
489 h.format_date(h.time_to_datetime(repo.locked[1]))),
490 'warning')
491 return redirect(h.url('files_home',
492 repo_name=repo_name, revision='tip'))
493
494 if not self._is_valid_head(commit_id, repo.scm_instance()):
495 h.flash(_('You can only delete files with revision '
496 'being a valid branch '), category='warning')
497 return redirect(h.url('files_home',
498 repo_name=repo_name, revision='tip',
499 f_path=f_path))
500
501 c.commit = self.__get_commit_or_redirect(commit_id, repo_name)
502 c.file = self.__get_filenode_or_redirect(repo_name, c.commit, f_path)
503
504 c.default_message = _(
505 'Deleted file {} via RhodeCode Enterprise').format(f_path)
506 c.f_path = f_path
507
508 return render('files/files_delete.mako')
509
510 @CSRFRequired()
511 @LoginRequired()
512 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
513 def edit(self, repo_name, revision, f_path):
514 commit_id = revision
515
516 repo = c.rhodecode_db_repo
517 if repo.enable_locking and repo.locked[0]:
518 h.flash(_('This repository has been locked by %s on %s')
519 % (h.person_by_id(repo.locked[0]),
520 h.format_date(h.time_to_datetime(repo.locked[1]))),
521 'warning')
522 return redirect(h.url('files_home',
523 repo_name=repo_name, revision='tip'))
524
525 if not self._is_valid_head(commit_id, repo.scm_instance()):
526 h.flash(_('You can only edit files with revision '
527 'being a valid branch '), category='warning')
528 return redirect(h.url('files_home',
529 repo_name=repo_name, revision='tip',
530 f_path=f_path))
531
532 c.commit = self.__get_commit_or_redirect(commit_id, repo_name)
533 c.file = self.__get_filenode_or_redirect(repo_name, c.commit, f_path)
534
535 if c.file.is_binary:
536 return redirect(url('files_home', repo_name=c.repo_name,
537 revision=c.commit.raw_id, f_path=f_path))
538 c.default_message = _(
539 'Edited file {} via RhodeCode Enterprise').format(f_path)
540 c.f_path = f_path
541 old_content = c.file.content
542 sl = old_content.splitlines(1)
543 first_line = sl[0] if sl else ''
544
545 # modes: 0 - Unix, 1 - Mac, 2 - DOS
546 mode = detect_mode(first_line, 0)
547 content = convert_line_endings(request.POST.get('content', ''), mode)
548
549 message = request.POST.get('message') or c.default_message
550 org_f_path = c.file.unicode_path
551 filename = request.POST['filename']
552 org_filename = c.file.name
553
554 if content == old_content and filename == org_filename:
555 h.flash(_('No changes'), category='warning')
556 return redirect(url('changeset_home', repo_name=c.repo_name,
557 revision='tip'))
558 try:
559 mapping = {
560 org_f_path: {
561 'org_filename': org_f_path,
562 'filename': os.path.join(c.file.dir_path, filename),
563 'content': content,
564 'lexer': '',
565 'op': 'mod',
566 }
567 }
568
569 ScmModel().update_nodes(
570 user=c.rhodecode_user.user_id,
571 repo=c.rhodecode_db_repo,
572 message=message,
573 nodes=mapping,
574 parent_commit=c.commit,
575 )
576
577 h.flash(
578 _('Successfully committed changes to file `{}`').format(
579 h.escape(f_path)), category='success')
580 except Exception:
581 log.exception('Error occurred during commit')
582 h.flash(_('Error occurred during commit'), category='error')
583 return redirect(url('changeset_home',
584 repo_name=c.repo_name, revision='tip'))
585
586 @LoginRequired()
587 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
588 def edit_home(self, repo_name, revision, f_path):
589 commit_id = revision
590
591 repo = c.rhodecode_db_repo
592 if repo.enable_locking and repo.locked[0]:
593 h.flash(_('This repository has been locked by %s on %s')
594 % (h.person_by_id(repo.locked[0]),
595 h.format_date(h.time_to_datetime(repo.locked[1]))),
596 'warning')
597 return redirect(h.url('files_home',
598 repo_name=repo_name, revision='tip'))
599
600 if not self._is_valid_head(commit_id, repo.scm_instance()):
601 h.flash(_('You can only edit files with revision '
602 'being a valid branch '), category='warning')
603 return redirect(h.url('files_home',
604 repo_name=repo_name, revision='tip',
605 f_path=f_path))
606
607 c.commit = self.__get_commit_or_redirect(commit_id, repo_name)
608 c.file = self.__get_filenode_or_redirect(repo_name, c.commit, f_path)
609
610 if c.file.is_binary:
611 return redirect(url('files_home', repo_name=c.repo_name,
612 revision=c.commit.raw_id, f_path=f_path))
613 c.default_message = _(
614 'Edited file {} via RhodeCode Enterprise').format(f_path)
615 c.f_path = f_path
616
617 return render('files/files_edit.mako')
618
619 def _is_valid_head(self, commit_id, repo):
620 # check if commit is a branch identifier- basically we cannot
621 # create multiple heads via file editing
622 valid_heads = repo.branches.keys() + repo.branches.values()
623
624 if h.is_svn(repo) and not repo.is_empty():
625 # Note: Subversion only has one head, we add it here in case there
626 # is no branch matched.
627 valid_heads.append(repo.get_commit(commit_idx=-1).raw_id)
628
629 # check if commit is a branch name or branch hash
630 return commit_id in valid_heads
631
632 @CSRFRequired()
633 @LoginRequired()
634 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
635 def add(self, repo_name, revision, f_path):
636 repo = Repository.get_by_repo_name(repo_name)
637 if repo.enable_locking and repo.locked[0]:
638 h.flash(_('This repository has been locked by %s on %s')
639 % (h.person_by_id(repo.locked[0]),
640 h.format_date(h.time_to_datetime(repo.locked[1]))),
641 'warning')
642 return redirect(h.url('files_home',
643 repo_name=repo_name, revision='tip'))
644
645 r_post = request.POST
646
647 c.commit = self.__get_commit_or_redirect(
648 revision, repo_name, redirect_after=False)
649 if c.commit is None:
650 c.commit = EmptyCommit(alias=c.rhodecode_repo.alias)
651 c.default_message = (_('Added file via RhodeCode Enterprise'))
652 c.f_path = f_path
653 unix_mode = 0
654 content = convert_line_endings(r_post.get('content', ''), unix_mode)
655
656 message = r_post.get('message') or c.default_message
657 filename = r_post.get('filename')
658 location = r_post.get('location', '') # dir location
659 file_obj = r_post.get('upload_file', None)
660
661 if file_obj is not None and hasattr(file_obj, 'filename'):
662 filename = r_post.get('filename_upload')
663 content = file_obj.file
664
665 if hasattr(content, 'file'):
666 # non posix systems store real file under file attr
667 content = content.file
668
669 # If there's no commit, redirect to repo summary
670 if type(c.commit) is EmptyCommit:
671 redirect_url = h.route_path('repo_summary', repo_name=c.repo_name)
672 else:
673 redirect_url = url("changeset_home", repo_name=c.repo_name,
674 revision='tip')
675
676 if not filename:
677 h.flash(_('No filename'), category='warning')
678 return redirect(redirect_url)
679
680 # extract the location from filename,
681 # allows using foo/bar.txt syntax to create subdirectories
682 subdir_loc = filename.rsplit('/', 1)
683 if len(subdir_loc) == 2:
684 location = os.path.join(location, subdir_loc[0])
685
686 # strip all crap out of file, just leave the basename
687 filename = os.path.basename(filename)
688 node_path = os.path.join(location, filename)
689 author = c.rhodecode_user.full_contact
690
691 try:
692 nodes = {
693 node_path: {
694 'content': content
695 }
696 }
697 self.scm_model.create_nodes(
698 user=c.rhodecode_user.user_id,
699 repo=c.rhodecode_db_repo,
700 message=message,
701 nodes=nodes,
702 parent_commit=c.commit,
703 author=author,
704 )
705
706 h.flash(
707 _('Successfully committed new file `{}`').format(
708 h.escape(node_path)), category='success')
709 except NonRelativePathError as e:
710 h.flash(_(
711 'The location specified must be a relative path and must not '
712 'contain .. in the path'), category='warning')
713 return redirect(url('changeset_home', repo_name=c.repo_name,
714 revision='tip'))
715 except (NodeError, NodeAlreadyExistsError) as e:
716 h.flash(_(h.escape(e)), category='error')
717 except Exception:
718 log.exception('Error occurred during commit')
719 h.flash(_('Error occurred during commit'), category='error')
720 return redirect(url('changeset_home',
721 repo_name=c.repo_name, revision='tip'))
722
723 @LoginRequired()
724 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
725 def add_home(self, repo_name, revision, f_path):
726
727 repo = Repository.get_by_repo_name(repo_name)
728 if repo.enable_locking and repo.locked[0]:
729 h.flash(_('This repository has been locked by %s on %s')
730 % (h.person_by_id(repo.locked[0]),
731 h.format_date(h.time_to_datetime(repo.locked[1]))),
732 'warning')
733 return redirect(h.url('files_home',
734 repo_name=repo_name, revision='tip'))
735
736 c.commit = self.__get_commit_or_redirect(
737 revision, repo_name, redirect_after=False)
738 if c.commit is None:
739 c.commit = EmptyCommit(alias=c.rhodecode_repo.alias)
740 c.default_message = (_('Added file via RhodeCode Enterprise'))
741 c.f_path = f_path
742
743 return render('files/files_add.mako')
744
745 @LoginRequired()
746 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
747 'repository.admin')
748 def archivefile(self, repo_name, fname):
749 fileformat = None
750 commit_id = None
751 ext = None
752 subrepos = request.GET.get('subrepos') == 'true'
753
754 for a_type, ext_data in settings.ARCHIVE_SPECS.items():
755 archive_spec = fname.split(ext_data[1])
756 if len(archive_spec) == 2 and archive_spec[1] == '':
757 fileformat = a_type or ext_data[1]
758 commit_id = archive_spec[0]
759 ext = ext_data[1]
760
761 dbrepo = RepoModel().get_by_repo_name(repo_name)
762 if not dbrepo.enable_downloads:
763 return _('Downloads disabled')
764
765 try:
766 commit = c.rhodecode_repo.get_commit(commit_id)
767 content_type = settings.ARCHIVE_SPECS[fileformat][0]
768 except CommitDoesNotExistError:
769 return _('Unknown revision %s') % commit_id
770 except EmptyRepositoryError:
771 return _('Empty repository')
772 except KeyError:
773 return _('Unknown archive type')
774
775 # archive cache
776 from rhodecode import CONFIG
777
778 archive_name = '%s-%s%s%s' % (
779 safe_str(repo_name.replace('/', '_')),
780 '-sub' if subrepos else '',
781 safe_str(commit.short_id), ext)
782
783 use_cached_archive = False
784 archive_cache_enabled = CONFIG.get(
785 'archive_cache_dir') and not request.GET.get('no_cache')
786
787 if archive_cache_enabled:
788 # check if we it's ok to write
789 if not os.path.isdir(CONFIG['archive_cache_dir']):
790 os.makedirs(CONFIG['archive_cache_dir'])
791 cached_archive_path = os.path.join(
792 CONFIG['archive_cache_dir'], archive_name)
793 if os.path.isfile(cached_archive_path):
794 log.debug('Found cached archive in %s', cached_archive_path)
795 fd, archive = None, cached_archive_path
796 use_cached_archive = True
797 else:
798 log.debug('Archive %s is not yet cached', archive_name)
799
800 if not use_cached_archive:
801 # generate new archive
802 fd, archive = tempfile.mkstemp()
803 log.debug('Creating new temp archive in %s', archive)
804 try:
805 commit.archive_repo(archive, kind=fileformat, subrepos=subrepos)
806 except ImproperArchiveTypeError:
807 return _('Unknown archive type')
808 if archive_cache_enabled:
809 # if we generated the archive and we have cache enabled
810 # let's use this for future
811 log.debug('Storing new archive in %s', cached_archive_path)
812 shutil.move(archive, cached_archive_path)
813 archive = cached_archive_path
814
815 # store download action
816 audit_logger.store_web(
817 'repo.archive.download', action_data={
818 'user_agent': request.user_agent,
819 'archive_name': archive_name,
820 'archive_spec': fname,
821 'archive_cached': use_cached_archive},
822 user=c.rhodecode_user,
823 repo=dbrepo,
824 commit=True
825 )
826
827 response.content_disposition = str(
828 'attachment; filename=%s' % archive_name)
829 response.content_type = str(content_type)
830
831 def get_chunked_archive(archive):
832 with open(archive, 'rb') as stream:
833 while True:
834 data = stream.read(16 * 1024)
835 if not data:
836 if fd: # fd means we used temporary file
837 os.close(fd)
838 if not archive_cache_enabled:
839 log.debug('Destroying temp archive %s', archive)
840 os.remove(archive)
841 break
842 yield data
843
844 return get_chunked_archive(archive)
845
846 @LoginRequired()
847 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
848 'repository.admin')
849 def diff(self, repo_name, f_path):
850
851 c.action = request.GET.get('diff')
852 diff1 = request.GET.get('diff1', '')
853 diff2 = request.GET.get('diff2', '')
854
855 path1, diff1 = parse_path_ref(diff1, default_path=f_path)
856
857 ignore_whitespace = str2bool(request.GET.get('ignorews'))
858 line_context = request.GET.get('context', 3)
859
860 if not any((diff1, diff2)):
861 h.flash(
862 'Need query parameter "diff1" or "diff2" to generate a diff.',
863 category='error')
864 raise HTTPBadRequest()
865
866 if c.action not in ['download', 'raw']:
867 # redirect to new view if we render diff
868 return redirect(
869 url('compare_url', repo_name=repo_name,
870 source_ref_type='rev',
871 source_ref=diff1,
872 target_repo=c.repo_name,
873 target_ref_type='rev',
874 target_ref=diff2,
875 f_path=f_path))
876
877 try:
878 node1 = self._get_file_node(diff1, path1)
879 node2 = self._get_file_node(diff2, f_path)
880 except (RepositoryError, NodeError):
881 log.exception("Exception while trying to get node from repository")
882 return redirect(url(
883 'files_home', repo_name=c.repo_name, f_path=f_path))
884
885 if all(isinstance(node.commit, EmptyCommit)
886 for node in (node1, node2)):
887 raise HTTPNotFound
888
889 c.commit_1 = node1.commit
890 c.commit_2 = node2.commit
891
892 if c.action == 'download':
893 _diff = diffs.get_gitdiff(node1, node2,
894 ignore_whitespace=ignore_whitespace,
895 context=line_context)
896 diff = diffs.DiffProcessor(_diff, format='gitdiff')
897
898 diff_name = '%s_vs_%s.diff' % (diff1, diff2)
899 response.content_type = 'text/plain'
900 response.content_disposition = (
901 'attachment; filename=%s' % (diff_name,)
902 )
903 charset = self._get_default_encoding()
904 if charset:
905 response.charset = charset
906 return diff.as_raw()
907
908 elif c.action == 'raw':
909 _diff = diffs.get_gitdiff(node1, node2,
910 ignore_whitespace=ignore_whitespace,
911 context=line_context)
912 diff = diffs.DiffProcessor(_diff, format='gitdiff')
913 response.content_type = 'text/plain'
914 charset = self._get_default_encoding()
915 if charset:
916 response.charset = charset
917 return diff.as_raw()
918
919 else:
920 return redirect(
921 url('compare_url', repo_name=repo_name,
922 source_ref_type='rev',
923 source_ref=diff1,
924 target_repo=c.repo_name,
925 target_ref_type='rev',
926 target_ref=diff2,
927 f_path=f_path))
928
929 @LoginRequired()
930 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
931 'repository.admin')
932 def diff_2way(self, repo_name, f_path):
933 """
934 Kept only to make OLD links work
935 """
936 diff1 = request.GET.get('diff1', '')
937 diff2 = request.GET.get('diff2', '')
938
939 if not any((diff1, diff2)):
940 h.flash(
941 'Need query parameter "diff1" or "diff2" to generate a diff.',
942 category='error')
943 raise HTTPBadRequest()
944
945 return redirect(
946 url('compare_url', repo_name=repo_name,
947 source_ref_type='rev',
948 source_ref=diff1,
949 target_repo=c.repo_name,
950 target_ref_type='rev',
951 target_ref=diff2,
952 f_path=f_path,
953 diffmode='sideside'))
954
955 def _get_file_node(self, commit_id, f_path):
956 if commit_id not in ['', None, 'None', '0' * 12, '0' * 40]:
957 commit = c.rhodecode_repo.get_commit(commit_id=commit_id)
958 try:
959 node = commit.get_node(f_path)
960 if node.is_dir():
961 raise NodeError('%s path is a %s not a file'
962 % (node, type(node)))
963 except NodeDoesNotExistError:
964 commit = EmptyCommit(
965 commit_id=commit_id,
966 idx=commit.idx,
967 repo=commit.repository,
968 alias=commit.repository.alias,
969 message=commit.message,
970 author=commit.author,
971 date=commit.date)
972 node = FileNode(f_path, '', commit=commit)
973 else:
974 commit = EmptyCommit(
975 repo=c.rhodecode_repo,
976 alias=c.rhodecode_repo.alias)
977 node = FileNode(f_path, '', commit=commit)
978 return node
979
980 def _get_node_history(self, commit, f_path, commits=None):
981 """
982 get commit history for given node
983
984 :param commit: commit to calculate history
985 :param f_path: path for node to calculate history for
986 :param commits: if passed don't calculate history and take
987 commits defined in this list
988 """
989 # calculate history based on tip
990 tip = c.rhodecode_repo.get_commit()
991 if commits is None:
992 pre_load = ["author", "branch"]
993 try:
994 commits = tip.get_file_history(f_path, pre_load=pre_load)
995 except (NodeDoesNotExistError, CommitError):
996 # this node is not present at tip!
997 commits = commit.get_file_history(f_path, pre_load=pre_load)
998
999 history = []
1000 commits_group = ([], _("Changesets"))
1001 for commit in commits:
1002 branch = ' (%s)' % commit.branch if commit.branch else ''
1003 n_desc = 'r%s:%s%s' % (commit.idx, commit.short_id, branch)
1004 commits_group[0].append((commit.raw_id, n_desc,))
1005 history.append(commits_group)
1006
1007 symbolic_reference = self._symbolic_reference
1008
1009 if c.rhodecode_repo.alias == 'svn':
1010 adjusted_f_path = self._adjust_file_path_for_svn(
1011 f_path, c.rhodecode_repo)
1012 if adjusted_f_path != f_path:
1013 log.debug(
1014 'Recognized svn tag or branch in file "%s", using svn '
1015 'specific symbolic references', f_path)
1016 f_path = adjusted_f_path
1017 symbolic_reference = self._symbolic_reference_svn
1018
1019 branches = self._create_references(
1020 c.rhodecode_repo.branches, symbolic_reference, f_path)
1021 branches_group = (branches, _("Branches"))
1022
1023 tags = self._create_references(
1024 c.rhodecode_repo.tags, symbolic_reference, f_path)
1025 tags_group = (tags, _("Tags"))
1026
1027 history.append(branches_group)
1028 history.append(tags_group)
1029
1030 return history, commits
1031
1032 def _adjust_file_path_for_svn(self, f_path, repo):
1033 """
1034 Computes the relative path of `f_path`.
1035
1036 This is mainly based on prefix matching of the recognized tags and
1037 branches in the underlying repository.
1038 """
1039 tags_and_branches = itertools.chain(
1040 repo.branches.iterkeys(),
1041 repo.tags.iterkeys())
1042 tags_and_branches = sorted(tags_and_branches, key=len, reverse=True)
1043
1044 for name in tags_and_branches:
1045 if f_path.startswith(name + '/'):
1046 f_path = vcspath.relpath(f_path, name)
1047 break
1048 return f_path
1049
1050 def _create_references(
1051 self, branches_or_tags, symbolic_reference, f_path):
1052 items = []
1053 for name, commit_id in branches_or_tags.items():
1054 sym_ref = symbolic_reference(commit_id, name, f_path)
1055 items.append((sym_ref, name))
1056 return items
1057
1058 def _symbolic_reference(self, commit_id, name, f_path):
1059 return commit_id
1060
1061 def _symbolic_reference_svn(self, commit_id, name, f_path):
1062 new_f_path = vcspath.join(name, f_path)
1063 return u'%s@%s' % (new_f_path, commit_id)
1064
1065 @LoginRequired()
1066 @XHRRequired()
1067 @HasRepoPermissionAnyDecorator(
1068 'repository.read', 'repository.write', 'repository.admin')
1069 @jsonify
1070 def nodelist(self, repo_name, revision, f_path):
1071 commit = self.__get_commit_or_redirect(revision, repo_name)
1072
1073 metadata = self._get_nodelist_at_commit(
1074 repo_name, commit.raw_id, f_path)
1075 return {'nodes': metadata}
1076
1077 @LoginRequired()
1078 @XHRRequired()
1079 @HasRepoPermissionAnyDecorator(
1080 'repository.read', 'repository.write', 'repository.admin')
1081 def nodetree_full(self, repo_name, commit_id, f_path):
1082 """
1083 Returns rendered html of file tree that contains commit date,
1084 author, revision for the specified combination of
1085 repo, commit_id and file path
1086
1087 :param repo_name: name of the repository
1088 :param commit_id: commit_id of file tree
1089 :param f_path: file path of the requested directory
1090 """
1091
1092 commit = self.__get_commit_or_redirect(commit_id, repo_name)
1093 try:
1094 dir_node = commit.get_node(f_path)
1095 except RepositoryError as e:
1096 return 'error {}'.format(safe_str(e))
1097
1098 if dir_node.is_file():
1099 return ''
1100
1101 c.file = dir_node
1102 c.commit = commit
1103
1104 # using force=True here, make a little trick. We flush the cache and
1105 # compute it using the same key as without full_load, so the fully
1106 # loaded cached tree is now returned instead of partial
1107 return self._get_tree_at_commit(
1108 repo_name, commit.raw_id, dir_node.path, full_load=True,
1109 force=True)
General Comments 0
You need to be logged in to leave comments. Login now