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