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