##// 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 251 # TODO: johbo: Unify generation of reference links
252 252 use_commit_id = '/' in ref_name or is_svn
253 files_url = h.url(
254 'files_home',
253
254 if use_commit_id:
255 files_url = h.route_path(
256 'repo_files',
255 257 repo_name=self.db_repo_name,
256 258 f_path=ref_name if is_svn else '',
257 revision=commit_id if use_commit_id else ref_name,
258 at=ref_name)
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 269 data.append({
261 270 "name": _render('name', ref_name, files_url, closed),
@@ -37,6 +37,95 b' def includeme(config):'
37 37 name='repo_commit',
38 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 129 # refs data
41 130 config.add_route(
42 131 name='repo_refs_data',
This diff has been collapsed as it changes many lines, (885 lines changed) Show them Hide them
@@ -23,15 +23,14 b' import os'
23 23 import mock
24 24 import pytest
25 25
26 from rhodecode.controllers.files import FilesController
26 from rhodecode.apps.repository.views.repo_files import RepoFilesView
27 27 from rhodecode.lib import helpers as h
28 28 from rhodecode.lib.compat import OrderedDict
29 29 from rhodecode.lib.ext_json import json
30 30 from rhodecode.lib.vcs import nodes
31 31
32 32 from rhodecode.lib.vcs.conf import settings
33 from rhodecode.tests import (
34 url, assert_session_flash, assert_not_in_session_flash)
33 from rhodecode.tests import assert_session_flash
35 34 from rhodecode.tests.fixture import Fixture
36 35
37 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 102 @pytest.mark.usefixtures("app")
48 class TestFilesController:
103 class TestFilesViews(object):
49 104
50 def test_index(self, backend):
51 response = self.app.get(url(
52 controller='files', action='index',
53 repo_name=backend.repo_name, revision='tip', f_path='/'))
105 def test_show_files(self, backend):
106 response = self.app.get(
107 route_path('repo_files',
108 repo_name=backend.repo_name,
109 commit_id='tip', f_path='/'))
54 110 commit = backend.repo.get_commit()
55 111
56 112 params = {
@@ -77,21 +133,23 b' class TestFilesController:'
77 133 assert_files_in_response(response, files, params)
78 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 137 repo = backend_hg['subrepos']
82 response = self.app.get(url(
83 controller='files', action='index',
84 repo_name=repo.repo_name, revision='tip', f_path='/'))
138 response = self.app.get(
139 route_path('repo_files',
140 repo_name=repo.repo_name,
141 commit_id='tip', f_path='/'))
85 142 assert_response = response.assert_response()
86 143 assert_response.contains_one_link(
87 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 147 self, backend_hg):
91 148 repo = backend_hg['subrepos']
92 response = self.app.get(url(
93 controller='files', action='index',
94 repo_name=repo.repo_name, revision='tip', f_path='/'))
149 response = self.app.get(
150 route_path('repo_files',
151 repo_name=repo.repo_name,
152 commit_id='tip', f_path='/'))
95 153 assert_response = response.assert_response()
96 154 assert_response.contains_one_link(
97 155 'subpaths-path @ 000000000000',
@@ -108,29 +166,29 b' class TestFilesController:'
108 166
109 167 backend.repo.landing_rev = "branch:%s" % new_branch
110 168
111 # get response based on tip and not new revision
112 response = self.app.get(url(
113 controller='files', action='index',
114 repo_name=backend.repo_name, revision='tip', f_path='/'),
115 status=200)
169 # get response based on tip and not new commit
170 response = self.app.get(
171 route_path('repo_files',
172 repo_name=backend.repo_name,
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 176 landing_rev = backend.repo.landing_rev[1]
119 files_url = url('files_home', repo_name=backend.repo_name,
120 revision=landing_rev)
177 files_url = route_path('repo_files:default_path',
178 repo_name=backend.repo_name,
179 commit_id=landing_rev)
121 180
122 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 186 commit = backend.repo.get_commit(commit_idx=32)
127 187
128 response = self.app.get(url(
129 controller='files', action='index',
188 response = self.app.get(
189 route_path('repo_files',
130 190 repo_name=backend.repo_name,
131 revision=commit.raw_id,
132 f_path='/')
133 )
191 commit_id=commit.raw_id, f_path='/'))
134 192
135 193 dirs = ['docs', 'tests']
136 194 files = ['README.rst']
@@ -141,7 +199,7 b' class TestFilesController:'
141 199 assert_dirs_in_response(response, dirs, params)
142 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 203 branches = dict(
146 204 hg=(150, ['git']),
147 205 # TODO: Git test repository does not contain other branches
@@ -151,104 +209,79 b' class TestFilesController:'
151 209 )
152 210 idx, branches = branches[backend.alias]
153 211 commit = backend.repo.get_commit(commit_idx=idx)
154 response = self.app.get(url(
155 controller='files', action='index',
212 response = self.app.get(
213 route_path('repo_files',
156 214 repo_name=backend.repo_name,
157 revision=commit.raw_id,
158 f_path='/'))
215 commit_id=commit.raw_id, f_path='/'))
216
159 217 assert_response = response.assert_response()
160 218 for branch in branches:
161 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 222 repo = backend.repo
165 223 indexes = [73, 92, 109, 1, 0]
166 224 idx_map = [(rev, repo.get_commit(commit_idx=rev).raw_id)
167 225 for rev in indexes]
168 226
169 227 for idx in idx_map:
170 response = self.app.get(url(
171 controller='files', action='index',
228 response = self.app.get(
229 route_path('repo_files',
172 230 repo_name=backend.repo_name,
173 revision=idx[1],
174 f_path='/'))
231 commit_id=idx[1], f_path='/'))
175 232
176 233 response.mustcontain("""r%s:%s""" % (idx[0], idx[1][:8]))
177 234
178 235 def test_file_source(self, backend):
179 236 commit = backend.repo.get_commit(commit_idx=167)
180 response = self.app.get(url(
181 controller='files', action='index',
237 response = self.app.get(
238 route_path('repo_files',
182 239 repo_name=backend.repo_name,
183 revision=commit.raw_id,
184 f_path='vcs/nodes.py'))
240 commit_id=commit.raw_id, f_path='vcs/nodes.py'))
185 241
186 242 msgbox = """<div class="commit right-content">%s</div>"""
187 243 response.mustcontain(msgbox % (commit.message, ))
188 244
189 245 assert_response = response.assert_response()
190 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 249 if commit.tags:
193 250 for tag in commit.tags:
194 251 assert_response.element_contains('.tags.tags-main .tagtag', tag)
195 252
196 def test_file_source_history(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']
253 def test_file_source_annotated(self, backend):
208 254 response = self.app.get(
209 url(
210 controller='files', action='history',
211 repo_name=simple_repo.repo_name,
212 revision='tip',
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',
255 route_path('repo_files:annotated',
224 256 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 = {
257 commit_id='tip', f_path='vcs/nodes.py'))
258 expected_commits = {
238 259 'hg': 'r356',
239 260 'git': 'r345',
240 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):
245 response = self.app.get(url(
246 controller='files', action='authors',
265 def test_file_source_authors(self, backend):
266 response = self.app.get(
267 route_path('repo_file_authors',
247 268 repo_name=backend.repo_name,
248 revision='tip',
249 f_path='vcs/nodes.py',
250 annotate=True))
269 commit_id='tip', f_path='vcs/nodes.py'))
270 expected_authors = {
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 285 expected_authors = {
253 286 'hg': ('Marcin Kuzminski', 'Lukasz Balcerzak'),
254 287 'git': ('Marcin Kuzminski', 'Lukasz Balcerzak'),
@@ -258,59 +291,89 b' class TestFilesController:'
258 291 for author in expected_authors[backend.alias]:
259 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 324 def test_tree_search_top_level(self, backend, xhr_header):
262 325 commit = backend.repo.get_commit(commit_idx=173)
263 326 response = self.app.get(
264 url('files_nodelist_home', repo_name=backend.repo_name,
265 revision=commit.raw_id, f_path='/'),
327 route_path('repo_files_nodelist',
328 repo_name=backend.repo_name,
329 commit_id=commit.raw_id, f_path='/'),
266 330 extra_environ=xhr_header)
267 331 assert 'nodes' in response.json
268 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 341 def test_tree_search_at_path(self, backend, xhr_header):
271 342 commit = backend.repo.get_commit(commit_idx=173)
272 343 response = self.app.get(
273 url('files_nodelist_home', repo_name=backend.repo_name,
274 revision=commit.raw_id, f_path='/docs'),
344 route_path('repo_files_nodelist',
345 repo_name=backend.repo_name,
346 commit_id=commit.raw_id, f_path='/docs'),
275 347 extra_environ=xhr_header)
276 348 assert 'nodes' in response.json
277 349 nodes = response.json['nodes']
278 350 assert {'name': 'docs/api', 'type': 'dir'} in nodes
279 351 assert {'name': 'docs/index.rst', 'type': 'file'} in nodes
280 352
281 def test_tree_search_at_path_missing_xhr(self, backend):
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):
353 def test_tree_search_at_path_2nd_level(self, backend, xhr_header):
296 354 commit = backend.repo.get_commit(commit_idx=173)
297 355 response = self.app.get(
298 url('files_nodelist_home', repo_name=backend.repo_name,
299 f_path='/docs', revision=commit.raw_id),
300 extra_environ=xhr_header,
301 )
302 response.mustcontain("docs/index.rst")
356 route_path('repo_files_nodelist',
357 repo_name=backend.repo_name,
358 commit_id=commit.raw_id, f_path='/docs/api'),
359 extra_environ=xhr_header)
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 365 self.app.get(
306 url('files_nodelist_home', repo_name=backend.repo_name,
307 f_path='/', revision='tip'), status=400)
366 route_path('repo_files_nodelist',
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 372 commit = backend.repo.get_commit(commit_idx=173)
311 373 response = self.app.get(
312 url('files_nodetree_full', repo_name=backend.repo_name,
313 f_path='/', commit_id=commit.raw_id),
374 route_path('repo_nodetree_full',
375 repo_name=backend.repo_name,
376 commit_id=commit.raw_id, f_path='/'),
314 377 extra_environ=xhr_header)
315 378
316 379 assert_response = response.assert_response()
@@ -322,79 +385,130 b' class TestFilesController:'
322 385 for element in elements:
323 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 389 commit = backend.repo.get_commit(commit_idx=173)
327 390 response = self.app.get(
328 url('files_nodetree_full', repo_name=backend.repo_name,
329 f_path='README.rst', commit_id=commit.raw_id),
391 route_path('repo_nodetree_full',
392 repo_name=backend.repo_name,
393 commit_id=commit.raw_id, f_path='README.rst'),
330 394 extra_environ=xhr_header)
331 395 assert response.body == ''
332 396
333 def test_tree_metadata_list_missing_xhr(self, backend):
334 self.app.get(
335 url('files_nodetree_full', repo_name=backend.repo_name,
336 f_path='/', commit_id='tip'), status=400)
397 def test_nodetree_wrong_path(self, backend, xhr_header):
398 commit = backend.repo.get_commit(commit_idx=173)
399 response = self.app.get(
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(
339 self, app, backend_stub, autologin_regular_user, user_regular,
340 user_util):
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))
405 err = 'error: There is no file nor ' \
406 'directory at the given path'
407 assert err in response.body
352 408
353 def test_access_empty_repo_redirect_to_summary_with_alert_no_write_perms(
354 self, backend_stub, user_util):
355 repo = backend_stub.create_repo()
356 repo_file_url = url(
357 'files_add_home',
358 repo_name=repo.repo_name,
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)
409 def test_nodetree_missing_xhr(self, backend):
410 self.app.get(
411 route_path('repo_nodetree_full',
412 repo_name=backend.repo_name,
413 commit_id='tip', f_path='/'),
414 status=404)
364 415
365 416
366 # TODO: johbo: Think about a better place for these tests. Either controller
367 # specific unit tests or we move down the whole logic further towards the vcs
368 # layer
369 class TestAdjustFilePathForSvn(object):
370 """SVN specific adjustments of node history in FileController."""
417 @pytest.mark.usefixtures("app", "autologin_user")
418 class TestRawFileHandling(object):
419
420 def test_download_file(self, backend):
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):
373 repo = self._repo(branches=['trunk'])
374 self.assert_file_adjustment('trunk/file', 'file', repo)
439 msg = """No such commit exists for this repository"""
440 response.mustcontain(msg)
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):
377 repo = self._repo(branches=['trunk'])
378 self.assert_file_adjustment('notes/file', 'notes/file', repo)
446 response = self.app.get(
447 route_path('repo_file_download',
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):
381 repo = self._repo(branches=['trun'])
382 self.assert_file_adjustment('trunk/file', 'trunk/file', repo)
464 assert response.content_type == "text/plain"
465
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):
385 repo = self._repo(branches=['trunk', 'trunk/new', 'trunk/old'])
386 self.assert_file_adjustment('trunk/new/file', 'file', repo)
476 def test_raw_file_wrong_cs(self, backend):
477 raw_id = u'ERRORcce30c96924232dffcd24178a07ffeb5dfc'
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):
389 controller = FilesController()
390 result = controller._adjust_file_path_for_svn(f_path, repo)
391 assert result == expected
488 def test_raw_wrong_f_path(self, backend):
489 commit = backend.repo.get_commit(commit_idx=173)
490 f_path = 'vcs/ERRORnodes.py'
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):
394 repo = mock.Mock()
395 repo.branches = OrderedDict((name, '0') for name in branches or [])
396 repo.tags = {}
397 return repo
497 msg = (
498 "There is no file nor directory at the given path: "
499 "`%s` at commit %s" % (f_path, commit.short_id))
500 response.mustcontain(msg)
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 514 @pytest.mark.usefixtures("app")
@@ -408,133 +522,50 b' class TestRepositoryArchival(object):'
408 522 short = commit.short_id + arch_ext
409 523 fname = commit.raw_id + arch_ext
410 524 filename = '%s-%s' % (backend.repo_name, short)
411 response = self.app.get(url(controller='files',
412 action='archivefile',
525 response = self.app.get(
526 route_path('repo_archivefile',
413 527 repo_name=backend.repo_name,
414 528 fname=fname))
415 529
416 530 assert response.status == '200 OK'
417 531 headers = [
418 ('Pragma', 'no-cache'),
419 ('Cache-Control', 'no-cache'),
420 532 ('Content-Disposition', 'attachment; filename=%s' % filename),
421 533 ('Content-Type', '%s' % mime_type),
422 534 ]
423 if 'Set-Cookie' in response.response.headers:
424 del response.response.headers['Set-Cookie']
425 assert response.response.headers.items() == headers
535
536 for header in 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 542 backend.enable_downloads()
429 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',
434 action='archivefile',
435 repo_name=backend.repo_name,
436 fname=fname))
437 response.mustcontain('Unknown archive type')
545 fname = commit.raw_id + '.' + arch_ext
438 546
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',
547 response = self.app.get(
548 route_path('repo_archivefile',
447 549 repo_name=backend.repo_name,
448 550 fname=fname))
449 response.mustcontain('Unknown revision')
450
451
452 @pytest.mark.usefixtures("app", "autologin_user")
453 class TestRawFileHandling(object):
454
455 def test_raw_file_ok(self, backend):
456 commit = backend.repo.get_commit(commit_idx=173)
457 response = self.app.get(url(controller='files', action='rawfile',
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
477 def test_raw_file_wrong_f_path(self, backend):
478 commit = backend.repo.get_commit(commit_idx=173)
479 f_path = 'vcs/ERRORnodes.py'
480 response = self.app.get(url(controller='files', action='rawfile',
481 repo_name=backend.repo_name,
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)
551 response.mustcontain(
552 'Unknown archive type for: `{}`'.format(fname))
489 553
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
503 response = self.app.get(url(controller='files', action='raw',
504 repo_name=backend.repo_name,
505 revision=commit_id,
506 f_path=f_path), status=404)
507
508 msg = """No such commit exists for this repository"""
509 response.mustcontain(msg)
554 @pytest.mark.parametrize('commit_id', [
555 '00x000000', 'tar', 'wrong', '@$@$42413232', '232dffcd'])
556 def test_archival_wrong_commit_id(self, backend, commit_id):
557 backend.enable_downloads()
558 fname = '%s.zip' % commit_id
510 559
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',
560 response = self.app.get(
561 route_path('repo_archivefile',
515 562 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"
563 fname=fname))
564 response.mustcontain('Unknown commit_id')
534 565
535 566
536 567 @pytest.mark.usefixtures("app")
537 class TestFilesDiff:
568 class TestFilesDiff(object):
538 569
539 570 @pytest.mark.parametrize("diff", ['diff', 'download', 'raw'])
540 571 def test_file_full_diff(self, backend, diff):
@@ -542,9 +573,7 b' class TestFilesDiff:'
542 573 commit2 = backend.repo.get_commit(commit_idx=-2)
543 574
544 575 response = self.app.get(
545 url(
546 controller='files',
547 action='diff',
576 route_path('repo_files_diff',
548 577 repo_name=backend.repo_name,
549 578 f_path='README'),
550 579 params={
@@ -571,9 +600,7 b' class TestFilesDiff:'
571 600 repo = backend.create_repo(commits=commits)
572 601
573 602 response = self.app.get(
574 url(
575 controller='files',
576 action='diff',
603 route_path('repo_files_diff',
577 604 repo_name=backend.repo_name,
578 605 f_path='file.bin'),
579 606 params={
@@ -598,9 +625,7 b' class TestFilesDiff:'
598 625 commit1 = backend.repo.get_commit(commit_idx=-1)
599 626 commit2 = backend.repo.get_commit(commit_idx=-2)
600 627 response = self.app.get(
601 url(
602 controller='files',
603 action='diff_2way',
628 route_path('repo_files_diff_2way_redirect',
604 629 repo_name=backend.repo_name,
605 630 f_path='README'),
606 631 params={
@@ -616,9 +641,7 b' class TestFilesDiff:'
616 641
617 642 def test_requires_one_commit_id(self, backend, autologin_user):
618 643 response = self.app.get(
619 url(
620 controller='files',
621 action='diff',
644 route_path('repo_files_diff',
622 645 repo_name=backend.repo_name,
623 646 f_path='README.rst'),
624 647 status=400)
@@ -628,30 +651,28 b' class TestFilesDiff:'
628 651 def test_returns_no_files_if_file_does_not_exist(self, vcsbackend):
629 652 repo = vcsbackend.repo
630 653 response = self.app.get(
631 url(
632 controller='files',
633 action='diff',
654 route_path('repo_files_diff',
634 655 repo_name=repo.name,
635 f_path='does-not-exist-in-any-commit',
636 diff1=repo[0].raw_id,
637 diff2=repo[1].raw_id),)
656 f_path='does-not-exist-in-any-commit'),
657 params={
658 'diff1': repo[0].raw_id,
659 'diff2': repo[1].raw_id
660 })
638 661
639 662 response = response.follow()
640 663 response.mustcontain('No files')
641 664
642 665 def test_returns_redirect_if_file_not_changed(self, backend):
643 666 commit = backend.repo.get_commit(commit_idx=-1)
644 f_path = 'README'
645 667 response = self.app.get(
646 url(
647 controller='files',
648 action='diff_2way',
668 route_path('repo_files_diff_2way_redirect',
649 669 repo_name=backend.repo_name,
650 f_path=f_path,
651 diff1=commit.raw_id,
652 diff2=commit.raw_id,
653 ),
654 )
670 f_path='README'),
671 params={
672 'diff1': commit.raw_id,
673 'diff2': commit.raw_id,
674 })
675
655 676 response = response.follow()
656 677 response.mustcontain('No files')
657 678 response.mustcontain('No commits in this compare')
@@ -664,23 +685,14 b' class TestFilesDiff:'
664 685 commit_id_1 = '24'
665 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 688 response = self.app.get(
677 url(
678 controller='files',
679 action='diff',
680 repo_name=repo.name,
681 f_path='trunk/example.py',
682 diff1='tags/v0.2/example.py@' + commit_id_1,
683 diff2=commit_id_2))
689 route_path('repo_files_diff',
690 repo_name=backend_svn.repo_name,
691 f_path='trunk/example.py'),
692 params={
693 'diff1': 'tags/v0.2/example.py@' + commit_id_1,
694 'diff2': commit_id_2,
695 })
684 696
685 697 response = response.follow()
686 698 response.mustcontain(
@@ -697,16 +709,17 b' class TestFilesDiff:'
697 709
698 710 repo = backend_svn['svn-simple-layout'].scm_instance()
699 711 commit_id = repo[-1].raw_id
712
700 713 response = self.app.get(
701 url(
702 controller='files',
703 action='diff',
704 repo_name=repo.name,
705 f_path='trunk/example.py',
706 diff1='branches/argparse/example.py@' + commit_id,
707 diff2=commit_id),
708 params={'show_rev': 'Show at Revision'},
714 route_path('repo_files_diff',
715 repo_name=backend_svn.repo_name,
716 f_path='trunk/example.py'),
717 params={
718 'diff1': 'branches/argparse/example.py@' + commit_id,
719 'diff2': commit_id,
720 },
709 721 status=302)
722 response = response.follow()
710 723 assert response.headers['Location'].endswith(
711 724 'svn-svn-simple-layout/files/26/branches/argparse/example.py')
712 725
@@ -717,40 +730,39 b' class TestFilesDiff:'
717 730 repo = backend_svn['svn-simple-layout'].scm_instance()
718 731 commit_id = repo[-1].raw_id
719 732 response = self.app.get(
720 url(
721 controller='files',
722 action='diff',
723 repo_name=repo.name,
724 f_path='trunk/example.py',
725 diff1='branches/argparse/example.py@' + commit_id,
726 diff2=commit_id),
733 route_path('repo_files_diff',
734 repo_name=backend_svn.repo_name,
735 f_path='trunk/example.py'),
727 736 params={
737 'diff1': 'branches/argparse/example.py@' + commit_id,
738 'diff2': commit_id,
728 739 'show_rev': 'Show at Revision',
729 740 'annotate': 'true',
730 741 },
731 742 status=302)
743 response = response.follow()
732 744 assert response.headers['Location'].endswith(
733 745 'svn-svn-simple-layout/annotate/26/branches/argparse/example.py')
734 746
735 747
736 748 @pytest.mark.usefixtures("app", "autologin_user")
737 class TestChangingFiles:
749 class TestModifyFilesWithWebInterface(object):
738 750
739 751 def test_add_file_view(self, backend):
740 self.app.get(url(
741 'files_add_home',
752 self.app.get(
753 route_path('repo_files_add_file',
742 754 repo_name=backend.repo_name,
743 revision='tip', f_path='/'))
755 commit_id='tip', f_path='/')
756 )
744 757
745 758 @pytest.mark.xfail_backends("svn", reason="Depends on online editing")
746 759 def test_add_file_into_repo_missing_content(self, backend, csrf_token):
747 760 repo = backend.create_repo()
748 761 filename = 'init.py'
749 762 response = self.app.post(
750 url(
751 'files_add',
752 repo_name=repo.repo_name,
753 revision='tip', f_path='/'),
763 route_path('repo_files_create_file',
764 repo_name=backend.repo_name,
765 commit_id='tip', f_path='/'),
754 766 params={
755 767 'content': "",
756 768 'filename': filename,
@@ -759,14 +771,14 b' class TestChangingFiles:'
759 771 },
760 772 status=302)
761 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 777 def test_add_file_into_repo_missing_filename(self, backend, csrf_token):
765 778 response = self.app.post(
766 url(
767 'files_add',
779 route_path('repo_files_create_file',
768 780 repo_name=backend.repo_name,
769 revision='tip', f_path='/'),
781 commit_id='tip', f_path='/'),
770 782 params={
771 783 'content': "foo",
772 784 'csrf_token': csrf_token,
@@ -781,10 +793,9 b' class TestChangingFiles:'
781 793 # Create a file with no filename, it will display an error but
782 794 # the repo has no commits yet
783 795 response = self.app.post(
784 url(
785 'files_add',
796 route_path('repo_files_create_file',
786 797 repo_name=repo.repo_name,
787 revision='tip', f_path='/'),
798 commit_id='tip', f_path='/'),
788 799 params={
789 800 'content': "foo",
790 801 'csrf_token': csrf_token,
@@ -810,10 +821,9 b' class TestChangingFiles:'
810 821 def test_add_file_into_repo_bad_filenames(
811 822 self, location, filename, backend, csrf_token):
812 823 response = self.app.post(
813 url(
814 'files_add',
824 route_path('repo_files_create_file',
815 825 repo_name=backend.repo_name,
816 revision='tip', f_path='/'),
826 commit_id='tip', f_path='/'),
817 827 params={
818 828 'content': "foo",
819 829 'filename': filename,
@@ -836,10 +846,9 b' class TestChangingFiles:'
836 846 csrf_token):
837 847 repo = backend.create_repo()
838 848 response = self.app.post(
839 url(
840 'files_add',
849 route_path('repo_files_create_file',
841 850 repo_name=repo.repo_name,
842 revision='tip', f_path='/'),
851 commit_id='tip', f_path='/'),
843 852 params={
844 853 'content': "foo",
845 854 'filename': filename,
@@ -853,10 +862,9 b' class TestChangingFiles:'
853 862
854 863 def test_edit_file_view(self, backend):
855 864 response = self.app.get(
856 url(
857 'files_edit_home',
865 route_path('repo_files_edit_file',
858 866 repo_name=backend.repo_name,
859 revision=backend.default_head_id,
867 commit_id=backend.default_head_id,
860 868 f_path='vcs/nodes.py'),
861 869 status=200)
862 870 response.mustcontain("Module holding everything related to vcs nodes.")
@@ -866,24 +874,23 b' class TestChangingFiles:'
866 874 backend.ensure_file("vcs/nodes.py")
867 875
868 876 response = self.app.get(
869 url(
870 'files_edit_home',
877 route_path('repo_files_edit_file',
871 878 repo_name=repo.repo_name,
872 revision='tip', f_path='vcs/nodes.py'),
879 commit_id='tip',
880 f_path='vcs/nodes.py'),
873 881 status=302)
874 882 assert_session_flash(
875 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 886 def test_edit_file_view_commit_changes(self, backend, csrf_token):
879 887 repo = backend.create_repo()
880 888 backend.ensure_file("vcs/nodes.py", content="print 'hello'")
881 889
882 890 response = self.app.post(
883 url(
884 'files_edit',
891 route_path('repo_files_update_file',
885 892 repo_name=repo.repo_name,
886 revision=backend.default_head_id,
893 commit_id=backend.default_head_id,
887 894 f_path='vcs/nodes.py'),
888 895 params={
889 896 'content': "print 'hello world'",
@@ -907,10 +914,9 b' class TestChangingFiles:'
907 914 backend.repo.scm_instance().commit_ids[-1])
908 915
909 916 response = self.app.post(
910 url(
911 'files_edit',
917 route_path('repo_files_update_file',
912 918 repo_name=repo.repo_name,
913 revision=commit_id,
919 commit_id=commit_id,
914 920 f_path='vcs/nodes.py'),
915 921 params={
916 922 'content': "print 'hello world'",
@@ -925,34 +931,35 b' class TestChangingFiles:'
925 931 assert tip.message == 'Edited file vcs/nodes.py via RhodeCode Enterprise'
926 932
927 933 def test_delete_file_view(self, backend):
928 self.app.get(url(
929 'files_delete_home',
934 self.app.get(
935 route_path('repo_files_remove_file',
930 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 941 def test_delete_file_view_not_on_branch(self, backend):
934 942 repo = backend.create_repo()
935 943 backend.ensure_file('vcs/nodes.py')
936 944
937 945 response = self.app.get(
938 url(
939 'files_delete_home',
946 route_path('repo_files_remove_file',
940 947 repo_name=repo.repo_name,
941 revision='tip', f_path='vcs/nodes.py'),
948 commit_id='tip',
949 f_path='vcs/nodes.py'),
942 950 status=302)
943 951 assert_session_flash(
944 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 955 def test_delete_file_view_commit_changes(self, backend, csrf_token):
948 956 repo = backend.create_repo()
949 957 backend.ensure_file("vcs/nodes.py")
950 958
951 959 response = self.app.post(
952 url(
953 'files_delete_home',
960 route_path('repo_files_delete_file',
954 961 repo_name=repo.repo_name,
955 revision=backend.default_head_id,
962 commit_id=backend.default_head_id,
956 963 f_path='vcs/nodes.py'),
957 964 params={
958 965 'message': 'i commited',
@@ -963,25 +970,93 b' class TestChangingFiles:'
963 970 response, 'Successfully deleted file `vcs/nodes.py`')
964 971
965 972
966 def assert_files_in_response(response, files, params):
967 template = (
968 'href="/%(repo_name)s/files/%(commit_id)s/%(name)s"')
969 _assert_items_in_response(response, files, template, params)
973 @pytest.mark.usefixtures("app")
974 class TestFilesViewOtherCases(object):
975
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):
973 template = (
974 'href="/%(repo_name)s/files/%(commit_id)s/%(name)s"')
975 _assert_items_in_response(response, dirs, template, params)
1033 class TestAdjustFilePathForSvn(object):
1034 """
1035 SVN specific adjustments of node history in RepoFilesView.
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):
979 for item in items:
980 item_params = {'name': item}
981 item_params.update(params)
982 response.mustcontain(template % item_params)
1046 def test_does_not_adjust_partial_directory_names(self):
1047 repo = self._repo(branches=['trun'])
1048 self.assert_file_adjustment('trunk/file', 'trunk/file', repo)
1049
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):
986 for item in items:
987 response.mustcontain(h.age_component(params['date']))
1058 def _repo(self, branches=None):
1059 repo = mock.Mock()
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 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 388 repo = mock.Mock()
389 389 repo.name = 'abcde'
390 390 full_repo_name = 'test-repo-group/' + repo.name
@@ -392,15 +392,15 b' class TestCreateFilesUrl(object):'
392 392 raw_id = 'deadbeef0123456789'
393 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 396 result = summary_view._create_files_url(
397 397 repo, full_repo_name, ref_name, raw_id, is_svn)
398 398 url_mock.assert_called_once_with(
399 'files_home', repo_name=full_repo_name, f_path='',
400 revision=ref_name, at=ref_name)
399 'repo_files', repo_name=full_repo_name, commit_id=ref_name,
400 f_path='', _query=dict(at=ref_name))
401 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 404 repo = mock.Mock()
405 405 repo.name = 'abcde'
406 406 full_repo_name = 'test-repo-group/' + repo.name
@@ -408,15 +408,15 b' class TestCreateFilesUrl(object):'
408 408 raw_id = 'deadbeef0123456789'
409 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 412 result = summary_view._create_files_url(
413 413 repo, full_repo_name, ref_name, raw_id, is_svn)
414 414 url_mock.assert_called_once_with(
415 'files_home', repo_name=full_repo_name, f_path=ref_name,
416 revision=raw_id, at=ref_name)
415 'repo_files', repo_name=full_repo_name, f_path=ref_name,
416 commit_id=raw_id, _query=dict(at=ref_name))
417 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 420 repo = mock.Mock()
421 421 repo.name = 'abcde'
422 422 full_repo_name = 'test-repo-group/' + repo.name
@@ -424,12 +424,12 b' class TestCreateFilesUrl(object):'
424 424 raw_id = 'deadbeef0123456789'
425 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 428 result = summary_view._create_files_url(
429 429 repo, full_repo_name, ref_name, raw_id, is_svn)
430 430 url_mock.assert_called_once_with(
431 'files_home', repo_name=full_repo_name, f_path='', revision=raw_id,
432 at=ref_name)
431 'repo_files', repo_name=full_repo_name, commit_id=raw_id,
432 f_path='', _query=dict(at=ref_name))
433 433 assert result == url_mock.return_value
434 434
435 435
@@ -74,10 +74,9 b' class RepoSummaryView(RepoAppView):'
74 74 log.debug("Searching for a README file.")
75 75 readme_node = ReadmeFinder(default_renderer).search(commit)
76 76 if readme_node:
77 relative_url = h.url('files_raw_home',
78 repo_name=repo_name,
79 revision=commit.raw_id,
80 f_path=readme_node.path)
77 relative_url = h.route_path(
78 'repo_file_raw', repo_name=repo_name,
79 commit_id=commit.raw_id, f_path=readme_node.path)
81 80 readme_data = self._render_readme_or_none(
82 81 commit, readme_node, relative_url)
83 82 readme_filename = readme_node.path
@@ -360,9 +359,9 b' class RepoSummaryView(RepoAppView):'
360 359
361 360 def _create_files_url(self, repo, full_repo_name, ref_name, raw_id, is_svn):
362 361 use_commit_id = '/' in ref_name or is_svn
363 return h.url(
364 'files_home',
362 return h.route_path(
363 'repo_files',
365 364 repo_name=full_repo_name,
366 365 f_path=ref_name if is_svn else '',
367 revision=raw_id if use_commit_id else ref_name,
368 at=ref_name)
366 commit_id=raw_id if use_commit_id else ref_name,
367 _query=dict(at=ref_name))
@@ -153,6 +153,7 b' def make_pyramid_app(global_config, **se'
153 153
154 154 includeme_first(config)
155 155 includeme(config)
156
156 157 pyramid_app = config.make_wsgi_app()
157 158 pyramid_app = wrap_app_in_wsgi_middlewares(pyramid_app, config)
158 159 pyramid_app.config = config
@@ -726,132 +726,6 b' def make_map(config):'
726 726 conditions={'function': check_repo},
727 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 729 rmap.connect('repo_fork_create_home', '/{repo_name}/fork',
856 730 controller='forks', action='fork_create',
857 731 conditions={'function': check_repo, 'method': ['POST']},
@@ -258,9 +258,10 b' def files_breadcrumbs(repo_name, commit_'
258 258 url_segments = [
259 259 link_to(
260 260 repo_name_html,
261 url('files_home',
261 route_path(
262 'repo_files',
262 263 repo_name=repo_name,
263 revision=commit_id,
264 commit_id=commit_id,
264 265 f_path=''),
265 266 class_='pjax-link')]
266 267
@@ -274,9 +275,10 b' def files_breadcrumbs(repo_name, commit_'
274 275 url_segments.append(
275 276 link_to(
276 277 segment_html,
277 url('files_home',
278 route_path(
279 'repo_files',
278 280 repo_name=repo_name,
279 revision=commit_id,
281 commit_id=commit_id,
280 282 f_path='/'.join(path_segments[:cnt + 1])),
281 283 class_='pjax-link'))
282 284 else:
@@ -1541,6 +1543,9 b' def format_byte_size_binary(file_size):'
1541 1543 """
1542 1544 Formats file/folder sizes to standard.
1543 1545 """
1546 if file_size is None:
1547 file_size = 0
1548
1544 1549 formatted_size = format_byte_size(file_size, binary=True)
1545 1550 return formatted_size
1546 1551
@@ -1740,8 +1745,9 b' def render_binary(repo_name, file_obj):'
1740 1745 for ext in ['*.png', '*.jpg', '*.ico', '*.gif']:
1741 1746 if fnmatch.fnmatch(filename, pat=ext):
1742 1747 alt = filename
1743 src = url('files_raw_home', repo_name=repo_name,
1744 revision=file_obj.commit.raw_id, f_path=file_obj.path)
1748 src = route_path(
1749 'repo_file_raw', repo_name=repo_name,
1750 commit_id=file_obj.commit.raw_id, f_path=file_obj.path)
1745 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 71 Mousetrap.bind(['g F'], function(e) {
72 72 window.location = pyroutes.url(
73 'files_home',
73 'repo_files',
74 74 {
75 75 'repo_name': repoName,
76 'revision': repoLandingRev,
76 'commit_id': repoLandingRev,
77 77 'f_path': '',
78 78 'search': '1'
79 79 });
80 80 });
81 81 Mousetrap.bind(['g f'], function(e) {
82 82 window.location = pyroutes.url(
83 'files_home',
83 'repo_files',
84 84 {
85 85 'repo_name': repoName,
86 'revision': repoLandingRev,
86 'commit_id': repoLandingRev,
87 87 'f_path': ''
88 88 });
89 89 });
@@ -33,14 +33,6 b' function registerRCRoutes() {'
33 33 pyroutes.register('changelog_home', '/%(repo_name)s/changelog', ['repo_name']);
34 34 pyroutes.register('changelog_file_home', '/%(repo_name)s/changelog/%(revision)s/%(f_path)s', ['repo_name', 'revision', 'f_path']);
35 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 36 pyroutes.register('favicon', '/favicon.ico', []);
45 37 pyroutes.register('robots', '/robots.txt', []);
46 38 pyroutes.register('auth_home', '/_admin/auth*traverse', []);
@@ -106,6 +98,29 b' function registerRCRoutes() {'
106 98 pyroutes.register('repo_summary_explicit', '/%(repo_name)s/summary', ['repo_name']);
107 99 pyroutes.register('repo_summary_commits', '/%(repo_name)s/summary-commits', ['repo_name']);
108 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 124 pyroutes.register('repo_refs_data', '/%(repo_name)s/refs-data', ['repo_name']);
110 125 pyroutes.register('repo_refs_changelog_data', '/%(repo_name)s/refs-data-changelog', ['repo_name']);
111 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 79 var select2FileHistorySwitcher = function(targetElement, initialData, state) {
80 var loadUrl = pyroutes.url('files_history_home',
81 {'repo_name': templateContext.repo_name, 'revision': state.rev,
80 var loadUrl = pyroutes.url('repo_file_history',
81 {'repo_name': templateContext.repo_name, 'commit_id': state.rev,
82 82 'f_path': state.f_path});
83 83 select2RefBaseSwitcher(targetElement, loadUrl, initialData);
84 84 };
@@ -227,7 +227,7 b''
227 227 <ul id="context-pages" class="horizontal-list navigation">
228 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 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 231 <li class="${is_active('compare')}">
232 232 <a class="menulink" href="${h.url('compare_home',repo_name=c.repo_name)}"><div class="menulabel">${_('Compare')}</div></a>
233 233 </li>
@@ -28,6 +28,6 b''
28 28
29 29 <%def name="commit(message, commit_id, commit_idx)">
30 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 32 </div>
33 33 </%def>
@@ -28,6 +28,6 b''
28 28
29 29 <%def name="commit(message, commit_id, commit_idx)">
30 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 32 </div>
33 33 </%def>
@@ -109,7 +109,7 b''
109 109 %if h.is_hg(c.rhodecode_repo):
110 110 %for book in commit.bookmarks:
111 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 113 </span>
114 114 %endfor
115 115 %endif
@@ -117,7 +117,7 b''
117 117 ## tags
118 118 %for tag in commit.tags:
119 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 121 </span>
122 122 %endfor
123 123
@@ -29,7 +29,7 b''
29 29 </code>
30 30 </td>
31 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 33 ${_('Show File')}
34 34 </a>
35 35 </td>
@@ -110,20 +110,20 b''
110 110 %if h.is_hg(c.rhodecode_repo):
111 111 %for book in c.commit.bookmarks:
112 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 114 </span>
115 115 %endfor
116 116 %endif
117 117
118 118 %for tag in c.commit.tags:
119 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 121 </span>
122 122 %endfor
123 123
124 124 %if c.commit.branch:
125 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 127 </span>
128 128 %endif
129 129 </div>
@@ -339,7 +339,7 b''
339 339
340 340 // browse tree @ revision
341 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 343 e.preventDefault();
344 344 });
345 345
@@ -114,7 +114,7 b' collapse_all = len(diffset.files) > coll'
114 114 <div class="diffset-heading ${diffset.limited_diff and 'diffset-heading-warning' or ''}">
115 115 %if commit:
116 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 118 ${_('Browse Files')}
119 119 </a>
120 120 </div>
@@ -410,7 +410,7 b' from rhodecode.lib.diffs import NEW_FILE'
410 410 %if filediff.operation in ['D', 'M']:
411 411 <a
412 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 414 title="${h.tooltip(_('Show file at commit: %(commit_id)s') % {'commit_id': filediff.diffset.source_ref[:12]})}"
415 415 >
416 416 ${_('Show file before')}
@@ -426,7 +426,7 b' from rhodecode.lib.diffs import NEW_FILE'
426 426 %if filediff.operation in ['A', 'M']:
427 427 <a
428 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 430 title="${h.tooltip(_('Show file at commit: %(commit_id)s') % {'commit_id': filediff.diffset.target_ref[:12]})}"
431 431 >
432 432 ${_('Show file after')}
@@ -442,14 +442,14 b' from rhodecode.lib.diffs import NEW_FILE'
442 442 <a
443 443 class="tooltip"
444 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 447 ${_('Raw diff')}
448 448 </a> |
449 449 <a
450 450 class="tooltip"
451 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 454 ${_('Download diff')}
455 455 </a>
@@ -22,7 +22,7 b''
22 22 <div class="cb-annotate-message truncate-wrap">${h.chop_at_smart(annotation.message, '\n', suffix_if_chopped='...')}</div>
23 23 </td>
24 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 26 <i class="icon-left"></i>
27 27 </a>
28 28 </td>
@@ -76,13 +76,14 b''
76 76 var AnnotationController = function() {
77 77 var self = this;
78 78
79 this.previousAnnotation = function(commitId, fPath) {
79 this.previousAnnotation = function(commitId, fPath, lineNo) {
80 80 var params = {
81 81 'repo_name': templateContext.repo_name,
82 'revision': commitId,
83 'f_path': fPath
82 'commit_id': commitId,
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 87 return false;
87 88 };
88 89 };
@@ -19,7 +19,7 b''
19 19 </a>
20 20 </li>
21 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 23 <span>${_('Files')}</span>
24 24 </a>
25 25 </li>
@@ -8,20 +8,20 b''
8 8 %if h.is_hg(c.rhodecode_repo):
9 9 %for book in commit.bookmarks:
10 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 12 </span>
13 13 %endfor
14 14 %endif
15 15
16 16 %for tag in commit.tags:
17 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 19 </span>
20 20 %endfor
21 21
22 22 %if commit.branch:
23 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 25 </span>
26 26 %endif
27 27
@@ -5,7 +5,7 b''
5 5 % if c.file_author:
6 6 ${_('Last Author')}
7 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 9 % endif
10 10 </h4>
11 11 <a href="#" id="show_authors" class="action_link">${_('Show All')}</a>
@@ -13,17 +13,29 b''
13 13
14 14 % if c.authors:
15 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 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 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 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 39 </li>
28 40 % endfor
29 41 </ul>
@@ -43,8 +43,8 b''
43 43
44 44 var getState = function(context) {
45 45 var url = $(location).attr('href');
46 var _base_url = '${h.url("files_home",repo_name=c.repo_name,revision='',f_path='')}';
47 var _annotate_url = '${h.url("files_annotate_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.route_path("repo_files:annotated",repo_name=c.repo_name,commit_id='',f_path='')}';
48 48 _base_url = _base_url.replace('//', '/');
49 49 _annotate_url = _annotate_url.replace('//', '/');
50 50
@@ -67,12 +67,12 b''
67 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 71 {repo_name: templateContext.repo_name,
72 revision: rev, f_path: f_path});
73 var _url_base = pyroutes.url('files_home',
72 commit_id: rev, f_path: f_path});
73 var _url_base = pyroutes.url('repo_files',
74 74 {repo_name: templateContext.repo_name,
75 revision: rev, f_path:'__FPATH__'});
75 commit_id: rev, f_path:'__FPATH__'});
76 76 return {
77 77 url: url,
78 78 f_path: f_path,
@@ -105,7 +105,7 b''
105 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 110 metadataRequest = $.ajax({url: url});
111 111
@@ -182,13 +182,13 b''
182 182
183 183 var annotate = $('#annotate').val();
184 184 if (annotate === "True") {
185 var url = pyroutes.url('files_annotate_home',
185 var url = pyroutes.url('repo_files:annotated',
186 186 {'repo_name': templateContext.repo_name,
187 'revision': diff1, 'f_path': state.f_path});
187 'commit_id': diff1, 'f_path': state.f_path});
188 188 } else {
189 var url = pyroutes.url('files_home',
189 var url = pyroutes.url('repo_files',
190 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 193 window.location = url;
194 194
@@ -197,9 +197,9 b''
197 197 // show more authors
198 198 $('#show_authors').on('click', function(e) {
199 199 e.preventDefault();
200 var url = pyroutes.url('files_authors_home',
200 var url = pyroutes.url('repo_file_authors',
201 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 204 $.pjax({
205 205 url: url,
@@ -264,9 +264,9 b''
264 264 var rev = $('#at_rev').val();
265 265 // explicit reload page here. with pjax entering bad input
266 266 // produces not so nice results
267 window.location = pyroutes.url('files_home',
267 window.location = pyroutes.url('repo_files',
268 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 27 <div class="edit-file-title">
28 28 ${self.breadcrumbs()}
29 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 31 <div class="edit-file-fieldset">
32 32 <div class="fieldset">
33 33 <div id="destination-label" class="left-label">
@@ -169,7 +169,7 b''
169 169 hide_upload();
170 170
171 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 173 var myCodeMirror = initCodeMirror('editor', reset_url, false);
174 174
175 175 var modes_select = $('#set_mode');
@@ -23,7 +23,7 b''
23 23 </div>
24 24 % if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name):
25 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 27 ${_('Add File')}</a>
28 28 </div>
29 29 % endif
@@ -43,7 +43,7 b''
43 43 </div>
44 44 ## file tree is computed from caches, and filled in
45 45 <div id="file-tree">
46 ${c.file_tree}
46 ${c.file_tree |n}
47 47 </div>
48 48
49 49 </div>
@@ -14,7 +14,7 b''
14 14 %if c.file.parent:
15 15 <tr class="parity0">
16 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 18 <i class="icon-folder"></i>..
19 19 </a>
20 20 </td>
@@ -38,7 +38,7 b''
38 38 % endif
39 39 </span>
40 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 42 <i class="${'icon-file browser-file' if node.is_file() else 'icon-folder browser-dir'}"></i>${node.name}
43 43 </a>
44 44 % endif
@@ -27,7 +27,7 b''
27 27 <div class="edit-file-title">
28 28 ${self.breadcrumbs()}
29 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 31 <div class="edit-file-fieldset">
32 32 <div class="fieldset">
33 33 <div id="destination-label" class="left-label">
@@ -44,11 +44,11 b''
44 44 %if c.file.is_binary:
45 45 ${_('Binary file (%s)') % c.file.mimetype}
46 46 %else:
47 %if c.file.size < c.cut_off_limit:
47 %if c.file.size < c.visual.cut_off_limit_file:
48 48 ${h.pygmentize(c.file,linenos=True,anchorlinenos=False,cssclass="code-highlight")}
49 49 %else:
50 ${_('File is too big to display')} ${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))}
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.route_path('repo_file_raw',repo_name=c.repo_name,commit_id=c.commit.raw_id,f_path=c.f_path))}
52 52 %endif
53 53 %endif
54 54 </div>
@@ -42,7 +42,7 b''
42 42 </div>
43 43
44 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 46 <div id="codeblock" class="codeblock" >
47 47 <div class="code-header">
48 48 <div class="stats">
@@ -58,12 +58,15 b''
58 58 % if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name):
59 59 % if not c.file.is_binary:
60 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 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 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")}
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)}">
65
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 70 <i class="icon-archive"></i> ${_('download')}
68 71 </a>
69 72 % endif
@@ -112,7 +115,7 b''
112 115 <script type="text/javascript">
113 116 $(document).ready(function(){
114 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 119 var myCodeMirror = initCodeMirror('editor', reset_url);
117 120
118 121 var modes_select = $('#set_mode');
@@ -7,7 +7,7 b''
7 7 % if c.lf_node:
8 8 <span title="${_('This file is a pointer to large binary file')}"> | ${_('LargeFile')} ${h.format_byte_size_binary(c.lf_node.size)} </span>
9 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 11 <span> | ${h.format_byte_size_binary(c.file.size)}</span>
12 12 <span> | ${c.file.mimetype} </span>
13 13 <span class="item last"> | ${h.get_lexer_for_filenode(c.file).__class__.__name__}</span>
@@ -20,18 +20,18 b''
20 20 ${_('Show Full History')}
21 21 </a> |
22 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 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 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 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 31 ${_('Download largefile')}
32 32 </a>
33 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 35 ${_('Download')}
36 36 </a>
37 37 % endif
@@ -39,14 +39,14 b''
39 39 %if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name):
40 40 |
41 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')}">
43 ${_('Edit on Branch:%s') % c.branch_name}
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:{}').format(c.branch_name)}
44 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 46 </a>
47 47 %elif c.on_branch_head and c.branch_or_raw_id and c.file.is_binary:
48 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 50 %else:
51 51 ${h.link_to(_('Edit'), '#', class_="btn btn-link disabled tooltip", title=_('Editing files allowed only when on branch head commit'))}
52 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 66 </div>
67 67 % endif
68 68 %else:
69 % if c.file.size < c.cut_off_limit:
69 % if c.file.size < c.visual.cut_off_limit_file:
70 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 72 %else:
73 73 <table class="cb codehilite">
74 74 %if c.annotate:
@@ -84,8 +84,8 b''
84 84 </table>
85 85 %endif
86 86 %else:
87 ${_('File is too big to display')} ${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))}
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.route_path('repo_file_raw',repo_name=c.repo_name,commit_id=c.commit.raw_id,f_path=c.f_path))}
89 89 %endif
90 90 %endif
91 91 </div>
@@ -28,6 +28,7 b' for line_number in matching_lines:'
28 28 query_terms=terms,
29 29 only_line_numbers=lines_of_interest
30 30 ))|n}
31
31 32 %if len(matching_lines) > shown_matching_lines:
32 33 <a href="${url}">
33 34 ${len(matching_lines) - shown_matching_lines} ${_('more matches in this file')}
@@ -52,7 +53,7 b' for line_number in matching_lines:'
52 53 ${h.link_to(entry['repository'], h.route_path('repo_summary',repo_name=entry['repository']))}
53 54 </h2>
54 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 57 %if entry.get('lines'):
57 58 | ${entry.get('lines', 0.)} ${_ungettext('line', 'lines', entry.get('lines', 0.))}
58 59 %endif
@@ -66,17 +67,15 b' for line_number in matching_lines:'
66 67 <div class="buttons">
67 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 69 ${_('Show Full History')}
69 </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(_('Raw'), h.url('files_raw_home', repo_name=entry.get('repository',''),revision=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 ${_('Download')}
74 70 </a>
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','')))}
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','')))}
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','')))}
75 74 </div>
76 75 </div>
77 76 <div class="code-body search-code-body">
78 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 79 mimetype=entry.get('mimetype'), filepath=entry.get('path'))}
81 80 </div>
82 81 </div>
@@ -20,7 +20,7 b''
20 20 </td>
21 21 <td class="td-componentname">
22 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 24 </td>
25 25 </tr>
26 26 % endif
@@ -173,7 +173,7 b''
173 173 % endif
174 174 % else:
175 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 177 <i class="icon-archive"></i> tip.zip
178 178 ## replaced by some JS on select
179 179 </a>
@@ -46,7 +46,7 b''
46 46 <div class="box" >
47 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 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 50 </h3>
51 51 </div>
52 52 <div class="readme codeblock">
@@ -96,7 +96,7 b''
96 96 // format of Object {text: "v0.0.3", type: "tag", id: "rev"}
97 97 var selected_cs = e.added;
98 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 100 // set new label
101 101 $('#archive_link').html('<i class="icon-archive"></i> '+ e.added.text+".zip");
102 102
@@ -60,14 +60,14 b''
60 60 %if h.is_hg(c.rhodecode_repo):
61 61 %for book in cs.bookmarks:
62 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 64 </span>
65 65 %endfor
66 66 %endif
67 67 ## tags
68 68 %for tag in cs.tags:
69 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 71 </span>
72 72 %endfor
73 73
@@ -100,7 +100,7 b''
100 100 <div class="left-label">${_('Add or upload files directly via RhodeCode:')}</div>
101 101 <div class="right-content">
102 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 104 </div>
105 105 </div>
106 106 %endif
@@ -24,6 +24,6 b''
24 24
25 25 <%def name="commit(message, commit_id, commit_idx)">
26 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 28 </div>
29 29 </%def>
@@ -18,23 +18,10 b''
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 import os
22
23 import mock
24 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 23 from rhodecode.lib.vcs import nodes
31 from rhodecode.lib.vcs.backends.base import EmptyCommit
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)
24 from rhodecode.tests import url
38 25 from rhodecode.tests.fixture import Fixture
39 26 from rhodecode.tests.utils import commit_change
40 27
@@ -59,7 +59,7 b' def test_urlify_text(url, expected_url):'
59 59 '</a>/bX&#34;X'),
60 60 ], ids=['simple', 'one_segment', 'empty_path', 'simple_quote'])
61 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 63 result = helpers.files_breadcrumbs(repo_name, commit_id, path)
64 64 # Expect it to encode all path fragments properly. This is important
65 65 # because it returns an instance of `literal`.
@@ -409,7 +409,6 b' def commit_change('
409 409 def add_test_routes(config):
410 410 """
411 411 Adds test routing that can be used in different functional tests
412
413 412 """
414 413 config.add_route(name='home', pattern='/')
415 414 config.add_route(name='repo_summary', pattern='/{repo_name}')
@@ -422,3 +421,6 b' def add_test_routes(config):'
422 421 pattern='/pull-request/{pull_request_id}')
423 422 config.add_route(name='repo_commit',
424 423 pattern='/{repo_name}/changeset/{commit_id}')
424
425 config.add_route(name='repo_files',
426 pattern='/{repo_name}/files/{commit_id}/{f_path}')
1 NO CONTENT: file was removed
This diff has been collapsed as it changes many lines, (1109 lines changed) Show them Hide them
General Comments 0
You need to be logged in to leave comments. Login now