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