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