diff --git a/rhodecode/controllers/files.py b/rhodecode/controllers/files.py
--- a/rhodecode/controllers/files.py
+++ b/rhodecode/controllers/files.py
@@ -22,40 +22,42 @@
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
+
import os
-import tempfile
import logging
-import rhodecode.lib.helpers as h
+import traceback
-from mercurial import archival
+from os.path import join as jn
from pylons import request, response, session, tmpl_context as c, url
from pylons.i18n.translation import _
from pylons.controllers.util import redirect
+from pylons.decorators import jsonify
+from vcs.conf import settings
+from vcs.exceptions import RepositoryError, ChangesetDoesNotExistError, \
+ EmptyRepositoryError, ImproperArchiveTypeError, VCSError, NodeAlreadyExistsError
+from vcs.nodes import FileNode, NodeKind
+from vcs.utils import diffs as differ
+
+from rhodecode.lib import convert_line_endings, detect_mode, safe_str
from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
-from rhodecode.lib.base import BaseController, render
+from rhodecode.lib.base import BaseRepoController, render
from rhodecode.lib.utils import EmptyChangeset
-from rhodecode.model.scm import ScmModel
-
-from vcs.exceptions import RepositoryError, ChangesetError, \
- ChangesetDoesNotExistError, EmptyRepositoryError
-from vcs.nodes import FileNode
-from vcs.utils import diffs as differ
+import rhodecode.lib.helpers as h
+from rhodecode.model.repo import RepoModel
log = logging.getLogger(__name__)
-class FilesController(BaseController):
+class FilesController(BaseRepoController):
@LoginRequired()
- @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
- 'repository.admin')
def __before__(self):
super(FilesController, self).__before__()
c.cut_off_limit = self.cut_off_limit
- def __get_cs_or_redirect(self, rev, repo_name):
+ def __get_cs_or_redirect(self, rev, repo_name, redirect_after=True):
"""
Safe way to get changeset if error occur it redirects to tip with
proper message
@@ -64,196 +66,412 @@ class FilesController(BaseController):
:param repo_name: repo name to redirect after
"""
- _repo = ScmModel().get_repo(c.repo_name)
try:
- return _repo.get_changeset(rev)
+ return c.rhodecode_repo.get_changeset(rev)
except EmptyRepositoryError, e:
- h.flash(_('There are no files yet'), category='warning')
+ if not redirect_after:
+ return None
+ url_ = url('files_add_home',
+ repo_name=c.repo_name,
+ revision=0, f_path='')
+ add_new = '[%s]' % (url_, _('add new'))
+ h.flash(h.literal(_('There are no files yet %s' % add_new)),
+ category='warning')
redirect(h.url('summary_home', repo_name=repo_name))
except RepositoryError, e:
h.flash(str(e), category='warning')
redirect(h.url('files_home', repo_name=repo_name, revision='tip'))
- def index(self, repo_name, revision, f_path):
- cs = self.__get_cs_or_redirect(revision, repo_name)
- c.repo = ScmModel().get_repo(c.repo_name)
-
- revision = request.POST.get('at_rev', None) or revision
-
- def get_next_rev(cur):
- max_rev = len(c.repo.revisions) - 1
- r = cur + 1
- if r > max_rev:
- r = max_rev
- return r
+ def __get_filenode_or_redirect(self, repo_name, cs, path):
+ """
+ Returns file_node, if error occurs or given path is directory,
+ it'll redirect to top level path
- def get_prev_rev(cur):
- r = cur - 1
- return r
-
- c.f_path = f_path
- c.changeset = cs
- cur_rev = c.changeset.revision
- prev_rev = c.repo.get_changeset(get_prev_rev(cur_rev)).raw_id
- next_rev = c.repo.get_changeset(get_next_rev(cur_rev)).raw_id
-
- c.url_prev = url('files_home', repo_name=c.repo_name,
- revision=prev_rev, f_path=f_path)
- c.url_next = url('files_home', repo_name=c.repo_name,
- revision=next_rev, f_path=f_path)
+ :param repo_name: repo_name
+ :param cs: given changeset
+ :param path: path to lookup
+ """
try:
- c.files_list = c.changeset.get_node(f_path)
- c.file_history = self._get_history(c.repo, c.files_list, f_path)
- except RepositoryError, e:
- h.flash(str(e), category='warning')
- redirect(h.url('files_home', repo_name=repo_name,
- revision=revision))
-
-
- return render('files/files.html')
-
- def rawfile(self, repo_name, revision, f_path):
- cs = self.__get_cs_or_redirect(revision, repo_name)
- try:
- file_node = cs.get_node(f_path)
- except RepositoryError, e:
- h.flash(str(e), category='warning')
- redirect(h.url('files_home', repo_name=repo_name,
- revision=cs.raw_id))
-
- fname = f_path.split(os.sep)[-1].encode('utf8', 'replace')
-
- response.content_disposition = 'attachment; filename=%s' % fname
- response.content_type = file_node.mimetype
- return file_node.content
-
- def raw(self, repo_name, revision, f_path):
- cs = self.__get_cs_or_redirect(revision, repo_name)
- try:
- file_node = cs.get_node(f_path)
+ file_node = cs.get_node(path)
+ if file_node.is_dir():
+ raise RepositoryError('given path is a directory')
except RepositoryError, e:
h.flash(str(e), category='warning')
redirect(h.url('files_home', repo_name=repo_name,
revision=cs.raw_id))
- response.content_type = 'text/plain'
- return file_node.content
+ return file_node
+
- def annotate(self, repo_name, revision, f_path):
- cs = self.__get_cs_or_redirect(revision, repo_name)
+ def __get_paths(self, changeset, starting_path):
+ """recursive walk in root dir and return a set of all path in that dir
+ based on repository walk function
+ """
+ _files = list()
+ _dirs = list()
+
try:
- c.file = cs.get_node(f_path)
+ tip = changeset
+ for topnode, dirs, files in tip.walk(starting_path):
+ for f in files:
+ _files.append(f.path)
+ for d in dirs:
+ _dirs.append(d.path)
except RepositoryError, e:
- h.flash(str(e), category='warning')
- redirect(h.url('files_home', repo_name=repo_name, revision=cs.raw_id))
+ log.debug(traceback.format_exc())
+ pass
+ return _dirs, _files
- c.file_history = self._get_history(ScmModel().get_repo(c.repo_name), c.file, f_path)
- c.cs = cs
+ @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
+ 'repository.admin')
+ def index(self, repo_name, revision, f_path):
+ #reditect to given revision from form if given
+ post_revision = request.POST.get('at_rev', None)
+ if post_revision:
+ cs = self.__get_cs_or_redirect(post_revision, repo_name)
+ redirect(url('files_home', repo_name=c.repo_name,
+ revision=cs.raw_id, f_path=f_path))
+
+ c.changeset = self.__get_cs_or_redirect(revision, repo_name)
+ c.branch = request.GET.get('branch', None)
c.f_path = f_path
+ cur_rev = c.changeset.revision
+
+ #prev link
+ try:
+ prev_rev = c.rhodecode_repo.get_changeset(cur_rev).prev(c.branch)
+ c.url_prev = url('files_home', repo_name=c.repo_name,
+ revision=prev_rev.raw_id, f_path=f_path)
+ if c.branch:
+ c.url_prev += '?branch=%s' % c.branch
+ except (ChangesetDoesNotExistError, VCSError):
+ c.url_prev = '#'
+
+ #next link
+ try:
+ next_rev = c.rhodecode_repo.get_changeset(cur_rev).next(c.branch)
+ c.url_next = url('files_home', repo_name=c.repo_name,
+ revision=next_rev.raw_id, f_path=f_path)
+ if c.branch:
+ c.url_next += '?branch=%s' % c.branch
+ except (ChangesetDoesNotExistError, VCSError):
+ c.url_next = '#'
+
+ #files or dirs
+ try:
+ c.files_list = c.changeset.get_node(f_path)
+
+ if c.files_list.is_file():
+ c.file_history = self._get_node_history(c.changeset, f_path)
+ else:
+ c.file_history = []
+ except RepositoryError, e:
+ h.flash(str(e), category='warning')
+ redirect(h.url('files_home', repo_name=repo_name,
+ revision=revision))
+
+ return render('files/files.html')
+
+ @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
+ 'repository.admin')
+ def rawfile(self, repo_name, revision, f_path):
+ cs = self.__get_cs_or_redirect(revision, repo_name)
+ file_node = self.__get_filenode_or_redirect(repo_name, cs, f_path)
+
+ response.content_disposition = 'attachment; filename=%s' % \
+ safe_str(f_path.split(os.sep)[-1])
+
+ response.content_type = file_node.mimetype
+ return file_node.content
+
+ @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
+ 'repository.admin')
+ def raw(self, repo_name, revision, f_path):
+ cs = self.__get_cs_or_redirect(revision, repo_name)
+ file_node = self.__get_filenode_or_redirect(repo_name, cs, f_path)
+
+ raw_mimetype_mapping = {
+ # map original mimetype to a mimetype used for "show as raw"
+ # you can also provide a content-disposition to override the
+ # default "attachment" disposition.
+ # orig_type: (new_type, new_dispo)
+
+ # show images inline:
+ 'image/x-icon': ('image/x-icon', 'inline'),
+ 'image/png': ('image/png', 'inline'),
+ 'image/gif': ('image/gif', 'inline'),
+ 'image/jpeg': ('image/jpeg', 'inline'),
+ 'image/svg+xml': ('image/svg+xml', 'inline'),
+ }
+
+ mimetype = file_node.mimetype
+ try:
+ mimetype, dispo = raw_mimetype_mapping[mimetype]
+ except KeyError:
+ # we don't know anything special about this, handle it safely
+ if file_node.is_binary:
+ # do same as download raw for binary files
+ mimetype, dispo = 'application/octet-stream', 'attachment'
+ else:
+ # do not just use the original mimetype, but force text/plain,
+ # otherwise it would serve text/html and that might be unsafe.
+ # Note: underlying vcs library fakes text/plain mimetype if the
+ # mimetype can not be determined and it thinks it is not
+ # binary.This might lead to erroneous text display in some
+ # cases, but helps in other cases, like with text files
+ # without extension.
+ mimetype, dispo = 'text/plain', 'inline'
+
+ if dispo == 'attachment':
+ dispo = 'attachment; filename=%s' % \
+ safe_str(f_path.split(os.sep)[-1])
+
+ response.content_disposition = dispo
+ response.content_type = mimetype
+ return file_node.content
+
+ @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
+ 'repository.admin')
+ def annotate(self, repo_name, revision, f_path):
+ c.cs = self.__get_cs_or_redirect(revision, repo_name)
+ c.file = self.__get_filenode_or_redirect(repo_name, c.cs, f_path)
+
+ c.file_history = self._get_node_history(c.cs, f_path)
+ c.f_path = f_path
return render('files/files_annotate.html')
- def archivefile(self, repo_name, revision, fileformat):
- archive_specs = {
- '.tar.bz2': ('application/x-bzip2', 'tbz2'),
- '.tar.gz': ('application/x-gzip', 'tgz'),
- '.zip': ('application/zip', 'zip'),
- }
- if not archive_specs.has_key(fileformat):
- return 'Unknown archive type %s' % fileformat
+ @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
+ def edit(self, repo_name, revision, f_path):
+ r_post = request.POST
+
+ c.cs = self.__get_cs_or_redirect(revision, repo_name)
+ c.file = self.__get_filenode_or_redirect(repo_name, c.cs, f_path)
+
+ if c.file.is_binary:
+ return redirect(url('files_home', repo_name=c.repo_name,
+ revision=c.cs.raw_id, f_path=f_path))
+
+ c.f_path = f_path
+
+ if r_post:
+
+ old_content = c.file.content
+ sl = old_content.splitlines(1)
+ first_line = sl[0] if sl else ''
+ # modes: 0 - Unix, 1 - Mac, 2 - DOS
+ mode = detect_mode(first_line, 0)
+ content = convert_line_endings(r_post.get('content'), mode)
+
+ message = r_post.get('message') or (_('Edited %s via RhodeCode')
+ % (f_path))
+ author = self.rhodecode_user.full_contact
+
+ if content == old_content:
+ h.flash(_('No changes'),
+ category='warning')
+ return redirect(url('changeset_home', repo_name=c.repo_name,
+ revision='tip'))
+
+ try:
+ self.scm_model.commit_change(repo=c.rhodecode_repo,
+ repo_name=repo_name, cs=c.cs,
+ user=self.rhodecode_user,
+ author=author, message=message,
+ content=content, f_path=f_path)
+ h.flash(_('Successfully committed to %s' % f_path),
+ category='success')
+
+ except Exception:
+ log.error(traceback.format_exc())
+ h.flash(_('Error occurred during commit'), category='error')
+ return redirect(url('changeset_home',
+ repo_name=c.repo_name, revision='tip'))
+
+ return render('files/files_edit.html')
+
+ @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
+ def add(self, repo_name, revision, f_path):
+ r_post = request.POST
+ c.cs = self.__get_cs_or_redirect(revision, repo_name,
+ redirect_after=False)
+ if c.cs is None:
+ c.cs = EmptyChangeset(alias=c.rhodecode_repo.alias)
+
+ c.f_path = f_path
+
+ if r_post:
+ unix_mode = 0
+ content = convert_line_endings(r_post.get('content'), unix_mode)
+
+ message = r_post.get('message') or (_('Added %s via RhodeCode')
+ % (f_path))
+ location = r_post.get('location')
+ filename = r_post.get('filename')
+ file_obj = r_post.get('upload_file', None)
+
+ if file_obj is not None and hasattr(file_obj, 'filename'):
+ filename = file_obj.filename
+ content = file_obj.file
- def read_in_chunks(file_object, chunk_size=1024 * 40):
- """Lazy function (generator) to read a file piece by piece.
- Default chunk size: 40k."""
+ node_path = os.path.join(location, filename)
+ author = self.rhodecode_user.full_contact
+
+ if not content:
+ h.flash(_('No content'), category='warning')
+ return redirect(url('changeset_home', repo_name=c.repo_name,
+ revision='tip'))
+ if not filename:
+ h.flash(_('No filename'), category='warning')
+ return redirect(url('changeset_home', repo_name=c.repo_name,
+ revision='tip'))
+
+ try:
+ self.scm_model.create_node(repo=c.rhodecode_repo,
+ repo_name=repo_name, cs=c.cs,
+ user=self.rhodecode_user,
+ author=author, message=message,
+ content=content, f_path=node_path)
+ h.flash(_('Successfully committed to %s' % node_path),
+ category='success')
+ except NodeAlreadyExistsError, e:
+ h.flash(_(e), category='error')
+ except Exception:
+ log.error(traceback.format_exc())
+ h.flash(_('Error occurred during commit'), category='error')
+ return redirect(url('changeset_home',
+ repo_name=c.repo_name, revision='tip'))
+
+ return render('files/files_add.html')
+
+ @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
+ 'repository.admin')
+ def archivefile(self, repo_name, fname):
+
+ fileformat = None
+ revision = None
+ ext = None
+ subrepos = request.GET.get('subrepos') == 'true'
+
+ for a_type, ext_data in settings.ARCHIVE_SPECS.items():
+ archive_spec = fname.split(ext_data[1])
+ if len(archive_spec) == 2 and archive_spec[1] == '':
+ fileformat = a_type or ext_data[1]
+ revision = archive_spec[0]
+ ext = ext_data[1]
+
+ try:
+ dbrepo = RepoModel().get_by_repo_name(repo_name)
+ if dbrepo.enable_downloads is False:
+ return _('downloads disabled')
+
+ cs = c.rhodecode_repo.get_changeset(revision)
+ content_type = settings.ARCHIVE_SPECS[fileformat][0]
+ except ChangesetDoesNotExistError:
+ return _('Unknown revision %s') % revision
+ except EmptyRepositoryError:
+ return _('Empty repository')
+ except (ImproperArchiveTypeError, KeyError):
+ return _('Unknown archive type')
+
+ response.content_type = content_type
+ response.content_disposition = 'attachment; filename=%s-%s%s' \
+ % (repo_name, revision, ext)
+
+ import tempfile
+ archive = tempfile.mkstemp()[1]
+ t = open(archive, 'wb')
+ cs.fill_archive(stream=t, kind=fileformat, subrepos=subrepos)
+
+ def get_chunked_archive(archive):
+ stream = open(archive, 'rb')
while True:
- data = file_object.read(chunk_size)
+ data = stream.read(4096)
if not data:
+ os.remove(archive)
break
yield data
- archive = tempfile.TemporaryFile()
- repo = ScmModel().get_repo(repo_name).repo
- fname = '%s-%s%s' % (repo_name, revision, fileformat)
- archival.archive(repo, archive, revision, archive_specs[fileformat][1],
- prefix='%s-%s' % (repo_name, revision))
- response.content_type = archive_specs[fileformat][0]
- response.content_disposition = 'attachment; filename=%s' % fname
- archive.seek(0)
- return read_in_chunks(archive)
+ return get_chunked_archive(archive)
+ @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
+ 'repository.admin')
def diff(self, repo_name, f_path):
- hg_model = ScmModel()
diff1 = request.GET.get('diff1')
diff2 = request.GET.get('diff2')
c.action = request.GET.get('diff')
c.no_changes = diff1 == diff2
c.f_path = f_path
- c.repo = hg_model.get_repo(c.repo_name)
+ c.big_diff = False
try:
if diff1 not in ['', None, 'None', '0' * 12, '0' * 40]:
- c.changeset_1 = c.repo.get_changeset(diff1)
+ c.changeset_1 = c.rhodecode_repo.get_changeset(diff1)
node1 = c.changeset_1.get_node(f_path)
else:
- c.changeset_1 = EmptyChangeset()
+ c.changeset_1 = EmptyChangeset(repo=c.rhodecode_repo)
node1 = FileNode('.', '', changeset=c.changeset_1)
if diff2 not in ['', None, 'None', '0' * 12, '0' * 40]:
- c.changeset_2 = c.repo.get_changeset(diff2)
+ c.changeset_2 = c.rhodecode_repo.get_changeset(diff2)
node2 = c.changeset_2.get_node(f_path)
else:
- c.changeset_2 = EmptyChangeset()
+ c.changeset_2 = EmptyChangeset(repo=c.rhodecode_repo)
node2 = FileNode('.', '', changeset=c.changeset_2)
except RepositoryError:
return redirect(url('files_home',
repo_name=c.repo_name, f_path=f_path))
- f_udiff = differ.get_udiff(node1, node2)
- diff = differ.DiffProcessor(f_udiff)
+ if c.action == 'download':
+ diff = differ.DiffProcessor(differ.get_gitdiff(node1, node2),
+ format='gitdiff')
- if c.action == 'download':
diff_name = '%s_vs_%s.diff' % (diff1, diff2)
response.content_type = 'text/plain'
response.content_disposition = 'attachment; filename=%s' \
% diff_name
- if node1.is_binary or node2.is_binary:
- return _('binary file changed')
return diff.raw_diff()
elif c.action == 'raw':
+ diff = differ.DiffProcessor(differ.get_gitdiff(node1, node2),
+ format='gitdiff')
response.content_type = 'text/plain'
- if node1.is_binary or node2.is_binary:
- return _('binary file changed')
return diff.raw_diff()
elif c.action == 'diff':
- if node1.is_binary or node2.is_binary:
+ if node1.is_binary or node2.is_binary:
c.cur_diff = _('Binary file')
elif node1.size > self.cut_off_limit or \
node2.size > self.cut_off_limit:
- c.cur_diff = _('Diff is too big to display')
+ c.cur_diff = ''
+ c.big_diff = True
else:
+ diff = differ.DiffProcessor(differ.get_gitdiff(node1, node2),
+ format='gitdiff')
c.cur_diff = diff.as_html()
else:
+
#default option
- if node1.size > self.cut_off_limit or node2.size > self.cut_off_limit:
- c.cur_diff = _('Diff is to big to display')
- elif node1.is_binary or node2.is_binary:
+ if node1.is_binary or node2.is_binary:
c.cur_diff = _('Binary file')
+ elif node1.size > self.cut_off_limit or \
+ node2.size > self.cut_off_limit:
+ c.cur_diff = ''
+ c.big_diff = True
+
else:
+ diff = differ.DiffProcessor(differ.get_gitdiff(node1, node2),
+ format='gitdiff')
c.cur_diff = diff.as_html()
- if not c.cur_diff:
+ if not c.cur_diff and not c.big_diff:
c.no_changes = True
return render('files/file_diff.html')
- def _get_history(self, repo, node, f_path):
- from vcs.nodes import NodeKind
- if not node.kind is NodeKind.FILE:
- return []
- changesets = node.history
+ def _get_node_history(self, cs, f_path):
+ changesets = cs.get_file_history(f_path)
hist_l = []
changesets_group = ([], _("Changesets"))
@@ -266,14 +484,24 @@ class FilesController(BaseController):
hist_l.append(changesets_group)
- for name, chs in c.repository_branches.items():
+ for name, chs in c.rhodecode_repo.branches.items():
#chs = chs.split(':')[-1]
branches_group[0].append((chs, name),)
hist_l.append(branches_group)
- for name, chs in c.repository_tags.items():
+ for name, chs in c.rhodecode_repo.tags.items():
#chs = chs.split(':')[-1]
tags_group[0].append((chs, name),)
hist_l.append(tags_group)
return hist_l
+
+ @jsonify
+ @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
+ 'repository.admin')
+ def nodelist(self, repo_name, revision, f_path):
+ if request.environ.get('HTTP_X_PARTIAL_XHR'):
+ cs = self.__get_cs_or_redirect(revision, repo_name)
+ _d, _f = self.__get_paths(cs, f_path)
+ return _d + _f
+