##// END OF EJS Templates
files: protect against 500 errors on nodelist.
marcink -
r2152:8e19a416 default
parent child Browse files
Show More
@@ -1,1282 +1,1290 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(fname))
266
266
267 try:
267 try:
268 commit = self.rhodecode_vcs_repo.get_commit(commit_id)
268 commit = self.rhodecode_vcs_repo.get_commit(commit_id)
269 except CommitDoesNotExistError:
269 except CommitDoesNotExistError:
270 return Response(_('Unknown commit_id %s') % commit_id)
270 return Response(_('Unknown commit_id %s') % commit_id)
271 except EmptyRepositoryError:
271 except EmptyRepositoryError:
272 return Response(_('Empty repository'))
272 return Response(_('Empty repository'))
273
273
274 archive_name = '%s-%s%s%s' % (
274 archive_name = '%s-%s%s%s' % (
275 safe_str(self.db_repo_name.replace('/', '_')),
275 safe_str(self.db_repo_name.replace('/', '_')),
276 '-sub' if subrepos else '',
276 '-sub' if subrepos else '',
277 safe_str(commit.short_id), ext)
277 safe_str(commit.short_id), ext)
278
278
279 use_cached_archive = False
279 use_cached_archive = False
280 archive_cache_enabled = CONFIG.get(
280 archive_cache_enabled = CONFIG.get(
281 'archive_cache_dir') and not self.request.GET.get('no_cache')
281 'archive_cache_dir') and not self.request.GET.get('no_cache')
282
282
283 if archive_cache_enabled:
283 if archive_cache_enabled:
284 # check if we it's ok to write
284 # check if we it's ok to write
285 if not os.path.isdir(CONFIG['archive_cache_dir']):
285 if not os.path.isdir(CONFIG['archive_cache_dir']):
286 os.makedirs(CONFIG['archive_cache_dir'])
286 os.makedirs(CONFIG['archive_cache_dir'])
287 cached_archive_path = os.path.join(
287 cached_archive_path = os.path.join(
288 CONFIG['archive_cache_dir'], archive_name)
288 CONFIG['archive_cache_dir'], archive_name)
289 if os.path.isfile(cached_archive_path):
289 if os.path.isfile(cached_archive_path):
290 log.debug('Found cached archive in %s', cached_archive_path)
290 log.debug('Found cached archive in %s', cached_archive_path)
291 fd, archive = None, cached_archive_path
291 fd, archive = None, cached_archive_path
292 use_cached_archive = True
292 use_cached_archive = True
293 else:
293 else:
294 log.debug('Archive %s is not yet cached', archive_name)
294 log.debug('Archive %s is not yet cached', archive_name)
295
295
296 if not use_cached_archive:
296 if not use_cached_archive:
297 # generate new archive
297 # generate new archive
298 fd, archive = tempfile.mkstemp()
298 fd, archive = tempfile.mkstemp()
299 log.debug('Creating new temp archive in %s', archive)
299 log.debug('Creating new temp archive in %s', archive)
300 try:
300 try:
301 commit.archive_repo(archive, kind=fileformat, subrepos=subrepos)
301 commit.archive_repo(archive, kind=fileformat, subrepos=subrepos)
302 except ImproperArchiveTypeError:
302 except ImproperArchiveTypeError:
303 return _('Unknown archive type')
303 return _('Unknown archive type')
304 if archive_cache_enabled:
304 if archive_cache_enabled:
305 # if we generated the archive and we have cache enabled
305 # if we generated the archive and we have cache enabled
306 # let's use this for future
306 # let's use this for future
307 log.debug('Storing new archive in %s', cached_archive_path)
307 log.debug('Storing new archive in %s', cached_archive_path)
308 shutil.move(archive, cached_archive_path)
308 shutil.move(archive, cached_archive_path)
309 archive = cached_archive_path
309 archive = cached_archive_path
310
310
311 # store download action
311 # store download action
312 audit_logger.store_web(
312 audit_logger.store_web(
313 'repo.archive.download', action_data={
313 'repo.archive.download', action_data={
314 'user_agent': self.request.user_agent,
314 'user_agent': self.request.user_agent,
315 'archive_name': archive_name,
315 'archive_name': archive_name,
316 'archive_spec': fname,
316 'archive_spec': fname,
317 'archive_cached': use_cached_archive},
317 'archive_cached': use_cached_archive},
318 user=self._rhodecode_user,
318 user=self._rhodecode_user,
319 repo=self.db_repo,
319 repo=self.db_repo,
320 commit=True
320 commit=True
321 )
321 )
322
322
323 def get_chunked_archive(archive):
323 def get_chunked_archive(archive):
324 with open(archive, 'rb') as stream:
324 with open(archive, 'rb') as stream:
325 while True:
325 while True:
326 data = stream.read(16 * 1024)
326 data = stream.read(16 * 1024)
327 if not data:
327 if not data:
328 if fd: # fd means we used temporary file
328 if fd: # fd means we used temporary file
329 os.close(fd)
329 os.close(fd)
330 if not archive_cache_enabled:
330 if not archive_cache_enabled:
331 log.debug('Destroying temp archive %s', archive)
331 log.debug('Destroying temp archive %s', archive)
332 os.remove(archive)
332 os.remove(archive)
333 break
333 break
334 yield data
334 yield data
335
335
336 response = Response(app_iter=get_chunked_archive(archive))
336 response = Response(app_iter=get_chunked_archive(archive))
337 response.content_disposition = str(
337 response.content_disposition = str(
338 'attachment; filename=%s' % archive_name)
338 'attachment; filename=%s' % archive_name)
339 response.content_type = str(content_type)
339 response.content_type = str(content_type)
340
340
341 return response
341 return response
342
342
343 def _get_file_node(self, commit_id, f_path):
343 def _get_file_node(self, commit_id, f_path):
344 if commit_id not in ['', None, 'None', '0' * 12, '0' * 40]:
344 if commit_id not in ['', None, 'None', '0' * 12, '0' * 40]:
345 commit = self.rhodecode_vcs_repo.get_commit(commit_id=commit_id)
345 commit = self.rhodecode_vcs_repo.get_commit(commit_id=commit_id)
346 try:
346 try:
347 node = commit.get_node(f_path)
347 node = commit.get_node(f_path)
348 if node.is_dir():
348 if node.is_dir():
349 raise NodeError('%s path is a %s not a file'
349 raise NodeError('%s path is a %s not a file'
350 % (node, type(node)))
350 % (node, type(node)))
351 except NodeDoesNotExistError:
351 except NodeDoesNotExistError:
352 commit = EmptyCommit(
352 commit = EmptyCommit(
353 commit_id=commit_id,
353 commit_id=commit_id,
354 idx=commit.idx,
354 idx=commit.idx,
355 repo=commit.repository,
355 repo=commit.repository,
356 alias=commit.repository.alias,
356 alias=commit.repository.alias,
357 message=commit.message,
357 message=commit.message,
358 author=commit.author,
358 author=commit.author,
359 date=commit.date)
359 date=commit.date)
360 node = FileNode(f_path, '', commit=commit)
360 node = FileNode(f_path, '', commit=commit)
361 else:
361 else:
362 commit = EmptyCommit(
362 commit = EmptyCommit(
363 repo=self.rhodecode_vcs_repo,
363 repo=self.rhodecode_vcs_repo,
364 alias=self.rhodecode_vcs_repo.alias)
364 alias=self.rhodecode_vcs_repo.alias)
365 node = FileNode(f_path, '', commit=commit)
365 node = FileNode(f_path, '', commit=commit)
366 return node
366 return node
367
367
368 @LoginRequired()
368 @LoginRequired()
369 @HasRepoPermissionAnyDecorator(
369 @HasRepoPermissionAnyDecorator(
370 'repository.read', 'repository.write', 'repository.admin')
370 'repository.read', 'repository.write', 'repository.admin')
371 @view_config(
371 @view_config(
372 route_name='repo_files_diff', request_method='GET',
372 route_name='repo_files_diff', request_method='GET',
373 renderer=None)
373 renderer=None)
374 def repo_files_diff(self):
374 def repo_files_diff(self):
375 c = self.load_default_context()
375 c = self.load_default_context()
376 f_path = self._get_f_path(self.request.matchdict)
376 f_path = self._get_f_path(self.request.matchdict)
377 diff1 = self.request.GET.get('diff1', '')
377 diff1 = self.request.GET.get('diff1', '')
378 diff2 = self.request.GET.get('diff2', '')
378 diff2 = self.request.GET.get('diff2', '')
379
379
380 path1, diff1 = parse_path_ref(diff1, default_path=f_path)
380 path1, diff1 = parse_path_ref(diff1, default_path=f_path)
381
381
382 ignore_whitespace = str2bool(self.request.GET.get('ignorews'))
382 ignore_whitespace = str2bool(self.request.GET.get('ignorews'))
383 line_context = self.request.GET.get('context', 3)
383 line_context = self.request.GET.get('context', 3)
384
384
385 if not any((diff1, diff2)):
385 if not any((diff1, diff2)):
386 h.flash(
386 h.flash(
387 'Need query parameter "diff1" or "diff2" to generate a diff.',
387 'Need query parameter "diff1" or "diff2" to generate a diff.',
388 category='error')
388 category='error')
389 raise HTTPBadRequest()
389 raise HTTPBadRequest()
390
390
391 c.action = self.request.GET.get('diff')
391 c.action = self.request.GET.get('diff')
392 if c.action not in ['download', 'raw']:
392 if c.action not in ['download', 'raw']:
393 compare_url = h.route_path(
393 compare_url = h.route_path(
394 'repo_compare',
394 'repo_compare',
395 repo_name=self.db_repo_name,
395 repo_name=self.db_repo_name,
396 source_ref_type='rev',
396 source_ref_type='rev',
397 source_ref=diff1,
397 source_ref=diff1,
398 target_repo=self.db_repo_name,
398 target_repo=self.db_repo_name,
399 target_ref_type='rev',
399 target_ref_type='rev',
400 target_ref=diff2,
400 target_ref=diff2,
401 _query=dict(f_path=f_path))
401 _query=dict(f_path=f_path))
402 # redirect to new view if we render diff
402 # redirect to new view if we render diff
403 raise HTTPFound(compare_url)
403 raise HTTPFound(compare_url)
404
404
405 try:
405 try:
406 node1 = self._get_file_node(diff1, path1)
406 node1 = self._get_file_node(diff1, path1)
407 node2 = self._get_file_node(diff2, f_path)
407 node2 = self._get_file_node(diff2, f_path)
408 except (RepositoryError, NodeError):
408 except (RepositoryError, NodeError):
409 log.exception("Exception while trying to get node from repository")
409 log.exception("Exception while trying to get node from repository")
410 raise HTTPFound(
410 raise HTTPFound(
411 h.route_path('repo_files', repo_name=self.db_repo_name,
411 h.route_path('repo_files', repo_name=self.db_repo_name,
412 commit_id='tip', f_path=f_path))
412 commit_id='tip', f_path=f_path))
413
413
414 if all(isinstance(node.commit, EmptyCommit)
414 if all(isinstance(node.commit, EmptyCommit)
415 for node in (node1, node2)):
415 for node in (node1, node2)):
416 raise HTTPNotFound()
416 raise HTTPNotFound()
417
417
418 c.commit_1 = node1.commit
418 c.commit_1 = node1.commit
419 c.commit_2 = node2.commit
419 c.commit_2 = node2.commit
420
420
421 if c.action == 'download':
421 if c.action == 'download':
422 _diff = diffs.get_gitdiff(node1, node2,
422 _diff = diffs.get_gitdiff(node1, node2,
423 ignore_whitespace=ignore_whitespace,
423 ignore_whitespace=ignore_whitespace,
424 context=line_context)
424 context=line_context)
425 diff = diffs.DiffProcessor(_diff, format='gitdiff')
425 diff = diffs.DiffProcessor(_diff, format='gitdiff')
426
426
427 response = Response(diff.as_raw())
427 response = Response(diff.as_raw())
428 response.content_type = 'text/plain'
428 response.content_type = 'text/plain'
429 response.content_disposition = (
429 response.content_disposition = (
430 'attachment; filename=%s_%s_vs_%s.diff' % (f_path, diff1, diff2)
430 'attachment; filename=%s_%s_vs_%s.diff' % (f_path, diff1, diff2)
431 )
431 )
432 charset = self._get_default_encoding(c)
432 charset = self._get_default_encoding(c)
433 if charset:
433 if charset:
434 response.charset = charset
434 response.charset = charset
435 return response
435 return response
436
436
437 elif c.action == 'raw':
437 elif c.action == 'raw':
438 _diff = diffs.get_gitdiff(node1, node2,
438 _diff = diffs.get_gitdiff(node1, node2,
439 ignore_whitespace=ignore_whitespace,
439 ignore_whitespace=ignore_whitespace,
440 context=line_context)
440 context=line_context)
441 diff = diffs.DiffProcessor(_diff, format='gitdiff')
441 diff = diffs.DiffProcessor(_diff, format='gitdiff')
442
442
443 response = Response(diff.as_raw())
443 response = Response(diff.as_raw())
444 response.content_type = 'text/plain'
444 response.content_type = 'text/plain'
445 charset = self._get_default_encoding(c)
445 charset = self._get_default_encoding(c)
446 if charset:
446 if charset:
447 response.charset = charset
447 response.charset = charset
448 return response
448 return response
449
449
450 # in case we ever end up here
450 # in case we ever end up here
451 raise HTTPNotFound()
451 raise HTTPNotFound()
452
452
453 @LoginRequired()
453 @LoginRequired()
454 @HasRepoPermissionAnyDecorator(
454 @HasRepoPermissionAnyDecorator(
455 'repository.read', 'repository.write', 'repository.admin')
455 'repository.read', 'repository.write', 'repository.admin')
456 @view_config(
456 @view_config(
457 route_name='repo_files_diff_2way_redirect', request_method='GET',
457 route_name='repo_files_diff_2way_redirect', request_method='GET',
458 renderer=None)
458 renderer=None)
459 def repo_files_diff_2way_redirect(self):
459 def repo_files_diff_2way_redirect(self):
460 """
460 """
461 Kept only to make OLD links work
461 Kept only to make OLD links work
462 """
462 """
463 f_path = self._get_f_path(self.request.matchdict)
463 f_path = self._get_f_path(self.request.matchdict)
464 diff1 = self.request.GET.get('diff1', '')
464 diff1 = self.request.GET.get('diff1', '')
465 diff2 = self.request.GET.get('diff2', '')
465 diff2 = self.request.GET.get('diff2', '')
466
466
467 if not any((diff1, diff2)):
467 if not any((diff1, diff2)):
468 h.flash(
468 h.flash(
469 'Need query parameter "diff1" or "diff2" to generate a diff.',
469 'Need query parameter "diff1" or "diff2" to generate a diff.',
470 category='error')
470 category='error')
471 raise HTTPBadRequest()
471 raise HTTPBadRequest()
472
472
473 compare_url = h.route_path(
473 compare_url = h.route_path(
474 'repo_compare',
474 'repo_compare',
475 repo_name=self.db_repo_name,
475 repo_name=self.db_repo_name,
476 source_ref_type='rev',
476 source_ref_type='rev',
477 source_ref=diff1,
477 source_ref=diff1,
478 target_ref_type='rev',
478 target_ref_type='rev',
479 target_ref=diff2,
479 target_ref=diff2,
480 _query=dict(f_path=f_path, diffmode='sideside',
480 _query=dict(f_path=f_path, diffmode='sideside',
481 target_repo=self.db_repo_name,))
481 target_repo=self.db_repo_name,))
482 raise HTTPFound(compare_url)
482 raise HTTPFound(compare_url)
483
483
484 @LoginRequired()
484 @LoginRequired()
485 @HasRepoPermissionAnyDecorator(
485 @HasRepoPermissionAnyDecorator(
486 'repository.read', 'repository.write', 'repository.admin')
486 'repository.read', 'repository.write', 'repository.admin')
487 @view_config(
487 @view_config(
488 route_name='repo_files', request_method='GET',
488 route_name='repo_files', request_method='GET',
489 renderer=None)
489 renderer=None)
490 @view_config(
490 @view_config(
491 route_name='repo_files:default_path', request_method='GET',
491 route_name='repo_files:default_path', request_method='GET',
492 renderer=None)
492 renderer=None)
493 @view_config(
493 @view_config(
494 route_name='repo_files:default_commit', request_method='GET',
494 route_name='repo_files:default_commit', request_method='GET',
495 renderer=None)
495 renderer=None)
496 @view_config(
496 @view_config(
497 route_name='repo_files:rendered', request_method='GET',
497 route_name='repo_files:rendered', request_method='GET',
498 renderer=None)
498 renderer=None)
499 @view_config(
499 @view_config(
500 route_name='repo_files:annotated', request_method='GET',
500 route_name='repo_files:annotated', request_method='GET',
501 renderer=None)
501 renderer=None)
502 def repo_files(self):
502 def repo_files(self):
503 c = self.load_default_context()
503 c = self.load_default_context()
504
504
505 view_name = getattr(self.request.matched_route, 'name', None)
505 view_name = getattr(self.request.matched_route, 'name', None)
506
506
507 c.annotate = view_name == 'repo_files:annotated'
507 c.annotate = view_name == 'repo_files:annotated'
508 # default is false, but .rst/.md files later are auto rendered, we can
508 # default is false, but .rst/.md files later are auto rendered, we can
509 # overwrite auto rendering by setting this GET flag
509 # overwrite auto rendering by setting this GET flag
510 c.renderer = view_name == 'repo_files:rendered' or \
510 c.renderer = view_name == 'repo_files:rendered' or \
511 not self.request.GET.get('no-render', False)
511 not self.request.GET.get('no-render', False)
512
512
513 # redirect to given commit_id from form if given
513 # redirect to given commit_id from form if given
514 get_commit_id = self.request.GET.get('at_rev', None)
514 get_commit_id = self.request.GET.get('at_rev', None)
515 if get_commit_id:
515 if get_commit_id:
516 self._get_commit_or_redirect(get_commit_id)
516 self._get_commit_or_redirect(get_commit_id)
517
517
518 commit_id, f_path = self._get_commit_and_path()
518 commit_id, f_path = self._get_commit_and_path()
519 c.commit = self._get_commit_or_redirect(commit_id)
519 c.commit = self._get_commit_or_redirect(commit_id)
520 c.branch = self.request.GET.get('branch', None)
520 c.branch = self.request.GET.get('branch', None)
521 c.f_path = f_path
521 c.f_path = f_path
522
522
523 # prev link
523 # prev link
524 try:
524 try:
525 prev_commit = c.commit.prev(c.branch)
525 prev_commit = c.commit.prev(c.branch)
526 c.prev_commit = prev_commit
526 c.prev_commit = prev_commit
527 c.url_prev = h.route_path(
527 c.url_prev = h.route_path(
528 'repo_files', repo_name=self.db_repo_name,
528 'repo_files', repo_name=self.db_repo_name,
529 commit_id=prev_commit.raw_id, f_path=f_path)
529 commit_id=prev_commit.raw_id, f_path=f_path)
530 if c.branch:
530 if c.branch:
531 c.url_prev += '?branch=%s' % c.branch
531 c.url_prev += '?branch=%s' % c.branch
532 except (CommitDoesNotExistError, VCSError):
532 except (CommitDoesNotExistError, VCSError):
533 c.url_prev = '#'
533 c.url_prev = '#'
534 c.prev_commit = EmptyCommit()
534 c.prev_commit = EmptyCommit()
535
535
536 # next link
536 # next link
537 try:
537 try:
538 next_commit = c.commit.next(c.branch)
538 next_commit = c.commit.next(c.branch)
539 c.next_commit = next_commit
539 c.next_commit = next_commit
540 c.url_next = h.route_path(
540 c.url_next = h.route_path(
541 'repo_files', repo_name=self.db_repo_name,
541 'repo_files', repo_name=self.db_repo_name,
542 commit_id=next_commit.raw_id, f_path=f_path)
542 commit_id=next_commit.raw_id, f_path=f_path)
543 if c.branch:
543 if c.branch:
544 c.url_next += '?branch=%s' % c.branch
544 c.url_next += '?branch=%s' % c.branch
545 except (CommitDoesNotExistError, VCSError):
545 except (CommitDoesNotExistError, VCSError):
546 c.url_next = '#'
546 c.url_next = '#'
547 c.next_commit = EmptyCommit()
547 c.next_commit = EmptyCommit()
548
548
549 # files or dirs
549 # files or dirs
550 try:
550 try:
551 c.file = c.commit.get_node(f_path)
551 c.file = c.commit.get_node(f_path)
552 c.file_author = True
552 c.file_author = True
553 c.file_tree = ''
553 c.file_tree = ''
554
554
555 # load file content
555 # load file content
556 if c.file.is_file():
556 if c.file.is_file():
557 c.lf_node = c.file.get_largefile_node()
557 c.lf_node = c.file.get_largefile_node()
558
558
559 c.file_source_page = 'true'
559 c.file_source_page = 'true'
560 c.file_last_commit = c.file.last_commit
560 c.file_last_commit = c.file.last_commit
561 if c.file.size < c.visual.cut_off_limit_diff:
561 if c.file.size < c.visual.cut_off_limit_diff:
562 if c.annotate: # annotation has precedence over renderer
562 if c.annotate: # annotation has precedence over renderer
563 c.annotated_lines = filenode_as_annotated_lines_tokens(
563 c.annotated_lines = filenode_as_annotated_lines_tokens(
564 c.file
564 c.file
565 )
565 )
566 else:
566 else:
567 c.renderer = (
567 c.renderer = (
568 c.renderer and h.renderer_from_filename(c.file.path)
568 c.renderer and h.renderer_from_filename(c.file.path)
569 )
569 )
570 if not c.renderer:
570 if not c.renderer:
571 c.lines = filenode_as_lines_tokens(c.file)
571 c.lines = filenode_as_lines_tokens(c.file)
572
572
573 c.on_branch_head = self._is_valid_head(
573 c.on_branch_head = self._is_valid_head(
574 commit_id, self.rhodecode_vcs_repo)
574 commit_id, self.rhodecode_vcs_repo)
575
575
576 branch = c.commit.branch if (
576 branch = c.commit.branch if (
577 c.commit.branch and '/' not in c.commit.branch) else None
577 c.commit.branch and '/' not in c.commit.branch) else None
578 c.branch_or_raw_id = branch or c.commit.raw_id
578 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)
579 c.branch_name = c.commit.branch or h.short_id(c.commit.raw_id)
580
580
581 author = c.file_last_commit.author
581 author = c.file_last_commit.author
582 c.authors = [[
582 c.authors = [[
583 h.email(author),
583 h.email(author),
584 h.person(author, 'username_or_name_or_email'),
584 h.person(author, 'username_or_name_or_email'),
585 1
585 1
586 ]]
586 ]]
587
587
588 else: # load tree content at path
588 else: # load tree content at path
589 c.file_source_page = 'false'
589 c.file_source_page = 'false'
590 c.authors = []
590 c.authors = []
591 # this loads a simple tree without metadata to speed things up
591 # this loads a simple tree without metadata to speed things up
592 # later via ajax we call repo_nodetree_full and fetch whole
592 # later via ajax we call repo_nodetree_full and fetch whole
593 c.file_tree = self._get_tree_at_commit(
593 c.file_tree = self._get_tree_at_commit(
594 c, c.commit.raw_id, f_path)
594 c, c.commit.raw_id, f_path)
595
595
596 except RepositoryError as e:
596 except RepositoryError as e:
597 h.flash(safe_str(h.escape(e)), category='error')
597 h.flash(safe_str(h.escape(e)), category='error')
598 raise HTTPNotFound()
598 raise HTTPNotFound()
599
599
600 if self.request.environ.get('HTTP_X_PJAX'):
600 if self.request.environ.get('HTTP_X_PJAX'):
601 html = render('rhodecode:templates/files/files_pjax.mako',
601 html = render('rhodecode:templates/files/files_pjax.mako',
602 self._get_template_context(c), self.request)
602 self._get_template_context(c), self.request)
603 else:
603 else:
604 html = render('rhodecode:templates/files/files.mako',
604 html = render('rhodecode:templates/files/files.mako',
605 self._get_template_context(c), self.request)
605 self._get_template_context(c), self.request)
606 return Response(html)
606 return Response(html)
607
607
608 @HasRepoPermissionAnyDecorator(
608 @HasRepoPermissionAnyDecorator(
609 'repository.read', 'repository.write', 'repository.admin')
609 'repository.read', 'repository.write', 'repository.admin')
610 @view_config(
610 @view_config(
611 route_name='repo_files:annotated_previous', request_method='GET',
611 route_name='repo_files:annotated_previous', request_method='GET',
612 renderer=None)
612 renderer=None)
613 def repo_files_annotated_previous(self):
613 def repo_files_annotated_previous(self):
614 self.load_default_context()
614 self.load_default_context()
615
615
616 commit_id, f_path = self._get_commit_and_path()
616 commit_id, f_path = self._get_commit_and_path()
617 commit = self._get_commit_or_redirect(commit_id)
617 commit = self._get_commit_or_redirect(commit_id)
618 prev_commit_id = commit.raw_id
618 prev_commit_id = commit.raw_id
619 line_anchor = self.request.GET.get('line_anchor')
619 line_anchor = self.request.GET.get('line_anchor')
620 is_file = False
620 is_file = False
621 try:
621 try:
622 _file = commit.get_node(f_path)
622 _file = commit.get_node(f_path)
623 is_file = _file.is_file()
623 is_file = _file.is_file()
624 except (NodeDoesNotExistError, CommitDoesNotExistError, VCSError):
624 except (NodeDoesNotExistError, CommitDoesNotExistError, VCSError):
625 pass
625 pass
626
626
627 if is_file:
627 if is_file:
628 history = commit.get_file_history(f_path)
628 history = commit.get_file_history(f_path)
629 prev_commit_id = history[1].raw_id \
629 prev_commit_id = history[1].raw_id \
630 if len(history) > 1 else prev_commit_id
630 if len(history) > 1 else prev_commit_id
631 prev_url = h.route_path(
631 prev_url = h.route_path(
632 'repo_files:annotated', repo_name=self.db_repo_name,
632 'repo_files:annotated', repo_name=self.db_repo_name,
633 commit_id=prev_commit_id, f_path=f_path,
633 commit_id=prev_commit_id, f_path=f_path,
634 _anchor='L{}'.format(line_anchor))
634 _anchor='L{}'.format(line_anchor))
635
635
636 raise HTTPFound(prev_url)
636 raise HTTPFound(prev_url)
637
637
638 @LoginRequired()
638 @LoginRequired()
639 @HasRepoPermissionAnyDecorator(
639 @HasRepoPermissionAnyDecorator(
640 'repository.read', 'repository.write', 'repository.admin')
640 'repository.read', 'repository.write', 'repository.admin')
641 @view_config(
641 @view_config(
642 route_name='repo_nodetree_full', request_method='GET',
642 route_name='repo_nodetree_full', request_method='GET',
643 renderer=None, xhr=True)
643 renderer=None, xhr=True)
644 @view_config(
644 @view_config(
645 route_name='repo_nodetree_full:default_path', request_method='GET',
645 route_name='repo_nodetree_full:default_path', request_method='GET',
646 renderer=None, xhr=True)
646 renderer=None, xhr=True)
647 def repo_nodetree_full(self):
647 def repo_nodetree_full(self):
648 """
648 """
649 Returns rendered html of file tree that contains commit date,
649 Returns rendered html of file tree that contains commit date,
650 author, commit_id for the specified combination of
650 author, commit_id for the specified combination of
651 repo, commit_id and file path
651 repo, commit_id and file path
652 """
652 """
653 c = self.load_default_context()
653 c = self.load_default_context()
654
654
655 commit_id, f_path = self._get_commit_and_path()
655 commit_id, f_path = self._get_commit_and_path()
656 commit = self._get_commit_or_redirect(commit_id)
656 commit = self._get_commit_or_redirect(commit_id)
657 try:
657 try:
658 dir_node = commit.get_node(f_path)
658 dir_node = commit.get_node(f_path)
659 except RepositoryError as e:
659 except RepositoryError as e:
660 return Response('error: {}'.format(safe_str(e)))
660 return Response('error: {}'.format(safe_str(e)))
661
661
662 if dir_node.is_file():
662 if dir_node.is_file():
663 return Response('')
663 return Response('')
664
664
665 c.file = dir_node
665 c.file = dir_node
666 c.commit = commit
666 c.commit = commit
667
667
668 # using force=True here, make a little trick. We flush the cache and
668 # 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
669 # compute it using the same key as without previous full_load, so now
670 # the fully loaded tree is now returned instead of partial,
670 # the fully loaded tree is now returned instead of partial,
671 # and we store this in caches
671 # and we store this in caches
672 html = self._get_tree_at_commit(
672 html = self._get_tree_at_commit(
673 c, commit.raw_id, dir_node.path, full_load=True, force=True)
673 c, commit.raw_id, dir_node.path, full_load=True, force=True)
674
674
675 return Response(html)
675 return Response(html)
676
676
677 def _get_attachement_disposition(self, f_path):
677 def _get_attachement_disposition(self, f_path):
678 return 'attachment; filename=%s' % \
678 return 'attachment; filename=%s' % \
679 safe_str(f_path.split(Repository.NAME_SEP)[-1])
679 safe_str(f_path.split(Repository.NAME_SEP)[-1])
680
680
681 @LoginRequired()
681 @LoginRequired()
682 @HasRepoPermissionAnyDecorator(
682 @HasRepoPermissionAnyDecorator(
683 'repository.read', 'repository.write', 'repository.admin')
683 'repository.read', 'repository.write', 'repository.admin')
684 @view_config(
684 @view_config(
685 route_name='repo_file_raw', request_method='GET',
685 route_name='repo_file_raw', request_method='GET',
686 renderer=None)
686 renderer=None)
687 def repo_file_raw(self):
687 def repo_file_raw(self):
688 """
688 """
689 Action for show as raw, some mimetypes are "rendered",
689 Action for show as raw, some mimetypes are "rendered",
690 those include images, icons.
690 those include images, icons.
691 """
691 """
692 c = self.load_default_context()
692 c = self.load_default_context()
693
693
694 commit_id, f_path = self._get_commit_and_path()
694 commit_id, f_path = self._get_commit_and_path()
695 commit = self._get_commit_or_redirect(commit_id)
695 commit = self._get_commit_or_redirect(commit_id)
696 file_node = self._get_filenode_or_redirect(commit, f_path)
696 file_node = self._get_filenode_or_redirect(commit, f_path)
697
697
698 raw_mimetype_mapping = {
698 raw_mimetype_mapping = {
699 # map original mimetype to a mimetype used for "show as raw"
699 # map original mimetype to a mimetype used for "show as raw"
700 # you can also provide a content-disposition to override the
700 # you can also provide a content-disposition to override the
701 # default "attachment" disposition.
701 # default "attachment" disposition.
702 # orig_type: (new_type, new_dispo)
702 # orig_type: (new_type, new_dispo)
703
703
704 # show images inline:
704 # show images inline:
705 # Do not re-add SVG: it is unsafe and permits XSS attacks. One can
705 # 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
706 # for example render an SVG with javascript inside or even render
707 # HTML.
707 # HTML.
708 'image/x-icon': ('image/x-icon', 'inline'),
708 'image/x-icon': ('image/x-icon', 'inline'),
709 'image/png': ('image/png', 'inline'),
709 'image/png': ('image/png', 'inline'),
710 'image/gif': ('image/gif', 'inline'),
710 'image/gif': ('image/gif', 'inline'),
711 'image/jpeg': ('image/jpeg', 'inline'),
711 'image/jpeg': ('image/jpeg', 'inline'),
712 'application/pdf': ('application/pdf', 'inline'),
712 'application/pdf': ('application/pdf', 'inline'),
713 }
713 }
714
714
715 mimetype = file_node.mimetype
715 mimetype = file_node.mimetype
716 try:
716 try:
717 mimetype, disposition = raw_mimetype_mapping[mimetype]
717 mimetype, disposition = raw_mimetype_mapping[mimetype]
718 except KeyError:
718 except KeyError:
719 # we don't know anything special about this, handle it safely
719 # we don't know anything special about this, handle it safely
720 if file_node.is_binary:
720 if file_node.is_binary:
721 # do same as download raw for binary files
721 # do same as download raw for binary files
722 mimetype, disposition = 'application/octet-stream', 'attachment'
722 mimetype, disposition = 'application/octet-stream', 'attachment'
723 else:
723 else:
724 # do not just use the original mimetype, but force text/plain,
724 # do not just use the original mimetype, but force text/plain,
725 # otherwise it would serve text/html and that might be unsafe.
725 # otherwise it would serve text/html and that might be unsafe.
726 # Note: underlying vcs library fakes text/plain mimetype if the
726 # Note: underlying vcs library fakes text/plain mimetype if the
727 # mimetype can not be determined and it thinks it is not
727 # mimetype can not be determined and it thinks it is not
728 # binary.This might lead to erroneous text display in some
728 # binary.This might lead to erroneous text display in some
729 # cases, but helps in other cases, like with text files
729 # cases, but helps in other cases, like with text files
730 # without extension.
730 # without extension.
731 mimetype, disposition = 'text/plain', 'inline'
731 mimetype, disposition = 'text/plain', 'inline'
732
732
733 if disposition == 'attachment':
733 if disposition == 'attachment':
734 disposition = self._get_attachement_disposition(f_path)
734 disposition = self._get_attachement_disposition(f_path)
735
735
736 def stream_node():
736 def stream_node():
737 yield file_node.raw_bytes
737 yield file_node.raw_bytes
738
738
739 response = Response(app_iter=stream_node())
739 response = Response(app_iter=stream_node())
740 response.content_disposition = disposition
740 response.content_disposition = disposition
741 response.content_type = mimetype
741 response.content_type = mimetype
742
742
743 charset = self._get_default_encoding(c)
743 charset = self._get_default_encoding(c)
744 if charset:
744 if charset:
745 response.charset = charset
745 response.charset = charset
746
746
747 return response
747 return response
748
748
749 @LoginRequired()
749 @LoginRequired()
750 @HasRepoPermissionAnyDecorator(
750 @HasRepoPermissionAnyDecorator(
751 'repository.read', 'repository.write', 'repository.admin')
751 'repository.read', 'repository.write', 'repository.admin')
752 @view_config(
752 @view_config(
753 route_name='repo_file_download', request_method='GET',
753 route_name='repo_file_download', request_method='GET',
754 renderer=None)
754 renderer=None)
755 @view_config(
755 @view_config(
756 route_name='repo_file_download:legacy', request_method='GET',
756 route_name='repo_file_download:legacy', request_method='GET',
757 renderer=None)
757 renderer=None)
758 def repo_file_download(self):
758 def repo_file_download(self):
759 c = self.load_default_context()
759 c = self.load_default_context()
760
760
761 commit_id, f_path = self._get_commit_and_path()
761 commit_id, f_path = self._get_commit_and_path()
762 commit = self._get_commit_or_redirect(commit_id)
762 commit = self._get_commit_or_redirect(commit_id)
763 file_node = self._get_filenode_or_redirect(commit, f_path)
763 file_node = self._get_filenode_or_redirect(commit, f_path)
764
764
765 if self.request.GET.get('lf'):
765 if self.request.GET.get('lf'):
766 # only if lf get flag is passed, we download this file
766 # only if lf get flag is passed, we download this file
767 # as LFS/Largefile
767 # as LFS/Largefile
768 lf_node = file_node.get_largefile_node()
768 lf_node = file_node.get_largefile_node()
769 if lf_node:
769 if lf_node:
770 # overwrite our pointer with the REAL large-file
770 # overwrite our pointer with the REAL large-file
771 file_node = lf_node
771 file_node = lf_node
772
772
773 disposition = self._get_attachement_disposition(f_path)
773 disposition = self._get_attachement_disposition(f_path)
774
774
775 def stream_node():
775 def stream_node():
776 yield file_node.raw_bytes
776 yield file_node.raw_bytes
777
777
778 response = Response(app_iter=stream_node())
778 response = Response(app_iter=stream_node())
779 response.content_disposition = disposition
779 response.content_disposition = disposition
780 response.content_type = file_node.mimetype
780 response.content_type = file_node.mimetype
781
781
782 charset = self._get_default_encoding(c)
782 charset = self._get_default_encoding(c)
783 if charset:
783 if charset:
784 response.charset = charset
784 response.charset = charset
785
785
786 return response
786 return response
787
787
788 def _get_nodelist_at_commit(self, repo_name, commit_id, f_path):
788 def _get_nodelist_at_commit(self, repo_name, commit_id, f_path):
789 def _cached_nodes():
789 def _cached_nodes():
790 log.debug('Generating cached nodelist for %s, %s, %s',
790 log.debug('Generating cached nodelist for %s, %s, %s',
791 repo_name, commit_id, f_path)
791 repo_name, commit_id, f_path)
792 _d, _f = ScmModel().get_nodes(
792 try:
793 repo_name, commit_id, f_path, flat=False)
793 _d, _f = ScmModel().get_nodes(
794 repo_name, commit_id, f_path, flat=False)
795 except (RepositoryError, CommitDoesNotExistError, Exception) as e:
796 log.exception(safe_str(e))
797 h.flash(safe_str(h.escape(e)), category='error')
798 raise HTTPFound(h.route_path(
799 'repo_files', repo_name=self.db_repo_name,
800 commit_id='tip', f_path='/'))
794 return _d + _f
801 return _d + _f
795
802
796 cache_manager = self._get_tree_cache_manager(caches.FILE_SEARCH_TREE_META)
803 cache_manager = self._get_tree_cache_manager(
804 caches.FILE_SEARCH_TREE_META)
797
805
798 cache_key = caches.compute_key_from_params(
806 cache_key = caches.compute_key_from_params(
799 repo_name, commit_id, f_path)
807 repo_name, commit_id, f_path)
800 return cache_manager.get(cache_key, createfunc=_cached_nodes)
808 return cache_manager.get(cache_key, createfunc=_cached_nodes)
801
809
802 @LoginRequired()
810 @LoginRequired()
803 @HasRepoPermissionAnyDecorator(
811 @HasRepoPermissionAnyDecorator(
804 'repository.read', 'repository.write', 'repository.admin')
812 'repository.read', 'repository.write', 'repository.admin')
805 @view_config(
813 @view_config(
806 route_name='repo_files_nodelist', request_method='GET',
814 route_name='repo_files_nodelist', request_method='GET',
807 renderer='json_ext', xhr=True)
815 renderer='json_ext', xhr=True)
808 def repo_nodelist(self):
816 def repo_nodelist(self):
809 self.load_default_context()
817 self.load_default_context()
810
818
811 commit_id, f_path = self._get_commit_and_path()
819 commit_id, f_path = self._get_commit_and_path()
812 commit = self._get_commit_or_redirect(commit_id)
820 commit = self._get_commit_or_redirect(commit_id)
813
821
814 metadata = self._get_nodelist_at_commit(
822 metadata = self._get_nodelist_at_commit(
815 self.db_repo_name, commit.raw_id, f_path)
823 self.db_repo_name, commit.raw_id, f_path)
816 return {'nodes': metadata}
824 return {'nodes': metadata}
817
825
818 def _create_references(
826 def _create_references(
819 self, branches_or_tags, symbolic_reference, f_path):
827 self, branches_or_tags, symbolic_reference, f_path):
820 items = []
828 items = []
821 for name, commit_id in branches_or_tags.items():
829 for name, commit_id in branches_or_tags.items():
822 sym_ref = symbolic_reference(commit_id, name, f_path)
830 sym_ref = symbolic_reference(commit_id, name, f_path)
823 items.append((sym_ref, name))
831 items.append((sym_ref, name))
824 return items
832 return items
825
833
826 def _symbolic_reference(self, commit_id, name, f_path):
834 def _symbolic_reference(self, commit_id, name, f_path):
827 return commit_id
835 return commit_id
828
836
829 def _symbolic_reference_svn(self, commit_id, name, f_path):
837 def _symbolic_reference_svn(self, commit_id, name, f_path):
830 new_f_path = vcspath.join(name, f_path)
838 new_f_path = vcspath.join(name, f_path)
831 return u'%s@%s' % (new_f_path, commit_id)
839 return u'%s@%s' % (new_f_path, commit_id)
832
840
833 def _get_node_history(self, commit_obj, f_path, commits=None):
841 def _get_node_history(self, commit_obj, f_path, commits=None):
834 """
842 """
835 get commit history for given node
843 get commit history for given node
836
844
837 :param commit_obj: commit to calculate history
845 :param commit_obj: commit to calculate history
838 :param f_path: path for node to calculate history for
846 :param f_path: path for node to calculate history for
839 :param commits: if passed don't calculate history and take
847 :param commits: if passed don't calculate history and take
840 commits defined in this list
848 commits defined in this list
841 """
849 """
842 _ = self.request.translate
850 _ = self.request.translate
843
851
844 # calculate history based on tip
852 # calculate history based on tip
845 tip = self.rhodecode_vcs_repo.get_commit()
853 tip = self.rhodecode_vcs_repo.get_commit()
846 if commits is None:
854 if commits is None:
847 pre_load = ["author", "branch"]
855 pre_load = ["author", "branch"]
848 try:
856 try:
849 commits = tip.get_file_history(f_path, pre_load=pre_load)
857 commits = tip.get_file_history(f_path, pre_load=pre_load)
850 except (NodeDoesNotExistError, CommitError):
858 except (NodeDoesNotExistError, CommitError):
851 # this node is not present at tip!
859 # this node is not present at tip!
852 commits = commit_obj.get_file_history(f_path, pre_load=pre_load)
860 commits = commit_obj.get_file_history(f_path, pre_load=pre_load)
853
861
854 history = []
862 history = []
855 commits_group = ([], _("Changesets"))
863 commits_group = ([], _("Changesets"))
856 for commit in commits:
864 for commit in commits:
857 branch = ' (%s)' % commit.branch if commit.branch else ''
865 branch = ' (%s)' % commit.branch if commit.branch else ''
858 n_desc = 'r%s:%s%s' % (commit.idx, commit.short_id, branch)
866 n_desc = 'r%s:%s%s' % (commit.idx, commit.short_id, branch)
859 commits_group[0].append((commit.raw_id, n_desc,))
867 commits_group[0].append((commit.raw_id, n_desc,))
860 history.append(commits_group)
868 history.append(commits_group)
861
869
862 symbolic_reference = self._symbolic_reference
870 symbolic_reference = self._symbolic_reference
863
871
864 if self.rhodecode_vcs_repo.alias == 'svn':
872 if self.rhodecode_vcs_repo.alias == 'svn':
865 adjusted_f_path = RepoFilesView.adjust_file_path_for_svn(
873 adjusted_f_path = RepoFilesView.adjust_file_path_for_svn(
866 f_path, self.rhodecode_vcs_repo)
874 f_path, self.rhodecode_vcs_repo)
867 if adjusted_f_path != f_path:
875 if adjusted_f_path != f_path:
868 log.debug(
876 log.debug(
869 'Recognized svn tag or branch in file "%s", using svn '
877 'Recognized svn tag or branch in file "%s", using svn '
870 'specific symbolic references', f_path)
878 'specific symbolic references', f_path)
871 f_path = adjusted_f_path
879 f_path = adjusted_f_path
872 symbolic_reference = self._symbolic_reference_svn
880 symbolic_reference = self._symbolic_reference_svn
873
881
874 branches = self._create_references(
882 branches = self._create_references(
875 self.rhodecode_vcs_repo.branches, symbolic_reference, f_path)
883 self.rhodecode_vcs_repo.branches, symbolic_reference, f_path)
876 branches_group = (branches, _("Branches"))
884 branches_group = (branches, _("Branches"))
877
885
878 tags = self._create_references(
886 tags = self._create_references(
879 self.rhodecode_vcs_repo.tags, symbolic_reference, f_path)
887 self.rhodecode_vcs_repo.tags, symbolic_reference, f_path)
880 tags_group = (tags, _("Tags"))
888 tags_group = (tags, _("Tags"))
881
889
882 history.append(branches_group)
890 history.append(branches_group)
883 history.append(tags_group)
891 history.append(tags_group)
884
892
885 return history, commits
893 return history, commits
886
894
887 @LoginRequired()
895 @LoginRequired()
888 @HasRepoPermissionAnyDecorator(
896 @HasRepoPermissionAnyDecorator(
889 'repository.read', 'repository.write', 'repository.admin')
897 'repository.read', 'repository.write', 'repository.admin')
890 @view_config(
898 @view_config(
891 route_name='repo_file_history', request_method='GET',
899 route_name='repo_file_history', request_method='GET',
892 renderer='json_ext')
900 renderer='json_ext')
893 def repo_file_history(self):
901 def repo_file_history(self):
894 self.load_default_context()
902 self.load_default_context()
895
903
896 commit_id, f_path = self._get_commit_and_path()
904 commit_id, f_path = self._get_commit_and_path()
897 commit = self._get_commit_or_redirect(commit_id)
905 commit = self._get_commit_or_redirect(commit_id)
898 file_node = self._get_filenode_or_redirect(commit, f_path)
906 file_node = self._get_filenode_or_redirect(commit, f_path)
899
907
900 if file_node.is_file():
908 if file_node.is_file():
901 file_history, _hist = self._get_node_history(commit, f_path)
909 file_history, _hist = self._get_node_history(commit, f_path)
902
910
903 res = []
911 res = []
904 for obj in file_history:
912 for obj in file_history:
905 res.append({
913 res.append({
906 'text': obj[1],
914 'text': obj[1],
907 'children': [{'id': o[0], 'text': o[1]} for o in obj[0]]
915 'children': [{'id': o[0], 'text': o[1]} for o in obj[0]]
908 })
916 })
909
917
910 data = {
918 data = {
911 'more': False,
919 'more': False,
912 'results': res
920 'results': res
913 }
921 }
914 return data
922 return data
915
923
916 log.warning('Cannot fetch history for directory')
924 log.warning('Cannot fetch history for directory')
917 raise HTTPBadRequest()
925 raise HTTPBadRequest()
918
926
919 @LoginRequired()
927 @LoginRequired()
920 @HasRepoPermissionAnyDecorator(
928 @HasRepoPermissionAnyDecorator(
921 'repository.read', 'repository.write', 'repository.admin')
929 'repository.read', 'repository.write', 'repository.admin')
922 @view_config(
930 @view_config(
923 route_name='repo_file_authors', request_method='GET',
931 route_name='repo_file_authors', request_method='GET',
924 renderer='rhodecode:templates/files/file_authors_box.mako')
932 renderer='rhodecode:templates/files/file_authors_box.mako')
925 def repo_file_authors(self):
933 def repo_file_authors(self):
926 c = self.load_default_context()
934 c = self.load_default_context()
927
935
928 commit_id, f_path = self._get_commit_and_path()
936 commit_id, f_path = self._get_commit_and_path()
929 commit = self._get_commit_or_redirect(commit_id)
937 commit = self._get_commit_or_redirect(commit_id)
930 file_node = self._get_filenode_or_redirect(commit, f_path)
938 file_node = self._get_filenode_or_redirect(commit, f_path)
931
939
932 if not file_node.is_file():
940 if not file_node.is_file():
933 raise HTTPBadRequest()
941 raise HTTPBadRequest()
934
942
935 c.file_last_commit = file_node.last_commit
943 c.file_last_commit = file_node.last_commit
936 if self.request.GET.get('annotate') == '1':
944 if self.request.GET.get('annotate') == '1':
937 # use _hist from annotation if annotation mode is on
945 # use _hist from annotation if annotation mode is on
938 commit_ids = set(x[1] for x in file_node.annotate)
946 commit_ids = set(x[1] for x in file_node.annotate)
939 _hist = (
947 _hist = (
940 self.rhodecode_vcs_repo.get_commit(commit_id)
948 self.rhodecode_vcs_repo.get_commit(commit_id)
941 for commit_id in commit_ids)
949 for commit_id in commit_ids)
942 else:
950 else:
943 _f_history, _hist = self._get_node_history(commit, f_path)
951 _f_history, _hist = self._get_node_history(commit, f_path)
944 c.file_author = False
952 c.file_author = False
945
953
946 unique = collections.OrderedDict()
954 unique = collections.OrderedDict()
947 for commit in _hist:
955 for commit in _hist:
948 author = commit.author
956 author = commit.author
949 if author not in unique:
957 if author not in unique:
950 unique[commit.author] = [
958 unique[commit.author] = [
951 h.email(author),
959 h.email(author),
952 h.person(author, 'username_or_name_or_email'),
960 h.person(author, 'username_or_name_or_email'),
953 1 # counter
961 1 # counter
954 ]
962 ]
955
963
956 else:
964 else:
957 # increase counter
965 # increase counter
958 unique[commit.author][2] += 1
966 unique[commit.author][2] += 1
959
967
960 c.authors = [val for val in unique.values()]
968 c.authors = [val for val in unique.values()]
961
969
962 return self._get_template_context(c)
970 return self._get_template_context(c)
963
971
964 @LoginRequired()
972 @LoginRequired()
965 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
973 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
966 @view_config(
974 @view_config(
967 route_name='repo_files_remove_file', request_method='GET',
975 route_name='repo_files_remove_file', request_method='GET',
968 renderer='rhodecode:templates/files/files_delete.mako')
976 renderer='rhodecode:templates/files/files_delete.mako')
969 def repo_files_remove_file(self):
977 def repo_files_remove_file(self):
970 _ = self.request.translate
978 _ = self.request.translate
971 c = self.load_default_context()
979 c = self.load_default_context()
972 commit_id, f_path = self._get_commit_and_path()
980 commit_id, f_path = self._get_commit_and_path()
973
981
974 self._ensure_not_locked()
982 self._ensure_not_locked()
975
983
976 if not self._is_valid_head(commit_id, self.rhodecode_vcs_repo):
984 if not self._is_valid_head(commit_id, self.rhodecode_vcs_repo):
977 h.flash(_('You can only delete files with commit '
985 h.flash(_('You can only delete files with commit '
978 'being a valid branch '), category='warning')
986 'being a valid branch '), category='warning')
979 raise HTTPFound(
987 raise HTTPFound(
980 h.route_path('repo_files',
988 h.route_path('repo_files',
981 repo_name=self.db_repo_name, commit_id='tip',
989 repo_name=self.db_repo_name, commit_id='tip',
982 f_path=f_path))
990 f_path=f_path))
983
991
984 c.commit = self._get_commit_or_redirect(commit_id)
992 c.commit = self._get_commit_or_redirect(commit_id)
985 c.file = self._get_filenode_or_redirect(c.commit, f_path)
993 c.file = self._get_filenode_or_redirect(c.commit, f_path)
986
994
987 c.default_message = _(
995 c.default_message = _(
988 'Deleted file {} via RhodeCode Enterprise').format(f_path)
996 'Deleted file {} via RhodeCode Enterprise').format(f_path)
989 c.f_path = f_path
997 c.f_path = f_path
990
998
991 return self._get_template_context(c)
999 return self._get_template_context(c)
992
1000
993 @LoginRequired()
1001 @LoginRequired()
994 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1002 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
995 @CSRFRequired()
1003 @CSRFRequired()
996 @view_config(
1004 @view_config(
997 route_name='repo_files_delete_file', request_method='POST',
1005 route_name='repo_files_delete_file', request_method='POST',
998 renderer=None)
1006 renderer=None)
999 def repo_files_delete_file(self):
1007 def repo_files_delete_file(self):
1000 _ = self.request.translate
1008 _ = self.request.translate
1001
1009
1002 c = self.load_default_context()
1010 c = self.load_default_context()
1003 commit_id, f_path = self._get_commit_and_path()
1011 commit_id, f_path = self._get_commit_and_path()
1004
1012
1005 self._ensure_not_locked()
1013 self._ensure_not_locked()
1006
1014
1007 if not self._is_valid_head(commit_id, self.rhodecode_vcs_repo):
1015 if not self._is_valid_head(commit_id, self.rhodecode_vcs_repo):
1008 h.flash(_('You can only delete files with commit '
1016 h.flash(_('You can only delete files with commit '
1009 'being a valid branch '), category='warning')
1017 'being a valid branch '), category='warning')
1010 raise HTTPFound(
1018 raise HTTPFound(
1011 h.route_path('repo_files',
1019 h.route_path('repo_files',
1012 repo_name=self.db_repo_name, commit_id='tip',
1020 repo_name=self.db_repo_name, commit_id='tip',
1013 f_path=f_path))
1021 f_path=f_path))
1014
1022
1015 c.commit = self._get_commit_or_redirect(commit_id)
1023 c.commit = self._get_commit_or_redirect(commit_id)
1016 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1024 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1017
1025
1018 c.default_message = _(
1026 c.default_message = _(
1019 'Deleted file {} via RhodeCode Enterprise').format(f_path)
1027 'Deleted file {} via RhodeCode Enterprise').format(f_path)
1020 c.f_path = f_path
1028 c.f_path = f_path
1021 node_path = f_path
1029 node_path = f_path
1022 author = self._rhodecode_db_user.full_contact
1030 author = self._rhodecode_db_user.full_contact
1023 message = self.request.POST.get('message') or c.default_message
1031 message = self.request.POST.get('message') or c.default_message
1024 try:
1032 try:
1025 nodes = {
1033 nodes = {
1026 node_path: {
1034 node_path: {
1027 'content': ''
1035 'content': ''
1028 }
1036 }
1029 }
1037 }
1030 ScmModel().delete_nodes(
1038 ScmModel().delete_nodes(
1031 user=self._rhodecode_db_user.user_id, repo=self.db_repo,
1039 user=self._rhodecode_db_user.user_id, repo=self.db_repo,
1032 message=message,
1040 message=message,
1033 nodes=nodes,
1041 nodes=nodes,
1034 parent_commit=c.commit,
1042 parent_commit=c.commit,
1035 author=author,
1043 author=author,
1036 )
1044 )
1037
1045
1038 h.flash(
1046 h.flash(
1039 _('Successfully deleted file `{}`').format(
1047 _('Successfully deleted file `{}`').format(
1040 h.escape(f_path)), category='success')
1048 h.escape(f_path)), category='success')
1041 except Exception:
1049 except Exception:
1042 log.exception('Error during commit operation')
1050 log.exception('Error during commit operation')
1043 h.flash(_('Error occurred during commit'), category='error')
1051 h.flash(_('Error occurred during commit'), category='error')
1044 raise HTTPFound(
1052 raise HTTPFound(
1045 h.route_path('repo_commit', repo_name=self.db_repo_name,
1053 h.route_path('repo_commit', repo_name=self.db_repo_name,
1046 commit_id='tip'))
1054 commit_id='tip'))
1047
1055
1048 @LoginRequired()
1056 @LoginRequired()
1049 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1057 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1050 @view_config(
1058 @view_config(
1051 route_name='repo_files_edit_file', request_method='GET',
1059 route_name='repo_files_edit_file', request_method='GET',
1052 renderer='rhodecode:templates/files/files_edit.mako')
1060 renderer='rhodecode:templates/files/files_edit.mako')
1053 def repo_files_edit_file(self):
1061 def repo_files_edit_file(self):
1054 _ = self.request.translate
1062 _ = self.request.translate
1055 c = self.load_default_context()
1063 c = self.load_default_context()
1056 commit_id, f_path = self._get_commit_and_path()
1064 commit_id, f_path = self._get_commit_and_path()
1057
1065
1058 self._ensure_not_locked()
1066 self._ensure_not_locked()
1059
1067
1060 if not self._is_valid_head(commit_id, self.rhodecode_vcs_repo):
1068 if not self._is_valid_head(commit_id, self.rhodecode_vcs_repo):
1061 h.flash(_('You can only edit files with commit '
1069 h.flash(_('You can only edit files with commit '
1062 'being a valid branch '), category='warning')
1070 'being a valid branch '), category='warning')
1063 raise HTTPFound(
1071 raise HTTPFound(
1064 h.route_path('repo_files',
1072 h.route_path('repo_files',
1065 repo_name=self.db_repo_name, commit_id='tip',
1073 repo_name=self.db_repo_name, commit_id='tip',
1066 f_path=f_path))
1074 f_path=f_path))
1067
1075
1068 c.commit = self._get_commit_or_redirect(commit_id)
1076 c.commit = self._get_commit_or_redirect(commit_id)
1069 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1077 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1070
1078
1071 if c.file.is_binary:
1079 if c.file.is_binary:
1072 files_url = h.route_path(
1080 files_url = h.route_path(
1073 'repo_files',
1081 'repo_files',
1074 repo_name=self.db_repo_name,
1082 repo_name=self.db_repo_name,
1075 commit_id=c.commit.raw_id, f_path=f_path)
1083 commit_id=c.commit.raw_id, f_path=f_path)
1076 raise HTTPFound(files_url)
1084 raise HTTPFound(files_url)
1077
1085
1078 c.default_message = _(
1086 c.default_message = _(
1079 'Edited file {} via RhodeCode Enterprise').format(f_path)
1087 'Edited file {} via RhodeCode Enterprise').format(f_path)
1080 c.f_path = f_path
1088 c.f_path = f_path
1081
1089
1082 return self._get_template_context(c)
1090 return self._get_template_context(c)
1083
1091
1084 @LoginRequired()
1092 @LoginRequired()
1085 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1093 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1086 @CSRFRequired()
1094 @CSRFRequired()
1087 @view_config(
1095 @view_config(
1088 route_name='repo_files_update_file', request_method='POST',
1096 route_name='repo_files_update_file', request_method='POST',
1089 renderer=None)
1097 renderer=None)
1090 def repo_files_update_file(self):
1098 def repo_files_update_file(self):
1091 _ = self.request.translate
1099 _ = self.request.translate
1092 c = self.load_default_context()
1100 c = self.load_default_context()
1093 commit_id, f_path = self._get_commit_and_path()
1101 commit_id, f_path = self._get_commit_and_path()
1094
1102
1095 self._ensure_not_locked()
1103 self._ensure_not_locked()
1096
1104
1097 if not self._is_valid_head(commit_id, self.rhodecode_vcs_repo):
1105 if not self._is_valid_head(commit_id, self.rhodecode_vcs_repo):
1098 h.flash(_('You can only edit files with commit '
1106 h.flash(_('You can only edit files with commit '
1099 'being a valid branch '), category='warning')
1107 'being a valid branch '), category='warning')
1100 raise HTTPFound(
1108 raise HTTPFound(
1101 h.route_path('repo_files',
1109 h.route_path('repo_files',
1102 repo_name=self.db_repo_name, commit_id='tip',
1110 repo_name=self.db_repo_name, commit_id='tip',
1103 f_path=f_path))
1111 f_path=f_path))
1104
1112
1105 c.commit = self._get_commit_or_redirect(commit_id)
1113 c.commit = self._get_commit_or_redirect(commit_id)
1106 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1114 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1107
1115
1108 if c.file.is_binary:
1116 if c.file.is_binary:
1109 raise HTTPFound(
1117 raise HTTPFound(
1110 h.route_path('repo_files',
1118 h.route_path('repo_files',
1111 repo_name=self.db_repo_name,
1119 repo_name=self.db_repo_name,
1112 commit_id=c.commit.raw_id,
1120 commit_id=c.commit.raw_id,
1113 f_path=f_path))
1121 f_path=f_path))
1114
1122
1115 c.default_message = _(
1123 c.default_message = _(
1116 'Edited file {} via RhodeCode Enterprise').format(f_path)
1124 'Edited file {} via RhodeCode Enterprise').format(f_path)
1117 c.f_path = f_path
1125 c.f_path = f_path
1118 old_content = c.file.content
1126 old_content = c.file.content
1119 sl = old_content.splitlines(1)
1127 sl = old_content.splitlines(1)
1120 first_line = sl[0] if sl else ''
1128 first_line = sl[0] if sl else ''
1121
1129
1122 r_post = self.request.POST
1130 r_post = self.request.POST
1123 # modes: 0 - Unix, 1 - Mac, 2 - DOS
1131 # modes: 0 - Unix, 1 - Mac, 2 - DOS
1124 mode = detect_mode(first_line, 0)
1132 mode = detect_mode(first_line, 0)
1125 content = convert_line_endings(r_post.get('content', ''), mode)
1133 content = convert_line_endings(r_post.get('content', ''), mode)
1126
1134
1127 message = r_post.get('message') or c.default_message
1135 message = r_post.get('message') or c.default_message
1128 org_f_path = c.file.unicode_path
1136 org_f_path = c.file.unicode_path
1129 filename = r_post['filename']
1137 filename = r_post['filename']
1130 org_filename = c.file.name
1138 org_filename = c.file.name
1131
1139
1132 if content == old_content and filename == org_filename:
1140 if content == old_content and filename == org_filename:
1133 h.flash(_('No changes'), category='warning')
1141 h.flash(_('No changes'), category='warning')
1134 raise HTTPFound(
1142 raise HTTPFound(
1135 h.route_path('repo_commit', repo_name=self.db_repo_name,
1143 h.route_path('repo_commit', repo_name=self.db_repo_name,
1136 commit_id='tip'))
1144 commit_id='tip'))
1137 try:
1145 try:
1138 mapping = {
1146 mapping = {
1139 org_f_path: {
1147 org_f_path: {
1140 'org_filename': org_f_path,
1148 'org_filename': org_f_path,
1141 'filename': os.path.join(c.file.dir_path, filename),
1149 'filename': os.path.join(c.file.dir_path, filename),
1142 'content': content,
1150 'content': content,
1143 'lexer': '',
1151 'lexer': '',
1144 'op': 'mod',
1152 'op': 'mod',
1145 }
1153 }
1146 }
1154 }
1147
1155
1148 ScmModel().update_nodes(
1156 ScmModel().update_nodes(
1149 user=self._rhodecode_db_user.user_id,
1157 user=self._rhodecode_db_user.user_id,
1150 repo=self.db_repo,
1158 repo=self.db_repo,
1151 message=message,
1159 message=message,
1152 nodes=mapping,
1160 nodes=mapping,
1153 parent_commit=c.commit,
1161 parent_commit=c.commit,
1154 )
1162 )
1155
1163
1156 h.flash(
1164 h.flash(
1157 _('Successfully committed changes to file `{}`').format(
1165 _('Successfully committed changes to file `{}`').format(
1158 h.escape(f_path)), category='success')
1166 h.escape(f_path)), category='success')
1159 except Exception:
1167 except Exception:
1160 log.exception('Error occurred during commit')
1168 log.exception('Error occurred during commit')
1161 h.flash(_('Error occurred during commit'), category='error')
1169 h.flash(_('Error occurred during commit'), category='error')
1162 raise HTTPFound(
1170 raise HTTPFound(
1163 h.route_path('repo_commit', repo_name=self.db_repo_name,
1171 h.route_path('repo_commit', repo_name=self.db_repo_name,
1164 commit_id='tip'))
1172 commit_id='tip'))
1165
1173
1166 @LoginRequired()
1174 @LoginRequired()
1167 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1175 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1168 @view_config(
1176 @view_config(
1169 route_name='repo_files_add_file', request_method='GET',
1177 route_name='repo_files_add_file', request_method='GET',
1170 renderer='rhodecode:templates/files/files_add.mako')
1178 renderer='rhodecode:templates/files/files_add.mako')
1171 def repo_files_add_file(self):
1179 def repo_files_add_file(self):
1172 _ = self.request.translate
1180 _ = self.request.translate
1173 c = self.load_default_context()
1181 c = self.load_default_context()
1174 commit_id, f_path = self._get_commit_and_path()
1182 commit_id, f_path = self._get_commit_and_path()
1175
1183
1176 self._ensure_not_locked()
1184 self._ensure_not_locked()
1177
1185
1178 c.commit = self._get_commit_or_redirect(commit_id, redirect_after=False)
1186 c.commit = self._get_commit_or_redirect(commit_id, redirect_after=False)
1179 if c.commit is None:
1187 if c.commit is None:
1180 c.commit = EmptyCommit(alias=self.rhodecode_vcs_repo.alias)
1188 c.commit = EmptyCommit(alias=self.rhodecode_vcs_repo.alias)
1181 c.default_message = (_('Added file via RhodeCode Enterprise'))
1189 c.default_message = (_('Added file via RhodeCode Enterprise'))
1182 c.f_path = f_path.lstrip('/') # ensure not relative path
1190 c.f_path = f_path.lstrip('/') # ensure not relative path
1183
1191
1184 return self._get_template_context(c)
1192 return self._get_template_context(c)
1185
1193
1186 @LoginRequired()
1194 @LoginRequired()
1187 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1195 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1188 @CSRFRequired()
1196 @CSRFRequired()
1189 @view_config(
1197 @view_config(
1190 route_name='repo_files_create_file', request_method='POST',
1198 route_name='repo_files_create_file', request_method='POST',
1191 renderer=None)
1199 renderer=None)
1192 def repo_files_create_file(self):
1200 def repo_files_create_file(self):
1193 _ = self.request.translate
1201 _ = self.request.translate
1194 c = self.load_default_context()
1202 c = self.load_default_context()
1195 commit_id, f_path = self._get_commit_and_path()
1203 commit_id, f_path = self._get_commit_and_path()
1196
1204
1197 self._ensure_not_locked()
1205 self._ensure_not_locked()
1198
1206
1199 r_post = self.request.POST
1207 r_post = self.request.POST
1200
1208
1201 c.commit = self._get_commit_or_redirect(
1209 c.commit = self._get_commit_or_redirect(
1202 commit_id, redirect_after=False)
1210 commit_id, redirect_after=False)
1203 if c.commit is None:
1211 if c.commit is None:
1204 c.commit = EmptyCommit(alias=self.rhodecode_vcs_repo.alias)
1212 c.commit = EmptyCommit(alias=self.rhodecode_vcs_repo.alias)
1205 c.default_message = (_('Added file via RhodeCode Enterprise'))
1213 c.default_message = (_('Added file via RhodeCode Enterprise'))
1206 c.f_path = f_path
1214 c.f_path = f_path
1207 unix_mode = 0
1215 unix_mode = 0
1208 content = convert_line_endings(r_post.get('content', ''), unix_mode)
1216 content = convert_line_endings(r_post.get('content', ''), unix_mode)
1209
1217
1210 message = r_post.get('message') or c.default_message
1218 message = r_post.get('message') or c.default_message
1211 filename = r_post.get('filename')
1219 filename = r_post.get('filename')
1212 location = r_post.get('location', '') # dir location
1220 location = r_post.get('location', '') # dir location
1213 file_obj = r_post.get('upload_file', None)
1221 file_obj = r_post.get('upload_file', None)
1214
1222
1215 if file_obj is not None and hasattr(file_obj, 'filename'):
1223 if file_obj is not None and hasattr(file_obj, 'filename'):
1216 filename = r_post.get('filename_upload')
1224 filename = r_post.get('filename_upload')
1217 content = file_obj.file
1225 content = file_obj.file
1218
1226
1219 if hasattr(content, 'file'):
1227 if hasattr(content, 'file'):
1220 # non posix systems store real file under file attr
1228 # non posix systems store real file under file attr
1221 content = content.file
1229 content = content.file
1222
1230
1223 if self.rhodecode_vcs_repo.is_empty:
1231 if self.rhodecode_vcs_repo.is_empty:
1224 default_redirect_url = h.route_path(
1232 default_redirect_url = h.route_path(
1225 'repo_summary', repo_name=self.db_repo_name)
1233 'repo_summary', repo_name=self.db_repo_name)
1226 else:
1234 else:
1227 default_redirect_url = h.route_path(
1235 default_redirect_url = h.route_path(
1228 'repo_commit', repo_name=self.db_repo_name, commit_id='tip')
1236 'repo_commit', repo_name=self.db_repo_name, commit_id='tip')
1229
1237
1230 # If there's no commit, redirect to repo summary
1238 # If there's no commit, redirect to repo summary
1231 if type(c.commit) is EmptyCommit:
1239 if type(c.commit) is EmptyCommit:
1232 redirect_url = h.route_path(
1240 redirect_url = h.route_path(
1233 'repo_summary', repo_name=self.db_repo_name)
1241 'repo_summary', repo_name=self.db_repo_name)
1234 else:
1242 else:
1235 redirect_url = default_redirect_url
1243 redirect_url = default_redirect_url
1236
1244
1237 if not filename:
1245 if not filename:
1238 h.flash(_('No filename'), category='warning')
1246 h.flash(_('No filename'), category='warning')
1239 raise HTTPFound(redirect_url)
1247 raise HTTPFound(redirect_url)
1240
1248
1241 # extract the location from filename,
1249 # extract the location from filename,
1242 # allows using foo/bar.txt syntax to create subdirectories
1250 # allows using foo/bar.txt syntax to create subdirectories
1243 subdir_loc = filename.rsplit('/', 1)
1251 subdir_loc = filename.rsplit('/', 1)
1244 if len(subdir_loc) == 2:
1252 if len(subdir_loc) == 2:
1245 location = os.path.join(location, subdir_loc[0])
1253 location = os.path.join(location, subdir_loc[0])
1246
1254
1247 # strip all crap out of file, just leave the basename
1255 # strip all crap out of file, just leave the basename
1248 filename = os.path.basename(filename)
1256 filename = os.path.basename(filename)
1249 node_path = os.path.join(location, filename)
1257 node_path = os.path.join(location, filename)
1250 author = self._rhodecode_db_user.full_contact
1258 author = self._rhodecode_db_user.full_contact
1251
1259
1252 try:
1260 try:
1253 nodes = {
1261 nodes = {
1254 node_path: {
1262 node_path: {
1255 'content': content
1263 'content': content
1256 }
1264 }
1257 }
1265 }
1258 ScmModel().create_nodes(
1266 ScmModel().create_nodes(
1259 user=self._rhodecode_db_user.user_id,
1267 user=self._rhodecode_db_user.user_id,
1260 repo=self.db_repo,
1268 repo=self.db_repo,
1261 message=message,
1269 message=message,
1262 nodes=nodes,
1270 nodes=nodes,
1263 parent_commit=c.commit,
1271 parent_commit=c.commit,
1264 author=author,
1272 author=author,
1265 )
1273 )
1266
1274
1267 h.flash(
1275 h.flash(
1268 _('Successfully committed new file `{}`').format(
1276 _('Successfully committed new file `{}`').format(
1269 h.escape(node_path)), category='success')
1277 h.escape(node_path)), category='success')
1270 except NonRelativePathError:
1278 except NonRelativePathError:
1271 log.exception('Non Relative path found')
1279 log.exception('Non Relative path found')
1272 h.flash(_(
1280 h.flash(_(
1273 'The location specified must be a relative path and must not '
1281 'The location specified must be a relative path and must not '
1274 'contain .. in the path'), category='warning')
1282 'contain .. in the path'), category='warning')
1275 raise HTTPFound(default_redirect_url)
1283 raise HTTPFound(default_redirect_url)
1276 except (NodeError, NodeAlreadyExistsError) as e:
1284 except (NodeError, NodeAlreadyExistsError) as e:
1277 h.flash(_(h.escape(e)), category='error')
1285 h.flash(_(h.escape(e)), category='error')
1278 except Exception:
1286 except Exception:
1279 log.exception('Error occurred during commit')
1287 log.exception('Error occurred during commit')
1280 h.flash(_('Error occurred during commit'), category='error')
1288 h.flash(_('Error occurred during commit'), category='error')
1281
1289
1282 raise HTTPFound(default_redirect_url)
1290 raise HTTPFound(default_redirect_url)
General Comments 0
You need to be logged in to leave comments. Login now