##// END OF EJS Templates
fix(app): Adjusted accordingly to PR suggestions. Fixes: RCCE-37
ilin.s -
r5270:53acf5d4 default
parent child Browse files
Show More
@@ -1,1586 +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 headers = "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
814
815 return headers.encode().decode('iso-8859-1', errors='replace')
815 return safe_bytes(headers).decode('latin-1', errors='replace')
816
816
817 @LoginRequired()
817 @LoginRequired()
818 @HasRepoPermissionAnyDecorator(
818 @HasRepoPermissionAnyDecorator(
819 'repository.read', 'repository.write', 'repository.admin')
819 'repository.read', 'repository.write', 'repository.admin')
820 def repo_file_raw(self):
820 def repo_file_raw(self):
821 """
821 """
822 Action for show as raw, some mimetypes are "rendered",
822 Action for show as raw, some mimetypes are "rendered",
823 those include images, icons.
823 those include images, icons.
824 """
824 """
825 c = self.load_default_context()
825 c = self.load_default_context()
826
826
827 commit_id, f_path = self._get_commit_and_path()
827 commit_id, f_path = self._get_commit_and_path()
828 commit = self._get_commit_or_redirect(commit_id)
828 commit = self._get_commit_or_redirect(commit_id)
829 file_node = self._get_filenode_or_redirect(commit, f_path)
829 file_node = self._get_filenode_or_redirect(commit, f_path)
830
830
831 raw_mimetype_mapping = {
831 raw_mimetype_mapping = {
832 # map original mimetype to a mimetype used for "show as raw"
832 # map original mimetype to a mimetype used for "show as raw"
833 # you can also provide a content-disposition to override the
833 # you can also provide a content-disposition to override the
834 # default "attachment" disposition.
834 # default "attachment" disposition.
835 # orig_type: (new_type, new_dispo)
835 # orig_type: (new_type, new_dispo)
836
836
837 # show images inline:
837 # show images inline:
838 # 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
839 # for example render an SVG with javascript inside or even render
839 # for example render an SVG with javascript inside or even render
840 # HTML.
840 # HTML.
841 'image/x-icon': ('image/x-icon', 'inline'),
841 'image/x-icon': ('image/x-icon', 'inline'),
842 'image/png': ('image/png', 'inline'),
842 'image/png': ('image/png', 'inline'),
843 'image/gif': ('image/gif', 'inline'),
843 'image/gif': ('image/gif', 'inline'),
844 'image/jpeg': ('image/jpeg', 'inline'),
844 'image/jpeg': ('image/jpeg', 'inline'),
845 'application/pdf': ('application/pdf', 'inline'),
845 'application/pdf': ('application/pdf', 'inline'),
846 }
846 }
847
847
848 mimetype = file_node.mimetype
848 mimetype = file_node.mimetype
849 try:
849 try:
850 mimetype, disposition = raw_mimetype_mapping[mimetype]
850 mimetype, disposition = raw_mimetype_mapping[mimetype]
851 except KeyError:
851 except KeyError:
852 # we don't know anything special about this, handle it safely
852 # we don't know anything special about this, handle it safely
853 if file_node.is_binary:
853 if file_node.is_binary:
854 # do same as download raw for binary files
854 # do same as download raw for binary files
855 mimetype, disposition = 'application/octet-stream', 'attachment'
855 mimetype, disposition = 'application/octet-stream', 'attachment'
856 else:
856 else:
857 # do not just use the original mimetype, but force text/plain,
857 # do not just use the original mimetype, but force text/plain,
858 # otherwise it would serve text/html and that might be unsafe.
858 # otherwise it would serve text/html and that might be unsafe.
859 # Note: underlying vcs library fakes text/plain mimetype if the
859 # Note: underlying vcs library fakes text/plain mimetype if the
860 # mimetype can not be determined and it thinks it is not
860 # mimetype can not be determined and it thinks it is not
861 # binary.This might lead to erroneous text display in some
861 # binary.This might lead to erroneous text display in some
862 # cases, but helps in other cases, like with text files
862 # cases, but helps in other cases, like with text files
863 # without extension.
863 # without extension.
864 mimetype, disposition = 'text/plain', 'inline'
864 mimetype, disposition = 'text/plain', 'inline'
865
865
866 if disposition == 'attachment':
866 if disposition == 'attachment':
867 disposition = self._get_attachement_headers(f_path)
867 disposition = self._get_attachement_headers(f_path)
868
868
869 stream_content = file_node.stream_bytes()
869 stream_content = file_node.stream_bytes()
870
870
871 response = Response(app_iter=stream_content)
871 response = Response(app_iter=stream_content)
872 response.content_disposition = disposition
872 response.content_disposition = disposition
873 response.content_type = mimetype
873 response.content_type = mimetype
874
874
875 charset = self._get_default_encoding(c)
875 charset = self._get_default_encoding(c)
876 if charset:
876 if charset:
877 response.charset = charset
877 response.charset = charset
878
878
879 return response
879 return response
880
880
881 @LoginRequired()
881 @LoginRequired()
882 @HasRepoPermissionAnyDecorator(
882 @HasRepoPermissionAnyDecorator(
883 'repository.read', 'repository.write', 'repository.admin')
883 'repository.read', 'repository.write', 'repository.admin')
884 def repo_file_download(self):
884 def repo_file_download(self):
885 c = self.load_default_context()
885 c = self.load_default_context()
886
886
887 commit_id, f_path = self._get_commit_and_path()
887 commit_id, f_path = self._get_commit_and_path()
888 commit = self._get_commit_or_redirect(commit_id)
888 commit = self._get_commit_or_redirect(commit_id)
889 file_node = self._get_filenode_or_redirect(commit, f_path)
889 file_node = self._get_filenode_or_redirect(commit, f_path)
890
890
891 if self.request.GET.get('lf'):
891 if self.request.GET.get('lf'):
892 # only if lf get flag is passed, we download this file
892 # only if lf get flag is passed, we download this file
893 # as LFS/Largefile
893 # as LFS/Largefile
894 lf_node = file_node.get_largefile_node()
894 lf_node = file_node.get_largefile_node()
895 if lf_node:
895 if lf_node:
896 # overwrite our pointer with the REAL large-file
896 # overwrite our pointer with the REAL large-file
897 file_node = lf_node
897 file_node = lf_node
898
898
899 disposition = self._get_attachement_headers(f_path)
899 disposition = self._get_attachement_headers(f_path)
900
900
901 stream_content = file_node.stream_bytes()
901 stream_content = file_node.stream_bytes()
902
902
903 response = Response(app_iter=stream_content)
903 response = Response(app_iter=stream_content)
904 response.content_disposition = disposition
904 response.content_disposition = disposition
905 response.content_type = file_node.mimetype
905 response.content_type = file_node.mimetype
906
906
907 charset = self._get_default_encoding(c)
907 charset = self._get_default_encoding(c)
908 if charset:
908 if charset:
909 response.charset = charset
909 response.charset = charset
910
910
911 return response
911 return response
912
912
913 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):
914
914
915 cache_seconds = safe_int(
915 cache_seconds = safe_int(
916 rhodecode.CONFIG.get('rc_cache.cache_repo.expiration_time'))
916 rhodecode.CONFIG.get('rc_cache.cache_repo.expiration_time'))
917 cache_on = cache_seconds > 0
917 cache_on = cache_seconds > 0
918 log.debug(
918 log.debug(
919 '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`'
920 'with caching: %s[TTL: %ss]' % (
920 'with caching: %s[TTL: %ss]' % (
921 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))
922
922
923 cache_namespace_uid = f'repo.{repo_id}'
923 cache_namespace_uid = f'repo.{repo_id}'
924 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)
925
925
926 @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)
927 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):
928 log.debug('Generating cached nodelist for repo_id:%s, %s, %s',
928 log.debug('Generating cached nodelist for repo_id:%s, %s, %s',
929 _repo_id, commit_id, f_path)
929 _repo_id, commit_id, f_path)
930 try:
930 try:
931 _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)
932 except (RepositoryError, CommitDoesNotExistError, Exception) as e:
932 except (RepositoryError, CommitDoesNotExistError, Exception) as e:
933 log.exception(safe_str(e))
933 log.exception(safe_str(e))
934 h.flash(h.escape(safe_str(e)), category='error')
934 h.flash(h.escape(safe_str(e)), category='error')
935 raise HTTPFound(h.route_path(
935 raise HTTPFound(h.route_path(
936 'repo_files', repo_name=self.db_repo_name,
936 'repo_files', repo_name=self.db_repo_name,
937 commit_id='tip', f_path='/'))
937 commit_id='tip', f_path='/'))
938
938
939 return _d + _f
939 return _d + _f
940
940
941 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,
942 commit_id, f_path)
942 commit_id, f_path)
943 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)
944
944
945 @LoginRequired()
945 @LoginRequired()
946 @HasRepoPermissionAnyDecorator(
946 @HasRepoPermissionAnyDecorator(
947 'repository.read', 'repository.write', 'repository.admin')
947 'repository.read', 'repository.write', 'repository.admin')
948 def repo_nodelist(self):
948 def repo_nodelist(self):
949 self.load_default_context()
949 self.load_default_context()
950
950
951 commit_id, f_path = self._get_commit_and_path()
951 commit_id, f_path = self._get_commit_and_path()
952 commit = self._get_commit_or_redirect(commit_id)
952 commit = self._get_commit_or_redirect(commit_id)
953
953
954 metadata = self._get_nodelist_at_commit(
954 metadata = self._get_nodelist_at_commit(
955 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)
956 return {'nodes': [x for x in metadata]}
956 return {'nodes': [x for x in metadata]}
957
957
958 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):
959 items = []
959 items = []
960 for name, commit_id in branches_or_tags.items():
960 for name, commit_id in branches_or_tags.items():
961 sym_ref = symbolic_reference(commit_id, name, f_path, ref_type)
961 sym_ref = symbolic_reference(commit_id, name, f_path, ref_type)
962 items.append((sym_ref, name, ref_type))
962 items.append((sym_ref, name, ref_type))
963 return items
963 return items
964
964
965 def _symbolic_reference(self, commit_id, name, f_path, ref_type):
965 def _symbolic_reference(self, commit_id, name, f_path, ref_type):
966 return commit_id
966 return commit_id
967
967
968 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):
969 return commit_id
969 return commit_id
970
970
971 # NOTE(dan): old code we used in "diff" mode compare
971 # NOTE(dan): old code we used in "diff" mode compare
972 new_f_path = vcspath.join(name, f_path)
972 new_f_path = vcspath.join(name, f_path)
973 return f'{new_f_path}@{commit_id}'
973 return f'{new_f_path}@{commit_id}'
974
974
975 def _get_node_history(self, commit_obj, f_path, commits=None):
975 def _get_node_history(self, commit_obj, f_path, commits=None):
976 """
976 """
977 get commit history for given node
977 get commit history for given node
978
978
979 :param commit_obj: commit to calculate history
979 :param commit_obj: commit to calculate history
980 :param f_path: path for node to calculate history for
980 :param f_path: path for node to calculate history for
981 :param commits: if passed don't calculate history and take
981 :param commits: if passed don't calculate history and take
982 commits defined in this list
982 commits defined in this list
983 """
983 """
984 _ = self.request.translate
984 _ = self.request.translate
985
985
986 # calculate history based on tip
986 # calculate history based on tip
987 tip = self.rhodecode_vcs_repo.get_commit()
987 tip = self.rhodecode_vcs_repo.get_commit()
988 if commits is None:
988 if commits is None:
989 pre_load = ["author", "branch"]
989 pre_load = ["author", "branch"]
990 try:
990 try:
991 commits = tip.get_path_history(f_path, pre_load=pre_load)
991 commits = tip.get_path_history(f_path, pre_load=pre_load)
992 except (NodeDoesNotExistError, CommitError):
992 except (NodeDoesNotExistError, CommitError):
993 # this node is not present at tip!
993 # this node is not present at tip!
994 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)
995
995
996 history = []
996 history = []
997 commits_group = ([], _("Changesets"))
997 commits_group = ([], _("Changesets"))
998 for commit in commits:
998 for commit in commits:
999 branch = ' (%s)' % commit.branch if commit.branch else ''
999 branch = ' (%s)' % commit.branch if commit.branch else ''
1000 n_desc = f'r{commit.idx}:{commit.short_id}{branch}'
1000 n_desc = f'r{commit.idx}:{commit.short_id}{branch}'
1001 commits_group[0].append((commit.raw_id, n_desc, 'sha'))
1001 commits_group[0].append((commit.raw_id, n_desc, 'sha'))
1002 history.append(commits_group)
1002 history.append(commits_group)
1003
1003
1004 symbolic_reference = self._symbolic_reference
1004 symbolic_reference = self._symbolic_reference
1005
1005
1006 if self.rhodecode_vcs_repo.alias == 'svn':
1006 if self.rhodecode_vcs_repo.alias == 'svn':
1007 adjusted_f_path = RepoFilesView.adjust_file_path_for_svn(
1007 adjusted_f_path = RepoFilesView.adjust_file_path_for_svn(
1008 f_path, self.rhodecode_vcs_repo)
1008 f_path, self.rhodecode_vcs_repo)
1009 if adjusted_f_path != f_path:
1009 if adjusted_f_path != f_path:
1010 log.debug(
1010 log.debug(
1011 'Recognized svn tag or branch in file "%s", using svn '
1011 'Recognized svn tag or branch in file "%s", using svn '
1012 'specific symbolic references', f_path)
1012 'specific symbolic references', f_path)
1013 f_path = adjusted_f_path
1013 f_path = adjusted_f_path
1014 symbolic_reference = self._symbolic_reference_svn
1014 symbolic_reference = self._symbolic_reference_svn
1015
1015
1016 branches = self._create_references(
1016 branches = self._create_references(
1017 self.rhodecode_vcs_repo.branches, symbolic_reference, f_path, 'branch')
1017 self.rhodecode_vcs_repo.branches, symbolic_reference, f_path, 'branch')
1018 branches_group = (branches, _("Branches"))
1018 branches_group = (branches, _("Branches"))
1019
1019
1020 tags = self._create_references(
1020 tags = self._create_references(
1021 self.rhodecode_vcs_repo.tags, symbolic_reference, f_path, 'tag')
1021 self.rhodecode_vcs_repo.tags, symbolic_reference, f_path, 'tag')
1022 tags_group = (tags, _("Tags"))
1022 tags_group = (tags, _("Tags"))
1023
1023
1024 history.append(branches_group)
1024 history.append(branches_group)
1025 history.append(tags_group)
1025 history.append(tags_group)
1026
1026
1027 return history, commits
1027 return history, commits
1028
1028
1029 @LoginRequired()
1029 @LoginRequired()
1030 @HasRepoPermissionAnyDecorator(
1030 @HasRepoPermissionAnyDecorator(
1031 'repository.read', 'repository.write', 'repository.admin')
1031 'repository.read', 'repository.write', 'repository.admin')
1032 def repo_file_history(self):
1032 def repo_file_history(self):
1033 self.load_default_context()
1033 self.load_default_context()
1034
1034
1035 commit_id, f_path = self._get_commit_and_path()
1035 commit_id, f_path = self._get_commit_and_path()
1036 commit = self._get_commit_or_redirect(commit_id)
1036 commit = self._get_commit_or_redirect(commit_id)
1037 file_node = self._get_filenode_or_redirect(commit, f_path)
1037 file_node = self._get_filenode_or_redirect(commit, f_path)
1038
1038
1039 if file_node.is_file():
1039 if file_node.is_file():
1040 file_history, _hist = self._get_node_history(commit, f_path)
1040 file_history, _hist = self._get_node_history(commit, f_path)
1041
1041
1042 res = []
1042 res = []
1043 for section_items, section in file_history:
1043 for section_items, section in file_history:
1044 items = []
1044 items = []
1045 for obj_id, obj_text, obj_type in section_items:
1045 for obj_id, obj_text, obj_type in section_items:
1046 at_rev = ''
1046 at_rev = ''
1047 if obj_type in ['branch', 'bookmark', 'tag']:
1047 if obj_type in ['branch', 'bookmark', 'tag']:
1048 at_rev = obj_text
1048 at_rev = obj_text
1049 entry = {
1049 entry = {
1050 'id': obj_id,
1050 'id': obj_id,
1051 'text': obj_text,
1051 'text': obj_text,
1052 'type': obj_type,
1052 'type': obj_type,
1053 'at_rev': at_rev
1053 'at_rev': at_rev
1054 }
1054 }
1055
1055
1056 items.append(entry)
1056 items.append(entry)
1057
1057
1058 res.append({
1058 res.append({
1059 'text': section,
1059 'text': section,
1060 'children': items
1060 'children': items
1061 })
1061 })
1062
1062
1063 data = {
1063 data = {
1064 'more': False,
1064 'more': False,
1065 'results': res
1065 'results': res
1066 }
1066 }
1067 return data
1067 return data
1068
1068
1069 log.warning('Cannot fetch history for directory')
1069 log.warning('Cannot fetch history for directory')
1070 raise HTTPBadRequest()
1070 raise HTTPBadRequest()
1071
1071
1072 @LoginRequired()
1072 @LoginRequired()
1073 @HasRepoPermissionAnyDecorator(
1073 @HasRepoPermissionAnyDecorator(
1074 'repository.read', 'repository.write', 'repository.admin')
1074 'repository.read', 'repository.write', 'repository.admin')
1075 def repo_file_authors(self):
1075 def repo_file_authors(self):
1076 c = self.load_default_context()
1076 c = self.load_default_context()
1077
1077
1078 commit_id, f_path = self._get_commit_and_path()
1078 commit_id, f_path = self._get_commit_and_path()
1079 commit = self._get_commit_or_redirect(commit_id)
1079 commit = self._get_commit_or_redirect(commit_id)
1080 file_node = self._get_filenode_or_redirect(commit, f_path)
1080 file_node = self._get_filenode_or_redirect(commit, f_path)
1081
1081
1082 if not file_node.is_file():
1082 if not file_node.is_file():
1083 raise HTTPBadRequest()
1083 raise HTTPBadRequest()
1084
1084
1085 c.file_last_commit = file_node.last_commit
1085 c.file_last_commit = file_node.last_commit
1086 if self.request.GET.get('annotate') == '1':
1086 if self.request.GET.get('annotate') == '1':
1087 # use _hist from annotation if annotation mode is on
1087 # use _hist from annotation if annotation mode is on
1088 commit_ids = {x[1] for x in file_node.annotate}
1088 commit_ids = {x[1] for x in file_node.annotate}
1089 _hist = (
1089 _hist = (
1090 self.rhodecode_vcs_repo.get_commit(commit_id)
1090 self.rhodecode_vcs_repo.get_commit(commit_id)
1091 for commit_id in commit_ids)
1091 for commit_id in commit_ids)
1092 else:
1092 else:
1093 _f_history, _hist = self._get_node_history(commit, f_path)
1093 _f_history, _hist = self._get_node_history(commit, f_path)
1094 c.file_author = False
1094 c.file_author = False
1095
1095
1096 unique = collections.OrderedDict()
1096 unique = collections.OrderedDict()
1097 for commit in _hist:
1097 for commit in _hist:
1098 author = commit.author
1098 author = commit.author
1099 if author not in unique:
1099 if author not in unique:
1100 unique[commit.author] = [
1100 unique[commit.author] = [
1101 h.email(author),
1101 h.email(author),
1102 h.person(author, 'username_or_name_or_email'),
1102 h.person(author, 'username_or_name_or_email'),
1103 1 # counter
1103 1 # counter
1104 ]
1104 ]
1105
1105
1106 else:
1106 else:
1107 # increase counter
1107 # increase counter
1108 unique[commit.author][2] += 1
1108 unique[commit.author][2] += 1
1109
1109
1110 c.authors = [val for val in unique.values()]
1110 c.authors = [val for val in unique.values()]
1111
1111
1112 return self._get_template_context(c)
1112 return self._get_template_context(c)
1113
1113
1114 @LoginRequired()
1114 @LoginRequired()
1115 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1115 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1116 def repo_files_check_head(self):
1116 def repo_files_check_head(self):
1117 self.load_default_context()
1117 self.load_default_context()
1118
1118
1119 commit_id, f_path = self._get_commit_and_path()
1119 commit_id, f_path = self._get_commit_and_path()
1120 _branch_name, _sha_commit_id, is_head = \
1120 _branch_name, _sha_commit_id, is_head = \
1121 self._is_valid_head(commit_id, self.rhodecode_vcs_repo,
1121 self._is_valid_head(commit_id, self.rhodecode_vcs_repo,
1122 landing_ref=self.db_repo.landing_ref_name)
1122 landing_ref=self.db_repo.landing_ref_name)
1123
1123
1124 new_path = self.request.POST.get('path')
1124 new_path = self.request.POST.get('path')
1125 operation = self.request.POST.get('operation')
1125 operation = self.request.POST.get('operation')
1126 path_exist = ''
1126 path_exist = ''
1127
1127
1128 if new_path and operation in ['create', 'upload']:
1128 if new_path and operation in ['create', 'upload']:
1129 new_f_path = os.path.join(f_path.lstrip('/'), new_path)
1129 new_f_path = os.path.join(f_path.lstrip('/'), new_path)
1130 try:
1130 try:
1131 commit_obj = self.rhodecode_vcs_repo.get_commit(commit_id)
1131 commit_obj = self.rhodecode_vcs_repo.get_commit(commit_id)
1132 # NOTE(dan): construct whole path without leading /
1132 # NOTE(dan): construct whole path without leading /
1133 file_node = commit_obj.get_node(new_f_path)
1133 file_node = commit_obj.get_node(new_f_path)
1134 if file_node is not None:
1134 if file_node is not None:
1135 path_exist = new_f_path
1135 path_exist = new_f_path
1136 except EmptyRepositoryError:
1136 except EmptyRepositoryError:
1137 pass
1137 pass
1138 except Exception:
1138 except Exception:
1139 pass
1139 pass
1140
1140
1141 return {
1141 return {
1142 'branch': _branch_name,
1142 'branch': _branch_name,
1143 'sha': _sha_commit_id,
1143 'sha': _sha_commit_id,
1144 'is_head': is_head,
1144 'is_head': is_head,
1145 'path_exists': path_exist
1145 'path_exists': path_exist
1146 }
1146 }
1147
1147
1148 @LoginRequired()
1148 @LoginRequired()
1149 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1149 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1150 def repo_files_remove_file(self):
1150 def repo_files_remove_file(self):
1151 _ = self.request.translate
1151 _ = self.request.translate
1152 c = self.load_default_context()
1152 c = self.load_default_context()
1153 commit_id, f_path = self._get_commit_and_path()
1153 commit_id, f_path = self._get_commit_and_path()
1154
1154
1155 self._ensure_not_locked()
1155 self._ensure_not_locked()
1156 _branch_name, _sha_commit_id, is_head = \
1156 _branch_name, _sha_commit_id, is_head = \
1157 self._is_valid_head(commit_id, self.rhodecode_vcs_repo,
1157 self._is_valid_head(commit_id, self.rhodecode_vcs_repo,
1158 landing_ref=self.db_repo.landing_ref_name)
1158 landing_ref=self.db_repo.landing_ref_name)
1159
1159
1160 self.forbid_non_head(is_head, f_path)
1160 self.forbid_non_head(is_head, f_path)
1161 self.check_branch_permission(_branch_name)
1161 self.check_branch_permission(_branch_name)
1162
1162
1163 c.commit = self._get_commit_or_redirect(commit_id)
1163 c.commit = self._get_commit_or_redirect(commit_id)
1164 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1164 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1165
1165
1166 c.default_message = _(
1166 c.default_message = _(
1167 'Deleted file {} via RhodeCode Enterprise').format(f_path)
1167 'Deleted file {} via RhodeCode Enterprise').format(f_path)
1168 c.f_path = f_path
1168 c.f_path = f_path
1169
1169
1170 return self._get_template_context(c)
1170 return self._get_template_context(c)
1171
1171
1172 @LoginRequired()
1172 @LoginRequired()
1173 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1173 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1174 @CSRFRequired()
1174 @CSRFRequired()
1175 def repo_files_delete_file(self):
1175 def repo_files_delete_file(self):
1176 _ = self.request.translate
1176 _ = self.request.translate
1177
1177
1178 c = self.load_default_context()
1178 c = self.load_default_context()
1179 commit_id, f_path = self._get_commit_and_path()
1179 commit_id, f_path = self._get_commit_and_path()
1180
1180
1181 self._ensure_not_locked()
1181 self._ensure_not_locked()
1182 _branch_name, _sha_commit_id, is_head = \
1182 _branch_name, _sha_commit_id, is_head = \
1183 self._is_valid_head(commit_id, self.rhodecode_vcs_repo,
1183 self._is_valid_head(commit_id, self.rhodecode_vcs_repo,
1184 landing_ref=self.db_repo.landing_ref_name)
1184 landing_ref=self.db_repo.landing_ref_name)
1185
1185
1186 self.forbid_non_head(is_head, f_path)
1186 self.forbid_non_head(is_head, f_path)
1187 self.check_branch_permission(_branch_name)
1187 self.check_branch_permission(_branch_name)
1188
1188
1189 c.commit = self._get_commit_or_redirect(commit_id)
1189 c.commit = self._get_commit_or_redirect(commit_id)
1190 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1190 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1191
1191
1192 c.default_message = _(
1192 c.default_message = _(
1193 'Deleted file {} via RhodeCode Enterprise').format(f_path)
1193 'Deleted file {} via RhodeCode Enterprise').format(f_path)
1194 c.f_path = f_path
1194 c.f_path = f_path
1195 node_path = f_path
1195 node_path = f_path
1196 author = self._rhodecode_db_user.full_contact
1196 author = self._rhodecode_db_user.full_contact
1197 message = self.request.POST.get('message') or c.default_message
1197 message = self.request.POST.get('message') or c.default_message
1198 try:
1198 try:
1199 nodes = {
1199 nodes = {
1200 safe_bytes(node_path): {
1200 safe_bytes(node_path): {
1201 'content': b''
1201 'content': b''
1202 }
1202 }
1203 }
1203 }
1204 ScmModel().delete_nodes(
1204 ScmModel().delete_nodes(
1205 user=self._rhodecode_db_user.user_id, repo=self.db_repo,
1205 user=self._rhodecode_db_user.user_id, repo=self.db_repo,
1206 message=message,
1206 message=message,
1207 nodes=nodes,
1207 nodes=nodes,
1208 parent_commit=c.commit,
1208 parent_commit=c.commit,
1209 author=author,
1209 author=author,
1210 )
1210 )
1211
1211
1212 h.flash(
1212 h.flash(
1213 _('Successfully deleted file `{}`').format(
1213 _('Successfully deleted file `{}`').format(
1214 h.escape(f_path)), category='success')
1214 h.escape(f_path)), category='success')
1215 except Exception:
1215 except Exception:
1216 log.exception('Error during commit operation')
1216 log.exception('Error during commit operation')
1217 h.flash(_('Error occurred during commit'), category='error')
1217 h.flash(_('Error occurred during commit'), category='error')
1218 raise HTTPFound(
1218 raise HTTPFound(
1219 h.route_path('repo_commit', repo_name=self.db_repo_name,
1219 h.route_path('repo_commit', repo_name=self.db_repo_name,
1220 commit_id='tip'))
1220 commit_id='tip'))
1221
1221
1222 @LoginRequired()
1222 @LoginRequired()
1223 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1223 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1224 def repo_files_edit_file(self):
1224 def repo_files_edit_file(self):
1225 _ = self.request.translate
1225 _ = self.request.translate
1226 c = self.load_default_context()
1226 c = self.load_default_context()
1227 commit_id, f_path = self._get_commit_and_path()
1227 commit_id, f_path = self._get_commit_and_path()
1228
1228
1229 self._ensure_not_locked()
1229 self._ensure_not_locked()
1230 _branch_name, _sha_commit_id, is_head = \
1230 _branch_name, _sha_commit_id, is_head = \
1231 self._is_valid_head(commit_id, self.rhodecode_vcs_repo,
1231 self._is_valid_head(commit_id, self.rhodecode_vcs_repo,
1232 landing_ref=self.db_repo.landing_ref_name)
1232 landing_ref=self.db_repo.landing_ref_name)
1233
1233
1234 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)
1235 self.check_branch_permission(_branch_name, commit_id=commit_id)
1235 self.check_branch_permission(_branch_name, commit_id=commit_id)
1236
1236
1237 c.commit = self._get_commit_or_redirect(commit_id)
1237 c.commit = self._get_commit_or_redirect(commit_id)
1238 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1238 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1239
1239
1240 if c.file.is_binary:
1240 if c.file.is_binary:
1241 files_url = h.route_path(
1241 files_url = h.route_path(
1242 'repo_files',
1242 'repo_files',
1243 repo_name=self.db_repo_name,
1243 repo_name=self.db_repo_name,
1244 commit_id=c.commit.raw_id, f_path=f_path)
1244 commit_id=c.commit.raw_id, f_path=f_path)
1245 raise HTTPFound(files_url)
1245 raise HTTPFound(files_url)
1246
1246
1247 c.default_message = _('Edited file {} via RhodeCode Enterprise').format(f_path)
1247 c.default_message = _('Edited file {} via RhodeCode Enterprise').format(f_path)
1248 c.f_path = f_path
1248 c.f_path = f_path
1249
1249
1250 return self._get_template_context(c)
1250 return self._get_template_context(c)
1251
1251
1252 @LoginRequired()
1252 @LoginRequired()
1253 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1253 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1254 @CSRFRequired()
1254 @CSRFRequired()
1255 def repo_files_update_file(self):
1255 def repo_files_update_file(self):
1256 _ = self.request.translate
1256 _ = self.request.translate
1257 c = self.load_default_context()
1257 c = self.load_default_context()
1258 commit_id, f_path = self._get_commit_and_path()
1258 commit_id, f_path = self._get_commit_and_path()
1259
1259
1260 self._ensure_not_locked()
1260 self._ensure_not_locked()
1261
1261
1262 c.commit = self._get_commit_or_redirect(commit_id)
1262 c.commit = self._get_commit_or_redirect(commit_id)
1263 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1263 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1264
1264
1265 if c.file.is_binary:
1265 if c.file.is_binary:
1266 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,
1267 commit_id=c.commit.raw_id, f_path=f_path))
1267 commit_id=c.commit.raw_id, f_path=f_path))
1268
1268
1269 _branch_name, _sha_commit_id, is_head = \
1269 _branch_name, _sha_commit_id, is_head = \
1270 self._is_valid_head(commit_id, self.rhodecode_vcs_repo,
1270 self._is_valid_head(commit_id, self.rhodecode_vcs_repo,
1271 landing_ref=self.db_repo.landing_ref_name)
1271 landing_ref=self.db_repo.landing_ref_name)
1272
1272
1273 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)
1274 self.check_branch_permission(_branch_name, commit_id=commit_id)
1274 self.check_branch_permission(_branch_name, commit_id=commit_id)
1275
1275
1276 c.default_message = _('Edited file {} via RhodeCode Enterprise').format(f_path)
1276 c.default_message = _('Edited file {} via RhodeCode Enterprise').format(f_path)
1277 c.f_path = f_path
1277 c.f_path = f_path
1278
1278
1279 old_content = c.file.str_content
1279 old_content = c.file.str_content
1280 sl = old_content.splitlines(1)
1280 sl = old_content.splitlines(1)
1281 first_line = sl[0] if sl else ''
1281 first_line = sl[0] if sl else ''
1282
1282
1283 r_post = self.request.POST
1283 r_post = self.request.POST
1284 # line endings: 0 - Unix, 1 - Mac, 2 - DOS
1284 # line endings: 0 - Unix, 1 - Mac, 2 - DOS
1285 line_ending_mode = detect_mode(first_line, 0)
1285 line_ending_mode = detect_mode(first_line, 0)
1286 content = convert_line_endings(r_post.get('content', ''), line_ending_mode)
1286 content = convert_line_endings(r_post.get('content', ''), line_ending_mode)
1287
1287
1288 message = r_post.get('message') or c.default_message
1288 message = r_post.get('message') or c.default_message
1289
1289
1290 org_node_path = c.file.str_path
1290 org_node_path = c.file.str_path
1291 filename = r_post['filename']
1291 filename = r_post['filename']
1292
1292
1293 root_path = c.file.dir_path
1293 root_path = c.file.dir_path
1294 pure_path = self.create_pure_path(root_path, filename)
1294 pure_path = self.create_pure_path(root_path, filename)
1295 node_path = pure_path.as_posix()
1295 node_path = pure_path.as_posix()
1296
1296
1297 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,
1298 commit_id=commit_id)
1298 commit_id=commit_id)
1299 if content == old_content and node_path == org_node_path:
1299 if content == old_content and node_path == org_node_path:
1300 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)),
1301 category='warning')
1301 category='warning')
1302 raise HTTPFound(default_redirect_url)
1302 raise HTTPFound(default_redirect_url)
1303
1303
1304 try:
1304 try:
1305 mapping = {
1305 mapping = {
1306 c.file.bytes_path: {
1306 c.file.bytes_path: {
1307 'org_filename': org_node_path,
1307 'org_filename': org_node_path,
1308 'filename': safe_bytes(node_path),
1308 'filename': safe_bytes(node_path),
1309 'content': safe_bytes(content),
1309 'content': safe_bytes(content),
1310 'lexer': '',
1310 'lexer': '',
1311 'op': 'mod',
1311 'op': 'mod',
1312 'mode': c.file.mode
1312 'mode': c.file.mode
1313 }
1313 }
1314 }
1314 }
1315
1315
1316 commit = ScmModel().update_nodes(
1316 commit = ScmModel().update_nodes(
1317 user=self._rhodecode_db_user.user_id,
1317 user=self._rhodecode_db_user.user_id,
1318 repo=self.db_repo,
1318 repo=self.db_repo,
1319 message=message,
1319 message=message,
1320 nodes=mapping,
1320 nodes=mapping,
1321 parent_commit=c.commit,
1321 parent_commit=c.commit,
1322 )
1322 )
1323
1323
1324 h.flash(_('Successfully committed changes to file `{}`').format(
1324 h.flash(_('Successfully committed changes to file `{}`').format(
1325 h.escape(f_path)), category='success')
1325 h.escape(f_path)), category='success')
1326 default_redirect_url = h.route_path(
1326 default_redirect_url = h.route_path(
1327 '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)
1328
1328
1329 except Exception:
1329 except Exception:
1330 log.exception('Error occurred during commit')
1330 log.exception('Error occurred during commit')
1331 h.flash(_('Error occurred during commit'), category='error')
1331 h.flash(_('Error occurred during commit'), category='error')
1332
1332
1333 raise HTTPFound(default_redirect_url)
1333 raise HTTPFound(default_redirect_url)
1334
1334
1335 @LoginRequired()
1335 @LoginRequired()
1336 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1336 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1337 def repo_files_add_file(self):
1337 def repo_files_add_file(self):
1338 _ = self.request.translate
1338 _ = self.request.translate
1339 c = self.load_default_context()
1339 c = self.load_default_context()
1340 commit_id, f_path = self._get_commit_and_path()
1340 commit_id, f_path = self._get_commit_and_path()
1341
1341
1342 self._ensure_not_locked()
1342 self._ensure_not_locked()
1343
1343
1344 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)
1345 if c.commit is None:
1345 if c.commit is None:
1346 c.commit = EmptyCommit(alias=self.rhodecode_vcs_repo.alias)
1346 c.commit = EmptyCommit(alias=self.rhodecode_vcs_repo.alias)
1347
1347
1348 if self.rhodecode_vcs_repo.is_empty():
1348 if self.rhodecode_vcs_repo.is_empty():
1349 # for empty repository we cannot check for current branch, we rely on
1349 # for empty repository we cannot check for current branch, we rely on
1350 # c.commit.branch instead
1350 # c.commit.branch instead
1351 _branch_name, _sha_commit_id, is_head = c.commit.branch, '', True
1351 _branch_name, _sha_commit_id, is_head = c.commit.branch, '', True
1352 else:
1352 else:
1353 _branch_name, _sha_commit_id, is_head = \
1353 _branch_name, _sha_commit_id, is_head = \
1354 self._is_valid_head(commit_id, self.rhodecode_vcs_repo,
1354 self._is_valid_head(commit_id, self.rhodecode_vcs_repo,
1355 landing_ref=self.db_repo.landing_ref_name)
1355 landing_ref=self.db_repo.landing_ref_name)
1356
1356
1357 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)
1358 self.check_branch_permission(_branch_name, commit_id=commit_id)
1358 self.check_branch_permission(_branch_name, commit_id=commit_id)
1359
1359
1360 c.default_message = (_('Added file via RhodeCode Enterprise'))
1360 c.default_message = (_('Added file via RhodeCode Enterprise'))
1361 c.f_path = f_path.lstrip('/') # ensure not relative path
1361 c.f_path = f_path.lstrip('/') # ensure not relative path
1362
1362
1363 return self._get_template_context(c)
1363 return self._get_template_context(c)
1364
1364
1365 @LoginRequired()
1365 @LoginRequired()
1366 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1366 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1367 @CSRFRequired()
1367 @CSRFRequired()
1368 def repo_files_create_file(self):
1368 def repo_files_create_file(self):
1369 _ = self.request.translate
1369 _ = self.request.translate
1370 c = self.load_default_context()
1370 c = self.load_default_context()
1371 commit_id, f_path = self._get_commit_and_path()
1371 commit_id, f_path = self._get_commit_and_path()
1372
1372
1373 self._ensure_not_locked()
1373 self._ensure_not_locked()
1374
1374
1375 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)
1376 if c.commit is None:
1376 if c.commit is None:
1377 c.commit = EmptyCommit(alias=self.rhodecode_vcs_repo.alias)
1377 c.commit = EmptyCommit(alias=self.rhodecode_vcs_repo.alias)
1378
1378
1379 # calculate redirect URL
1379 # calculate redirect URL
1380 if self.rhodecode_vcs_repo.is_empty():
1380 if self.rhodecode_vcs_repo.is_empty():
1381 default_redirect_url = h.route_path(
1381 default_redirect_url = h.route_path(
1382 'repo_summary', repo_name=self.db_repo_name)
1382 'repo_summary', repo_name=self.db_repo_name)
1383 else:
1383 else:
1384 default_redirect_url = h.route_path(
1384 default_redirect_url = h.route_path(
1385 'repo_commit', repo_name=self.db_repo_name, commit_id='tip')
1385 'repo_commit', repo_name=self.db_repo_name, commit_id='tip')
1386
1386
1387 if self.rhodecode_vcs_repo.is_empty():
1387 if self.rhodecode_vcs_repo.is_empty():
1388 # 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
1389 # c.commit.branch instead
1389 # c.commit.branch instead
1390 _branch_name, _sha_commit_id, is_head = c.commit.branch, '', True
1390 _branch_name, _sha_commit_id, is_head = c.commit.branch, '', True
1391 else:
1391 else:
1392 _branch_name, _sha_commit_id, is_head = \
1392 _branch_name, _sha_commit_id, is_head = \
1393 self._is_valid_head(commit_id, self.rhodecode_vcs_repo,
1393 self._is_valid_head(commit_id, self.rhodecode_vcs_repo,
1394 landing_ref=self.db_repo.landing_ref_name)
1394 landing_ref=self.db_repo.landing_ref_name)
1395
1395
1396 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)
1397 self.check_branch_permission(_branch_name, commit_id=commit_id)
1397 self.check_branch_permission(_branch_name, commit_id=commit_id)
1398
1398
1399 c.default_message = (_('Added file via RhodeCode Enterprise'))
1399 c.default_message = (_('Added file via RhodeCode Enterprise'))
1400 c.f_path = f_path
1400 c.f_path = f_path
1401
1401
1402 r_post = self.request.POST
1402 r_post = self.request.POST
1403 message = r_post.get('message') or c.default_message
1403 message = r_post.get('message') or c.default_message
1404 filename = r_post.get('filename')
1404 filename = r_post.get('filename')
1405 unix_mode = 0
1405 unix_mode = 0
1406
1406
1407 if not filename:
1407 if not filename:
1408 # If there's no commit, redirect to repo summary
1408 # If there's no commit, redirect to repo summary
1409 if type(c.commit) is EmptyCommit:
1409 if type(c.commit) is EmptyCommit:
1410 redirect_url = h.route_path(
1410 redirect_url = h.route_path(
1411 'repo_summary', repo_name=self.db_repo_name)
1411 'repo_summary', repo_name=self.db_repo_name)
1412 else:
1412 else:
1413 redirect_url = default_redirect_url
1413 redirect_url = default_redirect_url
1414 h.flash(_('No filename specified'), category='warning')
1414 h.flash(_('No filename specified'), category='warning')
1415 raise HTTPFound(redirect_url)
1415 raise HTTPFound(redirect_url)
1416
1416
1417 root_path = f_path
1417 root_path = f_path
1418 pure_path = self.create_pure_path(root_path, filename)
1418 pure_path = self.create_pure_path(root_path, filename)
1419 node_path = pure_path.as_posix().lstrip('/')
1419 node_path = pure_path.as_posix().lstrip('/')
1420
1420
1421 author = self._rhodecode_db_user.full_contact
1421 author = self._rhodecode_db_user.full_contact
1422 content = convert_line_endings(r_post.get('content', ''), unix_mode)
1422 content = convert_line_endings(r_post.get('content', ''), unix_mode)
1423 nodes = {
1423 nodes = {
1424 safe_bytes(node_path): {
1424 safe_bytes(node_path): {
1425 'content': safe_bytes(content)
1425 'content': safe_bytes(content)
1426 }
1426 }
1427 }
1427 }
1428
1428
1429 try:
1429 try:
1430
1430
1431 commit = ScmModel().create_nodes(
1431 commit = ScmModel().create_nodes(
1432 user=self._rhodecode_db_user.user_id,
1432 user=self._rhodecode_db_user.user_id,
1433 repo=self.db_repo,
1433 repo=self.db_repo,
1434 message=message,
1434 message=message,
1435 nodes=nodes,
1435 nodes=nodes,
1436 parent_commit=c.commit,
1436 parent_commit=c.commit,
1437 author=author,
1437 author=author,
1438 )
1438 )
1439
1439
1440 h.flash(_('Successfully committed new file `{}`').format(
1440 h.flash(_('Successfully committed new file `{}`').format(
1441 h.escape(node_path)), category='success')
1441 h.escape(node_path)), category='success')
1442
1442
1443 default_redirect_url = h.route_path(
1443 default_redirect_url = h.route_path(
1444 '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)
1445
1445
1446 except NonRelativePathError:
1446 except NonRelativePathError:
1447 log.exception('Non Relative path found')
1447 log.exception('Non Relative path found')
1448 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 '
1449 'contain .. in the path'), category='warning')
1449 'contain .. in the path'), category='warning')
1450 raise HTTPFound(default_redirect_url)
1450 raise HTTPFound(default_redirect_url)
1451 except (NodeError, NodeAlreadyExistsError) as e:
1451 except (NodeError, NodeAlreadyExistsError) as e:
1452 h.flash(h.escape(safe_str(e)), category='error')
1452 h.flash(h.escape(safe_str(e)), category='error')
1453 except Exception:
1453 except Exception:
1454 log.exception('Error occurred during commit')
1454 log.exception('Error occurred during commit')
1455 h.flash(_('Error occurred during commit'), category='error')
1455 h.flash(_('Error occurred during commit'), category='error')
1456
1456
1457 raise HTTPFound(default_redirect_url)
1457 raise HTTPFound(default_redirect_url)
1458
1458
1459 @LoginRequired()
1459 @LoginRequired()
1460 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1460 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1461 @CSRFRequired()
1461 @CSRFRequired()
1462 def repo_files_upload_file(self):
1462 def repo_files_upload_file(self):
1463 _ = self.request.translate
1463 _ = self.request.translate
1464 c = self.load_default_context()
1464 c = self.load_default_context()
1465 commit_id, f_path = self._get_commit_and_path()
1465 commit_id, f_path = self._get_commit_and_path()
1466
1466
1467 self._ensure_not_locked()
1467 self._ensure_not_locked()
1468
1468
1469 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)
1470 if c.commit is None:
1470 if c.commit is None:
1471 c.commit = EmptyCommit(alias=self.rhodecode_vcs_repo.alias)
1471 c.commit = EmptyCommit(alias=self.rhodecode_vcs_repo.alias)
1472
1472
1473 # calculate redirect URL
1473 # calculate redirect URL
1474 if self.rhodecode_vcs_repo.is_empty():
1474 if self.rhodecode_vcs_repo.is_empty():
1475 default_redirect_url = h.route_path(
1475 default_redirect_url = h.route_path(
1476 'repo_summary', repo_name=self.db_repo_name)
1476 'repo_summary', repo_name=self.db_repo_name)
1477 else:
1477 else:
1478 default_redirect_url = h.route_path(
1478 default_redirect_url = h.route_path(
1479 'repo_commit', repo_name=self.db_repo_name, commit_id='tip')
1479 'repo_commit', repo_name=self.db_repo_name, commit_id='tip')
1480
1480
1481 if self.rhodecode_vcs_repo.is_empty():
1481 if self.rhodecode_vcs_repo.is_empty():
1482 # 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
1483 # c.commit.branch instead
1483 # c.commit.branch instead
1484 _branch_name, _sha_commit_id, is_head = c.commit.branch, '', True
1484 _branch_name, _sha_commit_id, is_head = c.commit.branch, '', True
1485 else:
1485 else:
1486 _branch_name, _sha_commit_id, is_head = \
1486 _branch_name, _sha_commit_id, is_head = \
1487 self._is_valid_head(commit_id, self.rhodecode_vcs_repo,
1487 self._is_valid_head(commit_id, self.rhodecode_vcs_repo,
1488 landing_ref=self.db_repo.landing_ref_name)
1488 landing_ref=self.db_repo.landing_ref_name)
1489
1489
1490 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)
1491 if error:
1491 if error:
1492 return {
1492 return {
1493 'error': error,
1493 'error': error,
1494 'redirect_url': default_redirect_url
1494 'redirect_url': default_redirect_url
1495 }
1495 }
1496 error = self.check_branch_permission(_branch_name, json_mode=True)
1496 error = self.check_branch_permission(_branch_name, json_mode=True)
1497 if error:
1497 if error:
1498 return {
1498 return {
1499 'error': error,
1499 'error': error,
1500 'redirect_url': default_redirect_url
1500 'redirect_url': default_redirect_url
1501 }
1501 }
1502
1502
1503 c.default_message = (_('Uploaded file via RhodeCode Enterprise'))
1503 c.default_message = (_('Uploaded file via RhodeCode Enterprise'))
1504 c.f_path = f_path
1504 c.f_path = f_path
1505
1505
1506 r_post = self.request.POST
1506 r_post = self.request.POST
1507
1507
1508 message = c.default_message
1508 message = c.default_message
1509 user_message = r_post.getall('message')
1509 user_message = r_post.getall('message')
1510 if isinstance(user_message, list) and user_message:
1510 if isinstance(user_message, list) and user_message:
1511 # 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
1512 message = user_message[0] if user_message[0] else message
1512 message = user_message[0] if user_message[0] else message
1513
1513
1514 nodes = {}
1514 nodes = {}
1515
1515
1516 for file_obj in r_post.getall('files_upload') or []:
1516 for file_obj in r_post.getall('files_upload') or []:
1517 content = file_obj.file
1517 content = file_obj.file
1518 filename = file_obj.filename
1518 filename = file_obj.filename
1519
1519
1520 root_path = f_path
1520 root_path = f_path
1521 pure_path = self.create_pure_path(root_path, filename)
1521 pure_path = self.create_pure_path(root_path, filename)
1522 node_path = pure_path.as_posix().lstrip('/')
1522 node_path = pure_path.as_posix().lstrip('/')
1523
1523
1524 nodes[safe_bytes(node_path)] = {
1524 nodes[safe_bytes(node_path)] = {
1525 'content': content
1525 'content': content
1526 }
1526 }
1527
1527
1528 if not nodes:
1528 if not nodes:
1529 error = 'missing files'
1529 error = 'missing files'
1530 return {
1530 return {
1531 'error': error,
1531 'error': error,
1532 'redirect_url': default_redirect_url
1532 'redirect_url': default_redirect_url
1533 }
1533 }
1534
1534
1535 author = self._rhodecode_db_user.full_contact
1535 author = self._rhodecode_db_user.full_contact
1536
1536
1537 try:
1537 try:
1538 commit = ScmModel().create_nodes(
1538 commit = ScmModel().create_nodes(
1539 user=self._rhodecode_db_user.user_id,
1539 user=self._rhodecode_db_user.user_id,
1540 repo=self.db_repo,
1540 repo=self.db_repo,
1541 message=message,
1541 message=message,
1542 nodes=nodes,
1542 nodes=nodes,
1543 parent_commit=c.commit,
1543 parent_commit=c.commit,
1544 author=author,
1544 author=author,
1545 )
1545 )
1546 if len(nodes) == 1:
1546 if len(nodes) == 1:
1547 flash_message = _('Successfully committed {} new files').format(len(nodes))
1547 flash_message = _('Successfully committed {} new files').format(len(nodes))
1548 else:
1548 else:
1549 flash_message = _('Successfully committed 1 new file')
1549 flash_message = _('Successfully committed 1 new file')
1550
1550
1551 h.flash(flash_message, category='success')
1551 h.flash(flash_message, category='success')
1552
1552
1553 default_redirect_url = h.route_path(
1553 default_redirect_url = h.route_path(
1554 '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)
1555
1555
1556 except NonRelativePathError:
1556 except NonRelativePathError:
1557 log.exception('Non Relative path found')
1557 log.exception('Non Relative path found')
1558 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 '
1559 'contain .. in the path')
1559 'contain .. in the path')
1560 h.flash(error, category='warning')
1560 h.flash(error, category='warning')
1561
1561
1562 return {
1562 return {
1563 'error': error,
1563 'error': error,
1564 'redirect_url': default_redirect_url
1564 'redirect_url': default_redirect_url
1565 }
1565 }
1566 except (NodeError, NodeAlreadyExistsError) as e:
1566 except (NodeError, NodeAlreadyExistsError) as e:
1567 error = h.escape(e)
1567 error = h.escape(e)
1568 h.flash(error, category='error')
1568 h.flash(error, category='error')
1569
1569
1570 return {
1570 return {
1571 'error': error,
1571 'error': error,
1572 'redirect_url': default_redirect_url
1572 'redirect_url': default_redirect_url
1573 }
1573 }
1574 except Exception:
1574 except Exception:
1575 log.exception('Error occurred during commit')
1575 log.exception('Error occurred during commit')
1576 error = _('Error occurred during commit')
1576 error = _('Error occurred during commit')
1577 h.flash(error, category='error')
1577 h.flash(error, category='error')
1578 return {
1578 return {
1579 'error': error,
1579 'error': error,
1580 'redirect_url': default_redirect_url
1580 'redirect_url': default_redirect_url
1581 }
1581 }
1582
1582
1583 return {
1583 return {
1584 'error': None,
1584 'error': None,
1585 'redirect_url': default_redirect_url
1585 'redirect_url': default_redirect_url
1586 }
1586 }
General Comments 0
You need to be logged in to leave comments. Login now