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