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