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