##// END OF EJS Templates
fix(submodules): fixed an error if reaching out submodule path....
super-admin -
r5261:550d6219 default
parent child Browse files
Show More
@@ -1,1583 +1,1584 b''
1 # Copyright (C) 2011-2023 RhodeCode GmbH
1 # Copyright (C) 2011-2023 RhodeCode GmbH
2 #
2 #
3 # This program is free software: you can redistribute it and/or modify
3 # This program is free software: you can redistribute it and/or modify
4 # it under the terms of the GNU Affero General Public License, version 3
4 # it under the terms of the GNU Affero General Public License, version 3
5 # (only), as published by the Free Software Foundation.
5 # (only), as published by the Free Software Foundation.
6 #
6 #
7 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # GNU General Public License for more details.
10 # GNU General Public License for more details.
11 #
11 #
12 # You should have received a copy of the GNU Affero General Public License
12 # You should have received a copy of the GNU Affero General Public License
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 #
14 #
15 # This program is dual-licensed. If you wish to learn more about the
15 # This program is dual-licensed. If you wish to learn more about the
16 # RhodeCode Enterprise Edition, including its added features, Support services,
16 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
18
18
19 import itertools
19 import itertools
20 import logging
20 import logging
21 import os
21 import os
22 import collections
22 import collections
23 import urllib.request
23 import urllib.request
24 import urllib.parse
24 import urllib.parse
25 import urllib.error
25 import urllib.error
26 import pathlib
26 import pathlib
27
27
28 from pyramid.httpexceptions import HTTPNotFound, HTTPBadRequest, HTTPFound
28 from pyramid.httpexceptions import HTTPNotFound, HTTPBadRequest, HTTPFound
29
29
30 from pyramid.renderers import render
30 from pyramid.renderers import render
31 from pyramid.response import Response
31 from pyramid.response import Response
32
32
33 import rhodecode
33 import rhodecode
34 from rhodecode.apps._base import RepoAppView
34 from rhodecode.apps._base import RepoAppView
35
35
36
36
37 from rhodecode.lib import diffs, helpers as h, rc_cache
37 from rhodecode.lib import diffs, helpers as h, rc_cache
38 from rhodecode.lib import audit_logger
38 from rhodecode.lib import audit_logger
39 from rhodecode.lib.hash_utils import sha1_safe
39 from rhodecode.lib.hash_utils import sha1_safe
40 from rhodecode.lib.rc_cache.archive_cache import get_archival_cache_store, get_archival_config, ReentrantLock
40 from rhodecode.lib.rc_cache.archive_cache import get_archival_cache_store, get_archival_config, ReentrantLock
41 from rhodecode.lib.str_utils import safe_bytes, convert_special_chars
41 from rhodecode.lib.str_utils import safe_bytes, convert_special_chars
42 from rhodecode.lib.view_utils import parse_path_ref
42 from rhodecode.lib.view_utils import parse_path_ref
43 from rhodecode.lib.exceptions import NonRelativePathError
43 from rhodecode.lib.exceptions import NonRelativePathError
44 from rhodecode.lib.codeblocks import (
44 from rhodecode.lib.codeblocks import (
45 filenode_as_lines_tokens, filenode_as_annotated_lines_tokens)
45 filenode_as_lines_tokens, filenode_as_annotated_lines_tokens)
46 from rhodecode.lib.utils2 import convert_line_endings, detect_mode
46 from rhodecode.lib.utils2 import convert_line_endings, detect_mode
47 from rhodecode.lib.type_utils import str2bool
47 from rhodecode.lib.type_utils import str2bool
48 from rhodecode.lib.str_utils import safe_str, safe_int
48 from rhodecode.lib.str_utils import safe_str, safe_int
49 from rhodecode.lib.auth import (
49 from rhodecode.lib.auth import (
50 LoginRequired, HasRepoPermissionAnyDecorator, CSRFRequired)
50 LoginRequired, HasRepoPermissionAnyDecorator, CSRFRequired)
51 from rhodecode.lib.vcs import path as vcspath
51 from rhodecode.lib.vcs import path as vcspath
52 from rhodecode.lib.vcs.backends.base import EmptyCommit
52 from rhodecode.lib.vcs.backends.base import EmptyCommit
53 from rhodecode.lib.vcs.conf import settings
53 from rhodecode.lib.vcs.conf import settings
54 from rhodecode.lib.vcs.nodes import FileNode
54 from rhodecode.lib.vcs.nodes import FileNode
55 from rhodecode.lib.vcs.exceptions import (
55 from rhodecode.lib.vcs.exceptions import (
56 RepositoryError, CommitDoesNotExistError, EmptyRepositoryError,
56 RepositoryError, CommitDoesNotExistError, EmptyRepositoryError,
57 ImproperArchiveTypeError, VCSError, NodeAlreadyExistsError,
57 ImproperArchiveTypeError, VCSError, NodeAlreadyExistsError,
58 NodeDoesNotExistError, CommitError, NodeError)
58 NodeDoesNotExistError, CommitError, NodeError)
59
59
60 from rhodecode.model.scm import ScmModel
60 from rhodecode.model.scm import ScmModel
61 from rhodecode.model.db import Repository
61 from rhodecode.model.db import Repository
62
62
63 log = logging.getLogger(__name__)
63 log = logging.getLogger(__name__)
64
64
65
65
66 def get_archive_name(db_repo_id, db_repo_name, commit_sha, ext, subrepos=False, path_sha='', with_hash=True):
66 def get_archive_name(db_repo_id, db_repo_name, commit_sha, ext, subrepos=False, path_sha='', with_hash=True):
67 # original backward compat name of archive
67 # original backward compat name of archive
68 clean_name = safe_str(convert_special_chars(db_repo_name).replace('/', '_'))
68 clean_name = safe_str(convert_special_chars(db_repo_name).replace('/', '_'))
69
69
70 # e.g vcsserver-id-abcd-sub-1-abcfdef-archive-all.zip
70 # e.g vcsserver-id-abcd-sub-1-abcfdef-archive-all.zip
71 # vcsserver-id-abcd-sub-0-abcfdef-COMMIT_SHA-PATH_SHA.zip
71 # vcsserver-id-abcd-sub-0-abcfdef-COMMIT_SHA-PATH_SHA.zip
72 id_sha = sha1_safe(str(db_repo_id))[:4]
72 id_sha = sha1_safe(str(db_repo_id))[:4]
73 sub_repo = 'sub-1' if subrepos else 'sub-0'
73 sub_repo = 'sub-1' if subrepos else 'sub-0'
74 commit = commit_sha if with_hash else 'archive'
74 commit = commit_sha if with_hash else 'archive'
75 path_marker = (path_sha if with_hash else '') or 'all'
75 path_marker = (path_sha if with_hash else '') or 'all'
76 archive_name = f'{clean_name}-id-{id_sha}-{sub_repo}-{commit}-{path_marker}{ext}'
76 archive_name = f'{clean_name}-id-{id_sha}-{sub_repo}-{commit}-{path_marker}{ext}'
77
77
78 return archive_name
78 return archive_name
79
79
80
80
81 def get_path_sha(at_path):
81 def get_path_sha(at_path):
82 return safe_str(sha1_safe(at_path)[:8])
82 return safe_str(sha1_safe(at_path)[:8])
83
83
84
84
85 def _get_archive_spec(fname):
85 def _get_archive_spec(fname):
86 log.debug('Detecting archive spec for: `%s`', fname)
86 log.debug('Detecting archive spec for: `%s`', fname)
87
87
88 fileformat = None
88 fileformat = None
89 ext = None
89 ext = None
90 content_type = None
90 content_type = None
91 for a_type, content_type, extension in settings.ARCHIVE_SPECS:
91 for a_type, content_type, extension in settings.ARCHIVE_SPECS:
92
92
93 if fname.endswith(extension):
93 if fname.endswith(extension):
94 fileformat = a_type
94 fileformat = a_type
95 log.debug('archive is of type: %s', fileformat)
95 log.debug('archive is of type: %s', fileformat)
96 ext = extension
96 ext = extension
97 break
97 break
98
98
99 if not fileformat:
99 if not fileformat:
100 raise ValueError()
100 raise ValueError()
101
101
102 # left over part of whole fname is the commit
102 # left over part of whole fname is the commit
103 commit_id = fname[:-len(ext)]
103 commit_id = fname[:-len(ext)]
104
104
105 return commit_id, ext, fileformat, content_type
105 return commit_id, ext, fileformat, content_type
106
106
107
107
108 class RepoFilesView(RepoAppView):
108 class RepoFilesView(RepoAppView):
109
109
110 @staticmethod
110 @staticmethod
111 def adjust_file_path_for_svn(f_path, repo):
111 def adjust_file_path_for_svn(f_path, repo):
112 """
112 """
113 Computes the relative path of `f_path`.
113 Computes the relative path of `f_path`.
114
114
115 This is mainly based on prefix matching of the recognized tags and
115 This is mainly based on prefix matching of the recognized tags and
116 branches in the underlying repository.
116 branches in the underlying repository.
117 """
117 """
118 tags_and_branches = itertools.chain(
118 tags_and_branches = itertools.chain(
119 repo.branches.keys(),
119 repo.branches.keys(),
120 repo.tags.keys())
120 repo.tags.keys())
121 tags_and_branches = sorted(tags_and_branches, key=len, reverse=True)
121 tags_and_branches = sorted(tags_and_branches, key=len, reverse=True)
122
122
123 for name in tags_and_branches:
123 for name in tags_and_branches:
124 if f_path.startswith(f'{name}/'):
124 if f_path.startswith(f'{name}/'):
125 f_path = vcspath.relpath(f_path, name)
125 f_path = vcspath.relpath(f_path, name)
126 break
126 break
127 return f_path
127 return f_path
128
128
129 def load_default_context(self):
129 def load_default_context(self):
130 c = self._get_local_tmpl_context(include_app_defaults=True)
130 c = self._get_local_tmpl_context(include_app_defaults=True)
131 c.rhodecode_repo = self.rhodecode_vcs_repo
131 c.rhodecode_repo = self.rhodecode_vcs_repo
132 c.enable_downloads = self.db_repo.enable_downloads
132 c.enable_downloads = self.db_repo.enable_downloads
133 return c
133 return c
134
134
135 def _ensure_not_locked(self, commit_id='tip'):
135 def _ensure_not_locked(self, commit_id='tip'):
136 _ = self.request.translate
136 _ = self.request.translate
137
137
138 repo = self.db_repo
138 repo = self.db_repo
139 if repo.enable_locking and repo.locked[0]:
139 if repo.enable_locking and repo.locked[0]:
140 h.flash(_('This repository has been locked by %s on %s')
140 h.flash(_('This repository has been locked by %s on %s')
141 % (h.person_by_id(repo.locked[0]),
141 % (h.person_by_id(repo.locked[0]),
142 h.format_date(h.time_to_datetime(repo.locked[1]))),
142 h.format_date(h.time_to_datetime(repo.locked[1]))),
143 'warning')
143 'warning')
144 files_url = h.route_path(
144 files_url = h.route_path(
145 'repo_files:default_path',
145 'repo_files:default_path',
146 repo_name=self.db_repo_name, commit_id=commit_id)
146 repo_name=self.db_repo_name, commit_id=commit_id)
147 raise HTTPFound(files_url)
147 raise HTTPFound(files_url)
148
148
149 def forbid_non_head(self, is_head, f_path, commit_id='tip', json_mode=False):
149 def forbid_non_head(self, is_head, f_path, commit_id='tip', json_mode=False):
150 _ = self.request.translate
150 _ = self.request.translate
151
151
152 if not is_head:
152 if not is_head:
153 message = _('Cannot modify file. '
153 message = _('Cannot modify file. '
154 'Given commit `{}` is not head of a branch.').format(commit_id)
154 'Given commit `{}` is not head of a branch.').format(commit_id)
155 h.flash(message, category='warning')
155 h.flash(message, category='warning')
156
156
157 if json_mode:
157 if json_mode:
158 return message
158 return message
159
159
160 files_url = h.route_path(
160 files_url = h.route_path(
161 'repo_files', repo_name=self.db_repo_name, commit_id=commit_id,
161 'repo_files', repo_name=self.db_repo_name, commit_id=commit_id,
162 f_path=f_path)
162 f_path=f_path)
163 raise HTTPFound(files_url)
163 raise HTTPFound(files_url)
164
164
165 def check_branch_permission(self, branch_name, commit_id='tip', json_mode=False):
165 def check_branch_permission(self, branch_name, commit_id='tip', json_mode=False):
166 _ = self.request.translate
166 _ = self.request.translate
167
167
168 rule, branch_perm = self._rhodecode_user.get_rule_and_branch_permission(
168 rule, branch_perm = self._rhodecode_user.get_rule_and_branch_permission(
169 self.db_repo_name, branch_name)
169 self.db_repo_name, branch_name)
170 if branch_perm and branch_perm not in ['branch.push', 'branch.push_force']:
170 if branch_perm and branch_perm not in ['branch.push', 'branch.push_force']:
171 message = _('Branch `{}` changes forbidden by rule {}.').format(
171 message = _('Branch `{}` changes forbidden by rule {}.').format(
172 h.escape(branch_name), h.escape(rule))
172 h.escape(branch_name), h.escape(rule))
173 h.flash(message, 'warning')
173 h.flash(message, 'warning')
174
174
175 if json_mode:
175 if json_mode:
176 return message
176 return message
177
177
178 files_url = h.route_path(
178 files_url = h.route_path(
179 'repo_files:default_path', repo_name=self.db_repo_name, commit_id=commit_id)
179 'repo_files:default_path', repo_name=self.db_repo_name, commit_id=commit_id)
180
180
181 raise HTTPFound(files_url)
181 raise HTTPFound(files_url)
182
182
183 def _get_commit_and_path(self):
183 def _get_commit_and_path(self):
184 default_commit_id = self.db_repo.landing_ref_name
184 default_commit_id = self.db_repo.landing_ref_name
185 default_f_path = '/'
185 default_f_path = '/'
186
186
187 commit_id = self.request.matchdict.get(
187 commit_id = self.request.matchdict.get(
188 'commit_id', default_commit_id)
188 'commit_id', default_commit_id)
189 f_path = self._get_f_path(self.request.matchdict, default_f_path)
189 f_path = self._get_f_path(self.request.matchdict, default_f_path)
190 return commit_id, f_path
190 return commit_id, f_path
191
191
192 def _get_default_encoding(self, c):
192 def _get_default_encoding(self, c):
193 enc_list = getattr(c, 'default_encodings', [])
193 enc_list = getattr(c, 'default_encodings', [])
194 return enc_list[0] if enc_list else 'UTF-8'
194 return enc_list[0] if enc_list else 'UTF-8'
195
195
196 def _get_commit_or_redirect(self, commit_id, redirect_after=True):
196 def _get_commit_or_redirect(self, commit_id, redirect_after=True):
197 """
197 """
198 This is a safe way to get commit. If an error occurs it redirects to
198 This is a safe way to get commit. If an error occurs it redirects to
199 tip with proper message
199 tip with proper message
200
200
201 :param commit_id: id of commit to fetch
201 :param commit_id: id of commit to fetch
202 :param redirect_after: toggle redirection
202 :param redirect_after: toggle redirection
203 """
203 """
204 _ = self.request.translate
204 _ = self.request.translate
205
205
206 try:
206 try:
207 return self.rhodecode_vcs_repo.get_commit(commit_id)
207 return self.rhodecode_vcs_repo.get_commit(commit_id)
208 except EmptyRepositoryError:
208 except EmptyRepositoryError:
209 if not redirect_after:
209 if not redirect_after:
210 return None
210 return None
211
211
212 add_new = upload_new = ""
212 add_new = upload_new = ""
213 if h.HasRepoPermissionAny(
213 if h.HasRepoPermissionAny(
214 'repository.write', 'repository.admin')(self.db_repo_name):
214 'repository.write', 'repository.admin')(self.db_repo_name):
215 _url = h.route_path(
215 _url = h.route_path(
216 'repo_files_add_file',
216 'repo_files_add_file',
217 repo_name=self.db_repo_name, commit_id=0, f_path='')
217 repo_name=self.db_repo_name, commit_id=0, f_path='')
218 add_new = h.link_to(
218 add_new = h.link_to(
219 _('add a new file'), _url, class_="alert-link")
219 _('add a new file'), _url, class_="alert-link")
220
220
221 _url_upld = h.route_path(
221 _url_upld = h.route_path(
222 'repo_files_upload_file',
222 'repo_files_upload_file',
223 repo_name=self.db_repo_name, commit_id=0, f_path='')
223 repo_name=self.db_repo_name, commit_id=0, f_path='')
224 upload_new = h.link_to(
224 upload_new = h.link_to(
225 _('upload a new file'), _url_upld, class_="alert-link")
225 _('upload a new file'), _url_upld, class_="alert-link")
226
226
227 h.flash(h.literal(
227 h.flash(h.literal(
228 _('There are no files yet. Click here to %s or %s.') % (add_new, upload_new)), category='warning')
228 _('There are no files yet. Click here to %s or %s.') % (add_new, upload_new)), category='warning')
229 raise HTTPFound(
229 raise HTTPFound(
230 h.route_path('repo_summary', repo_name=self.db_repo_name))
230 h.route_path('repo_summary', repo_name=self.db_repo_name))
231
231
232 except (CommitDoesNotExistError, LookupError) as e:
232 except (CommitDoesNotExistError, LookupError) as e:
233 msg = _('No such commit exists for this repository. Commit: {}').format(commit_id)
233 msg = _('No such commit exists for this repository. Commit: {}').format(commit_id)
234 h.flash(msg, category='error')
234 h.flash(msg, category='error')
235 raise HTTPNotFound()
235 raise HTTPNotFound()
236 except RepositoryError as e:
236 except RepositoryError as e:
237 h.flash(h.escape(safe_str(e)), category='error')
237 h.flash(h.escape(safe_str(e)), category='error')
238 raise HTTPNotFound()
238 raise HTTPNotFound()
239
239
240 def _get_filenode_or_redirect(self, commit_obj, path, pre_load=None):
240 def _get_filenode_or_redirect(self, commit_obj, path, pre_load=None):
241 """
241 """
242 Returns file_node, if error occurs or given path is directory,
242 Returns file_node, if error occurs or given path is directory,
243 it'll redirect to top level path
243 it'll redirect to top level path
244 """
244 """
245 _ = self.request.translate
245 _ = self.request.translate
246
246
247 try:
247 try:
248 file_node = commit_obj.get_node(path, pre_load=pre_load)
248 file_node = commit_obj.get_node(path, pre_load=pre_load)
249 if file_node.is_dir():
249 if file_node.is_dir():
250 raise RepositoryError('The given path is a directory')
250 raise RepositoryError('The given path is a directory')
251 except CommitDoesNotExistError:
251 except CommitDoesNotExistError:
252 log.exception('No such commit exists for this repository')
252 log.exception('No such commit exists for this repository')
253 h.flash(_('No such commit exists for this repository'), category='error')
253 h.flash(_('No such commit exists for this repository'), category='error')
254 raise HTTPNotFound()
254 raise HTTPNotFound()
255 except RepositoryError as e:
255 except RepositoryError as e:
256 log.warning('Repository error while fetching filenode `%s`. Err:%s', path, e)
256 log.warning('Repository error while fetching filenode `%s`. Err:%s', path, e)
257 h.flash(h.escape(safe_str(e)), category='error')
257 h.flash(h.escape(safe_str(e)), category='error')
258 raise HTTPNotFound()
258 raise HTTPNotFound()
259
259
260 return file_node
260 return file_node
261
261
262 def _is_valid_head(self, commit_id, repo, landing_ref):
262 def _is_valid_head(self, commit_id, repo, landing_ref):
263 branch_name = sha_commit_id = ''
263 branch_name = sha_commit_id = ''
264 is_head = False
264 is_head = False
265 log.debug('Checking if commit_id `%s` is a head for %s.', commit_id, repo)
265 log.debug('Checking if commit_id `%s` is a head for %s.', commit_id, repo)
266
266
267 for _branch_name, branch_commit_id in repo.branches.items():
267 for _branch_name, branch_commit_id in repo.branches.items():
268 # simple case we pass in branch name, it's a HEAD
268 # simple case we pass in branch name, it's a HEAD
269 if commit_id == _branch_name:
269 if commit_id == _branch_name:
270 is_head = True
270 is_head = True
271 branch_name = _branch_name
271 branch_name = _branch_name
272 sha_commit_id = branch_commit_id
272 sha_commit_id = branch_commit_id
273 break
273 break
274 # case when we pass in full sha commit_id, which is a head
274 # case when we pass in full sha commit_id, which is a head
275 elif commit_id == branch_commit_id:
275 elif commit_id == branch_commit_id:
276 is_head = True
276 is_head = True
277 branch_name = _branch_name
277 branch_name = _branch_name
278 sha_commit_id = branch_commit_id
278 sha_commit_id = branch_commit_id
279 break
279 break
280
280
281 if h.is_svn(repo) and not repo.is_empty():
281 if h.is_svn(repo) and not repo.is_empty():
282 # Note: Subversion only has one head.
282 # Note: Subversion only has one head.
283 if commit_id == repo.get_commit(commit_idx=-1).raw_id:
283 if commit_id == repo.get_commit(commit_idx=-1).raw_id:
284 is_head = True
284 is_head = True
285 return branch_name, sha_commit_id, is_head
285 return branch_name, sha_commit_id, is_head
286
286
287 # checked branches, means we only need to try to get the branch/commit_sha
287 # checked branches, means we only need to try to get the branch/commit_sha
288 if repo.is_empty():
288 if repo.is_empty():
289 is_head = True
289 is_head = True
290 branch_name = landing_ref
290 branch_name = landing_ref
291 sha_commit_id = EmptyCommit().raw_id
291 sha_commit_id = EmptyCommit().raw_id
292 else:
292 else:
293 commit = repo.get_commit(commit_id=commit_id)
293 commit = repo.get_commit(commit_id=commit_id)
294 if commit:
294 if commit:
295 branch_name = commit.branch
295 branch_name = commit.branch
296 sha_commit_id = commit.raw_id
296 sha_commit_id = commit.raw_id
297
297
298 return branch_name, sha_commit_id, is_head
298 return branch_name, sha_commit_id, is_head
299
299
300 def _get_tree_at_commit(self, c, commit_id, f_path, full_load=False, at_rev=None):
300 def _get_tree_at_commit(self, c, commit_id, f_path, full_load=False, at_rev=None):
301
301
302 repo_id = self.db_repo.repo_id
302 repo_id = self.db_repo.repo_id
303 force_recache = self.get_recache_flag()
303 force_recache = self.get_recache_flag()
304
304
305 cache_seconds = safe_int(
305 cache_seconds = safe_int(
306 rhodecode.CONFIG.get('rc_cache.cache_repo.expiration_time'))
306 rhodecode.CONFIG.get('rc_cache.cache_repo.expiration_time'))
307 cache_on = not force_recache and cache_seconds > 0
307 cache_on = not force_recache and cache_seconds > 0
308 log.debug(
308 log.debug(
309 'Computing FILE TREE for repo_id %s commit_id `%s` and path `%s`'
309 'Computing FILE TREE for repo_id %s commit_id `%s` and path `%s`'
310 'with caching: %s[TTL: %ss]' % (
310 'with caching: %s[TTL: %ss]' % (
311 repo_id, commit_id, f_path, cache_on, cache_seconds or 0))
311 repo_id, commit_id, f_path, cache_on, cache_seconds or 0))
312
312
313 cache_namespace_uid = f'repo.{rc_cache.FILE_TREE_CACHE_VER}.{repo_id}'
313 cache_namespace_uid = f'repo.{rc_cache.FILE_TREE_CACHE_VER}.{repo_id}'
314 region = rc_cache.get_or_create_region('cache_repo', cache_namespace_uid)
314 region = rc_cache.get_or_create_region('cache_repo', cache_namespace_uid)
315
315
316 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid, condition=cache_on)
316 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid, condition=cache_on)
317 def compute_file_tree(_name_hash, _repo_id, _commit_id, _f_path, _full_load, _at_rev):
317 def compute_file_tree(_name_hash, _repo_id, _commit_id, _f_path, _full_load, _at_rev):
318 log.debug('Generating cached file tree at for repo_id: %s, %s, %s',
318 log.debug('Generating cached file tree at for repo_id: %s, %s, %s',
319 _repo_id, _commit_id, _f_path)
319 _repo_id, _commit_id, _f_path)
320
320
321 c.full_load = _full_load
321 c.full_load = _full_load
322 return render(
322 return render(
323 'rhodecode:templates/files/files_browser_tree.mako',
323 'rhodecode:templates/files/files_browser_tree.mako',
324 self._get_template_context(c), self.request, _at_rev)
324 self._get_template_context(c), self.request, _at_rev)
325
325
326 return compute_file_tree(
326 return compute_file_tree(
327 self.db_repo.repo_name_hash, self.db_repo.repo_id, commit_id, f_path, full_load, at_rev)
327 self.db_repo.repo_name_hash, self.db_repo.repo_id, commit_id, f_path, full_load, at_rev)
328
328
329 def create_pure_path(self, *parts):
329 def create_pure_path(self, *parts):
330 # Split paths and sanitize them, removing any ../ etc
330 # Split paths and sanitize them, removing any ../ etc
331 sanitized_path = [
331 sanitized_path = [
332 x for x in pathlib.PurePath(*parts).parts
332 x for x in pathlib.PurePath(*parts).parts
333 if x not in ['.', '..']]
333 if x not in ['.', '..']]
334
334
335 pure_path = pathlib.PurePath(*sanitized_path)
335 pure_path = pathlib.PurePath(*sanitized_path)
336 return pure_path
336 return pure_path
337
337
338 def _is_lf_enabled(self, target_repo):
338 def _is_lf_enabled(self, target_repo):
339 lf_enabled = False
339 lf_enabled = False
340
340
341 lf_key_for_vcs_map = {
341 lf_key_for_vcs_map = {
342 'hg': 'extensions_largefiles',
342 'hg': 'extensions_largefiles',
343 'git': 'vcs_git_lfs_enabled'
343 'git': 'vcs_git_lfs_enabled'
344 }
344 }
345
345
346 lf_key_for_vcs = lf_key_for_vcs_map.get(target_repo.repo_type)
346 lf_key_for_vcs = lf_key_for_vcs_map.get(target_repo.repo_type)
347
347
348 if lf_key_for_vcs:
348 if lf_key_for_vcs:
349 lf_enabled = self._get_repo_setting(target_repo, lf_key_for_vcs)
349 lf_enabled = self._get_repo_setting(target_repo, lf_key_for_vcs)
350
350
351 return lf_enabled
351 return lf_enabled
352
352
353 @LoginRequired()
353 @LoginRequired()
354 @HasRepoPermissionAnyDecorator(
354 @HasRepoPermissionAnyDecorator(
355 'repository.read', 'repository.write', 'repository.admin')
355 'repository.read', 'repository.write', 'repository.admin')
356 def repo_archivefile(self):
356 def repo_archivefile(self):
357 # archive cache config
357 # archive cache config
358 from rhodecode import CONFIG
358 from rhodecode import CONFIG
359 _ = self.request.translate
359 _ = self.request.translate
360 self.load_default_context()
360 self.load_default_context()
361 default_at_path = '/'
361 default_at_path = '/'
362 fname = self.request.matchdict['fname']
362 fname = self.request.matchdict['fname']
363 subrepos = self.request.GET.get('subrepos') == 'true'
363 subrepos = self.request.GET.get('subrepos') == 'true'
364 with_hash = str2bool(self.request.GET.get('with_hash', '1'))
364 with_hash = str2bool(self.request.GET.get('with_hash', '1'))
365 at_path = self.request.GET.get('at_path') or default_at_path
365 at_path = self.request.GET.get('at_path') or default_at_path
366
366
367 if not self.db_repo.enable_downloads:
367 if not self.db_repo.enable_downloads:
368 return Response(_('Downloads disabled'))
368 return Response(_('Downloads disabled'))
369
369
370 try:
370 try:
371 commit_id, ext, fileformat, content_type = \
371 commit_id, ext, fileformat, content_type = \
372 _get_archive_spec(fname)
372 _get_archive_spec(fname)
373 except ValueError:
373 except ValueError:
374 return Response(_('Unknown archive type for: `{}`').format(
374 return Response(_('Unknown archive type for: `{}`').format(
375 h.escape(fname)))
375 h.escape(fname)))
376
376
377 try:
377 try:
378 commit = self.rhodecode_vcs_repo.get_commit(commit_id)
378 commit = self.rhodecode_vcs_repo.get_commit(commit_id)
379 except CommitDoesNotExistError:
379 except CommitDoesNotExistError:
380 return Response(_('Unknown commit_id {}').format(
380 return Response(_('Unknown commit_id {}').format(
381 h.escape(commit_id)))
381 h.escape(commit_id)))
382 except EmptyRepositoryError:
382 except EmptyRepositoryError:
383 return Response(_('Empty repository'))
383 return Response(_('Empty repository'))
384
384
385 # we used a ref, or a shorter version, lets redirect client ot use explicit hash
385 # we used a ref, or a shorter version, lets redirect client ot use explicit hash
386 if commit_id != commit.raw_id:
386 if commit_id != commit.raw_id:
387 fname=f'{commit.raw_id}{ext}'
387 fname=f'{commit.raw_id}{ext}'
388 raise HTTPFound(self.request.current_route_path(fname=fname))
388 raise HTTPFound(self.request.current_route_path(fname=fname))
389
389
390 try:
390 try:
391 at_path = commit.get_node(at_path).path or default_at_path
391 at_path = commit.get_node(at_path).path or default_at_path
392 except Exception:
392 except Exception:
393 return Response(_('No node at path {} for this repository').format(h.escape(at_path)))
393 return Response(_('No node at path {} for this repository').format(h.escape(at_path)))
394
394
395 path_sha = get_path_sha(at_path)
395 path_sha = get_path_sha(at_path)
396
396
397 # used for cache etc, consistent unique archive name
397 # used for cache etc, consistent unique archive name
398 archive_name_key = get_archive_name(
398 archive_name_key = get_archive_name(
399 self.db_repo.repo_id, self.db_repo_name, commit_sha=commit.short_id, ext=ext, subrepos=subrepos,
399 self.db_repo.repo_id, self.db_repo_name, commit_sha=commit.short_id, ext=ext, subrepos=subrepos,
400 path_sha=path_sha, with_hash=True)
400 path_sha=path_sha, with_hash=True)
401
401
402 if not with_hash:
402 if not with_hash:
403 path_sha = ''
403 path_sha = ''
404
404
405 # what end client gets served
405 # what end client gets served
406 response_archive_name = get_archive_name(
406 response_archive_name = get_archive_name(
407 self.db_repo.repo_id, self.db_repo_name, commit_sha=commit.short_id, ext=ext, subrepos=subrepos,
407 self.db_repo.repo_id, self.db_repo_name, commit_sha=commit.short_id, ext=ext, subrepos=subrepos,
408 path_sha=path_sha, with_hash=with_hash)
408 path_sha=path_sha, with_hash=with_hash)
409
409
410 # remove extension from our archive directory name
410 # remove extension from our archive directory name
411 archive_dir_name = response_archive_name[:-len(ext)]
411 archive_dir_name = response_archive_name[:-len(ext)]
412
412
413 archive_cache_disable = self.request.GET.get('no_cache')
413 archive_cache_disable = self.request.GET.get('no_cache')
414
414
415 d_cache = get_archival_cache_store(config=CONFIG)
415 d_cache = get_archival_cache_store(config=CONFIG)
416
416
417 # NOTE: we get the config to pass to a call to lazy-init the SAME type of cache on vcsserver
417 # NOTE: we get the config to pass to a call to lazy-init the SAME type of cache on vcsserver
418 d_cache_conf = get_archival_config(config=CONFIG)
418 d_cache_conf = get_archival_config(config=CONFIG)
419
419
420 reentrant_lock_key = archive_name_key + '.lock'
420 reentrant_lock_key = archive_name_key + '.lock'
421 with ReentrantLock(d_cache, reentrant_lock_key):
421 with ReentrantLock(d_cache, reentrant_lock_key):
422 # This is also a cache key
422 # This is also a cache key
423 use_cached_archive = False
423 use_cached_archive = False
424 if archive_name_key in d_cache and not archive_cache_disable:
424 if archive_name_key in d_cache and not archive_cache_disable:
425 reader, tag = d_cache.get(archive_name_key, read=True, tag=True, retry=True)
425 reader, tag = d_cache.get(archive_name_key, read=True, tag=True, retry=True)
426 use_cached_archive = True
426 use_cached_archive = True
427 log.debug('Found cached archive as key=%s tag=%s, serving archive from cache reader=%s',
427 log.debug('Found cached archive as key=%s tag=%s, serving archive from cache reader=%s',
428 archive_name_key, tag, reader.name)
428 archive_name_key, tag, reader.name)
429 else:
429 else:
430 reader = None
430 reader = None
431 log.debug('Archive with key=%s is not yet cached, creating one now...', archive_name_key)
431 log.debug('Archive with key=%s is not yet cached, creating one now...', archive_name_key)
432
432
433 # generate new archive, as previous was not found in the cache
433 # generate new archive, as previous was not found in the cache
434 if not reader:
434 if not reader:
435
435
436 try:
436 try:
437 commit.archive_repo(archive_name_key, archive_dir_name=archive_dir_name,
437 commit.archive_repo(archive_name_key, archive_dir_name=archive_dir_name,
438 kind=fileformat, subrepos=subrepos,
438 kind=fileformat, subrepos=subrepos,
439 archive_at_path=at_path, cache_config=d_cache_conf)
439 archive_at_path=at_path, cache_config=d_cache_conf)
440 except ImproperArchiveTypeError:
440 except ImproperArchiveTypeError:
441 return _('Unknown archive type')
441 return _('Unknown archive type')
442
442
443 reader, tag = d_cache.get(archive_name_key, read=True, tag=True, retry=True)
443 reader, tag = d_cache.get(archive_name_key, read=True, tag=True, retry=True)
444
444
445 if not reader:
445 if not reader:
446 raise ValueError('archive cache reader is empty, failed to fetch file from distributed archive cache')
446 raise ValueError('archive cache reader is empty, failed to fetch file from distributed archive cache')
447
447
448 def archive_iterator(_reader, block_size: int = 4096*512):
448 def archive_iterator(_reader, block_size: int = 4096*512):
449 # 4096 * 64 = 64KB
449 # 4096 * 64 = 64KB
450 while 1:
450 while 1:
451 data = _reader.read(block_size)
451 data = _reader.read(block_size)
452 if not data:
452 if not data:
453 break
453 break
454 yield data
454 yield data
455
455
456 response = Response(app_iter=archive_iterator(reader))
456 response = Response(app_iter=archive_iterator(reader))
457 response.content_disposition = f'attachment; filename={response_archive_name}'
457 response.content_disposition = f'attachment; filename={response_archive_name}'
458 response.content_type = str(content_type)
458 response.content_type = str(content_type)
459
459
460 try:
460 try:
461 return response
461 return response
462 finally:
462 finally:
463 # store download action
463 # store download action
464 audit_logger.store_web(
464 audit_logger.store_web(
465 'repo.archive.download', action_data={
465 'repo.archive.download', action_data={
466 'user_agent': self.request.user_agent,
466 'user_agent': self.request.user_agent,
467 'archive_name': archive_name_key,
467 'archive_name': archive_name_key,
468 'archive_spec': fname,
468 'archive_spec': fname,
469 'archive_cached': use_cached_archive},
469 'archive_cached': use_cached_archive},
470 user=self._rhodecode_user,
470 user=self._rhodecode_user,
471 repo=self.db_repo,
471 repo=self.db_repo,
472 commit=True
472 commit=True
473 )
473 )
474
474
475 def _get_file_node(self, commit_id, f_path):
475 def _get_file_node(self, commit_id, f_path):
476 if commit_id not in ['', None, 'None', '0' * 12, '0' * 40]:
476 if commit_id not in ['', None, 'None', '0' * 12, '0' * 40]:
477 commit = self.rhodecode_vcs_repo.get_commit(commit_id=commit_id)
477 commit = self.rhodecode_vcs_repo.get_commit(commit_id=commit_id)
478 try:
478 try:
479 node = commit.get_node(f_path)
479 node = commit.get_node(f_path)
480 if node.is_dir():
480 if node.is_dir():
481 raise NodeError(f'{node} path is a {type(node)} not a file')
481 raise NodeError(f'{node} path is a {type(node)} not a file')
482 except NodeDoesNotExistError:
482 except NodeDoesNotExistError:
483 commit = EmptyCommit(
483 commit = EmptyCommit(
484 commit_id=commit_id,
484 commit_id=commit_id,
485 idx=commit.idx,
485 idx=commit.idx,
486 repo=commit.repository,
486 repo=commit.repository,
487 alias=commit.repository.alias,
487 alias=commit.repository.alias,
488 message=commit.message,
488 message=commit.message,
489 author=commit.author,
489 author=commit.author,
490 date=commit.date)
490 date=commit.date)
491 node = FileNode(safe_bytes(f_path), b'', commit=commit)
491 node = FileNode(safe_bytes(f_path), b'', commit=commit)
492 else:
492 else:
493 commit = EmptyCommit(
493 commit = EmptyCommit(
494 repo=self.rhodecode_vcs_repo,
494 repo=self.rhodecode_vcs_repo,
495 alias=self.rhodecode_vcs_repo.alias)
495 alias=self.rhodecode_vcs_repo.alias)
496 node = FileNode(safe_bytes(f_path), b'', commit=commit)
496 node = FileNode(safe_bytes(f_path), b'', commit=commit)
497 return node
497 return node
498
498
499 @LoginRequired()
499 @LoginRequired()
500 @HasRepoPermissionAnyDecorator(
500 @HasRepoPermissionAnyDecorator(
501 'repository.read', 'repository.write', 'repository.admin')
501 'repository.read', 'repository.write', 'repository.admin')
502 def repo_files_diff(self):
502 def repo_files_diff(self):
503 c = self.load_default_context()
503 c = self.load_default_context()
504 f_path = self._get_f_path(self.request.matchdict)
504 f_path = self._get_f_path(self.request.matchdict)
505 diff1 = self.request.GET.get('diff1', '')
505 diff1 = self.request.GET.get('diff1', '')
506 diff2 = self.request.GET.get('diff2', '')
506 diff2 = self.request.GET.get('diff2', '')
507
507
508 path1, diff1 = parse_path_ref(diff1, default_path=f_path)
508 path1, diff1 = parse_path_ref(diff1, default_path=f_path)
509
509
510 ignore_whitespace = str2bool(self.request.GET.get('ignorews'))
510 ignore_whitespace = str2bool(self.request.GET.get('ignorews'))
511 line_context = self.request.GET.get('context', 3)
511 line_context = self.request.GET.get('context', 3)
512
512
513 if not any((diff1, diff2)):
513 if not any((diff1, diff2)):
514 h.flash(
514 h.flash(
515 'Need query parameter "diff1" or "diff2" to generate a diff.',
515 'Need query parameter "diff1" or "diff2" to generate a diff.',
516 category='error')
516 category='error')
517 raise HTTPBadRequest()
517 raise HTTPBadRequest()
518
518
519 c.action = self.request.GET.get('diff')
519 c.action = self.request.GET.get('diff')
520 if c.action not in ['download', 'raw']:
520 if c.action not in ['download', 'raw']:
521 compare_url = h.route_path(
521 compare_url = h.route_path(
522 'repo_compare',
522 'repo_compare',
523 repo_name=self.db_repo_name,
523 repo_name=self.db_repo_name,
524 source_ref_type='rev',
524 source_ref_type='rev',
525 source_ref=diff1,
525 source_ref=diff1,
526 target_repo=self.db_repo_name,
526 target_repo=self.db_repo_name,
527 target_ref_type='rev',
527 target_ref_type='rev',
528 target_ref=diff2,
528 target_ref=diff2,
529 _query=dict(f_path=f_path))
529 _query=dict(f_path=f_path))
530 # redirect to new view if we render diff
530 # redirect to new view if we render diff
531 raise HTTPFound(compare_url)
531 raise HTTPFound(compare_url)
532
532
533 try:
533 try:
534 node1 = self._get_file_node(diff1, path1)
534 node1 = self._get_file_node(diff1, path1)
535 node2 = self._get_file_node(diff2, f_path)
535 node2 = self._get_file_node(diff2, f_path)
536 except (RepositoryError, NodeError):
536 except (RepositoryError, NodeError):
537 log.exception("Exception while trying to get node from repository")
537 log.exception("Exception while trying to get node from repository")
538 raise HTTPFound(
538 raise HTTPFound(
539 h.route_path('repo_files', repo_name=self.db_repo_name,
539 h.route_path('repo_files', repo_name=self.db_repo_name,
540 commit_id='tip', f_path=f_path))
540 commit_id='tip', f_path=f_path))
541
541
542 if all(isinstance(node.commit, EmptyCommit)
542 if all(isinstance(node.commit, EmptyCommit)
543 for node in (node1, node2)):
543 for node in (node1, node2)):
544 raise HTTPNotFound()
544 raise HTTPNotFound()
545
545
546 c.commit_1 = node1.commit
546 c.commit_1 = node1.commit
547 c.commit_2 = node2.commit
547 c.commit_2 = node2.commit
548
548
549 if c.action == 'download':
549 if c.action == 'download':
550 _diff = diffs.get_gitdiff(node1, node2,
550 _diff = diffs.get_gitdiff(node1, node2,
551 ignore_whitespace=ignore_whitespace,
551 ignore_whitespace=ignore_whitespace,
552 context=line_context)
552 context=line_context)
553 # NOTE: this was using diff_format='gitdiff'
553 # NOTE: this was using diff_format='gitdiff'
554 diff = diffs.DiffProcessor(_diff, diff_format='newdiff')
554 diff = diffs.DiffProcessor(_diff, diff_format='newdiff')
555
555
556 response = Response(self.path_filter.get_raw_patch(diff))
556 response = Response(self.path_filter.get_raw_patch(diff))
557 response.content_type = 'text/plain'
557 response.content_type = 'text/plain'
558 response.content_disposition = (
558 response.content_disposition = (
559 f'attachment; filename={f_path}_{diff1}_vs_{diff2}.diff'
559 f'attachment; filename={f_path}_{diff1}_vs_{diff2}.diff'
560 )
560 )
561 charset = self._get_default_encoding(c)
561 charset = self._get_default_encoding(c)
562 if charset:
562 if charset:
563 response.charset = charset
563 response.charset = charset
564 return response
564 return response
565
565
566 elif c.action == 'raw':
566 elif c.action == 'raw':
567 _diff = diffs.get_gitdiff(node1, node2,
567 _diff = diffs.get_gitdiff(node1, node2,
568 ignore_whitespace=ignore_whitespace,
568 ignore_whitespace=ignore_whitespace,
569 context=line_context)
569 context=line_context)
570 # NOTE: this was using diff_format='gitdiff'
570 # NOTE: this was using diff_format='gitdiff'
571 diff = diffs.DiffProcessor(_diff, diff_format='newdiff')
571 diff = diffs.DiffProcessor(_diff, diff_format='newdiff')
572
572
573 response = Response(self.path_filter.get_raw_patch(diff))
573 response = Response(self.path_filter.get_raw_patch(diff))
574 response.content_type = 'text/plain'
574 response.content_type = 'text/plain'
575 charset = self._get_default_encoding(c)
575 charset = self._get_default_encoding(c)
576 if charset:
576 if charset:
577 response.charset = charset
577 response.charset = charset
578 return response
578 return response
579
579
580 # in case we ever end up here
580 # in case we ever end up here
581 raise HTTPNotFound()
581 raise HTTPNotFound()
582
582
583 @LoginRequired()
583 @LoginRequired()
584 @HasRepoPermissionAnyDecorator(
584 @HasRepoPermissionAnyDecorator(
585 'repository.read', 'repository.write', 'repository.admin')
585 'repository.read', 'repository.write', 'repository.admin')
586 def repo_files_diff_2way_redirect(self):
586 def repo_files_diff_2way_redirect(self):
587 """
587 """
588 Kept only to make OLD links work
588 Kept only to make OLD links work
589 """
589 """
590 f_path = self._get_f_path_unchecked(self.request.matchdict)
590 f_path = self._get_f_path_unchecked(self.request.matchdict)
591 diff1 = self.request.GET.get('diff1', '')
591 diff1 = self.request.GET.get('diff1', '')
592 diff2 = self.request.GET.get('diff2', '')
592 diff2 = self.request.GET.get('diff2', '')
593
593
594 if not any((diff1, diff2)):
594 if not any((diff1, diff2)):
595 h.flash(
595 h.flash(
596 'Need query parameter "diff1" or "diff2" to generate a diff.',
596 'Need query parameter "diff1" or "diff2" to generate a diff.',
597 category='error')
597 category='error')
598 raise HTTPBadRequest()
598 raise HTTPBadRequest()
599
599
600 compare_url = h.route_path(
600 compare_url = h.route_path(
601 'repo_compare',
601 'repo_compare',
602 repo_name=self.db_repo_name,
602 repo_name=self.db_repo_name,
603 source_ref_type='rev',
603 source_ref_type='rev',
604 source_ref=diff1,
604 source_ref=diff1,
605 target_ref_type='rev',
605 target_ref_type='rev',
606 target_ref=diff2,
606 target_ref=diff2,
607 _query=dict(f_path=f_path, diffmode='sideside',
607 _query=dict(f_path=f_path, diffmode='sideside',
608 target_repo=self.db_repo_name,))
608 target_repo=self.db_repo_name,))
609 raise HTTPFound(compare_url)
609 raise HTTPFound(compare_url)
610
610
611 @LoginRequired()
611 @LoginRequired()
612 def repo_files_default_commit_redirect(self):
612 def repo_files_default_commit_redirect(self):
613 """
613 """
614 Special page that redirects to the landing page of files based on the default
614 Special page that redirects to the landing page of files based on the default
615 commit for repository
615 commit for repository
616 """
616 """
617 c = self.load_default_context()
617 c = self.load_default_context()
618 ref_name = c.rhodecode_db_repo.landing_ref_name
618 ref_name = c.rhodecode_db_repo.landing_ref_name
619 landing_url = h.repo_files_by_ref_url(
619 landing_url = h.repo_files_by_ref_url(
620 c.rhodecode_db_repo.repo_name,
620 c.rhodecode_db_repo.repo_name,
621 c.rhodecode_db_repo.repo_type,
621 c.rhodecode_db_repo.repo_type,
622 f_path='',
622 f_path='',
623 ref_name=ref_name,
623 ref_name=ref_name,
624 commit_id='tip',
624 commit_id='tip',
625 query=dict(at=ref_name)
625 query=dict(at=ref_name)
626 )
626 )
627
627
628 raise HTTPFound(landing_url)
628 raise HTTPFound(landing_url)
629
629
630 @LoginRequired()
630 @LoginRequired()
631 @HasRepoPermissionAnyDecorator(
631 @HasRepoPermissionAnyDecorator(
632 'repository.read', 'repository.write', 'repository.admin')
632 'repository.read', 'repository.write', 'repository.admin')
633 def repo_files(self):
633 def repo_files(self):
634 c = self.load_default_context()
634 c = self.load_default_context()
635
635
636 view_name = getattr(self.request.matched_route, 'name', None)
636 view_name = getattr(self.request.matched_route, 'name', None)
637
637
638 c.annotate = view_name == 'repo_files:annotated'
638 c.annotate = view_name == 'repo_files:annotated'
639 # default is false, but .rst/.md files later are auto rendered, we can
639 # default is false, but .rst/.md files later are auto rendered, we can
640 # overwrite auto rendering by setting this GET flag
640 # overwrite auto rendering by setting this GET flag
641 c.renderer = view_name == 'repo_files:rendered' or not self.request.GET.get('no-render', False)
641 c.renderer = view_name == 'repo_files:rendered' or not self.request.GET.get('no-render', False)
642
642
643 commit_id, f_path = self._get_commit_and_path()
643 commit_id, f_path = self._get_commit_and_path()
644
644
645 c.commit = self._get_commit_or_redirect(commit_id)
645 c.commit = self._get_commit_or_redirect(commit_id)
646 c.branch = self.request.GET.get('branch', None)
646 c.branch = self.request.GET.get('branch', None)
647 c.f_path = f_path
647 c.f_path = f_path
648 at_rev = self.request.GET.get('at')
648 at_rev = self.request.GET.get('at')
649
649
650 # prev link
651 try:
652 prev_commit = c.commit.prev(c.branch)
653 c.prev_commit = prev_commit
654 c.url_prev = h.route_path(
655 'repo_files', repo_name=self.db_repo_name,
656 commit_id=prev_commit.raw_id, f_path=f_path)
657 if c.branch:
658 c.url_prev += '?branch=%s' % c.branch
659 except (CommitDoesNotExistError, VCSError):
660 c.url_prev = '#'
661 c.prev_commit = EmptyCommit()
662
663 # next link
664 try:
665 next_commit = c.commit.next(c.branch)
666 c.next_commit = next_commit
667 c.url_next = h.route_path(
668 'repo_files', repo_name=self.db_repo_name,
669 commit_id=next_commit.raw_id, f_path=f_path)
670 if c.branch:
671 c.url_next += '?branch=%s' % c.branch
672 except (CommitDoesNotExistError, VCSError):
673 c.url_next = '#'
674 c.next_commit = EmptyCommit()
675
676 # files or dirs
650 # files or dirs
677 try:
651 try:
678 c.file = c.commit.get_node(f_path, pre_load=['is_binary', 'size', 'data'])
652 c.file = c.commit.get_node(f_path, pre_load=['is_binary', 'size', 'data'])
679
653
680 c.file_author = True
654 c.file_author = True
681 c.file_tree = ''
655 c.file_tree = ''
682
656
657 # prev link
658 try:
659 prev_commit = c.commit.prev(c.branch)
660 c.prev_commit = prev_commit
661 c.url_prev = h.route_path(
662 'repo_files', repo_name=self.db_repo_name,
663 commit_id=prev_commit.raw_id, f_path=f_path)
664 if c.branch:
665 c.url_prev += '?branch=%s' % c.branch
666 except (CommitDoesNotExistError, VCSError):
667 c.url_prev = '#'
668 c.prev_commit = EmptyCommit()
669
670 # next link
671 try:
672 next_commit = c.commit.next(c.branch)
673 c.next_commit = next_commit
674 c.url_next = h.route_path(
675 'repo_files', repo_name=self.db_repo_name,
676 commit_id=next_commit.raw_id, f_path=f_path)
677 if c.branch:
678 c.url_next += '?branch=%s' % c.branch
679 except (CommitDoesNotExistError, VCSError):
680 c.url_next = '#'
681 c.next_commit = EmptyCommit()
682
683 # load file content
683 # load file content
684 if c.file.is_file():
684 if c.file.is_file():
685
685 c.lf_node = {}
686 c.lf_node = {}
686
687
687 has_lf_enabled = self._is_lf_enabled(self.db_repo)
688 has_lf_enabled = self._is_lf_enabled(self.db_repo)
688 if has_lf_enabled:
689 if has_lf_enabled:
689 c.lf_node = c.file.get_largefile_node()
690 c.lf_node = c.file.get_largefile_node()
690
691
691 c.file_source_page = 'true'
692 c.file_source_page = 'true'
692 c.file_last_commit = c.file.last_commit
693 c.file_last_commit = c.file.last_commit
693
694
694 c.file_size_too_big = c.file.size > c.visual.cut_off_limit_file
695 c.file_size_too_big = c.file.size > c.visual.cut_off_limit_file
695
696
696 if not (c.file_size_too_big or c.file.is_binary):
697 if not (c.file_size_too_big or c.file.is_binary):
697 if c.annotate: # annotation has precedence over renderer
698 if c.annotate: # annotation has precedence over renderer
698 c.annotated_lines = filenode_as_annotated_lines_tokens(
699 c.annotated_lines = filenode_as_annotated_lines_tokens(
699 c.file
700 c.file
700 )
701 )
701 else:
702 else:
702 c.renderer = (
703 c.renderer = (
703 c.renderer and h.renderer_from_filename(c.file.path)
704 c.renderer and h.renderer_from_filename(c.file.path)
704 )
705 )
705 if not c.renderer:
706 if not c.renderer:
706 c.lines = filenode_as_lines_tokens(c.file)
707 c.lines = filenode_as_lines_tokens(c.file)
707
708
708 _branch_name, _sha_commit_id, is_head = \
709 _branch_name, _sha_commit_id, is_head = \
709 self._is_valid_head(commit_id, self.rhodecode_vcs_repo,
710 self._is_valid_head(commit_id, self.rhodecode_vcs_repo,
710 landing_ref=self.db_repo.landing_ref_name)
711 landing_ref=self.db_repo.landing_ref_name)
711 c.on_branch_head = is_head
712 c.on_branch_head = is_head
712
713
713 branch = c.commit.branch if (
714 branch = c.commit.branch if (
714 c.commit.branch and '/' not in c.commit.branch) else None
715 c.commit.branch and '/' not in c.commit.branch) else None
715 c.branch_or_raw_id = branch or c.commit.raw_id
716 c.branch_or_raw_id = branch or c.commit.raw_id
716 c.branch_name = c.commit.branch or h.short_id(c.commit.raw_id)
717 c.branch_name = c.commit.branch or h.short_id(c.commit.raw_id)
717
718
718 author = c.file_last_commit.author
719 author = c.file_last_commit.author
719 c.authors = [[
720 c.authors = [[
720 h.email(author),
721 h.email(author),
721 h.person(author, 'username_or_name_or_email'),
722 h.person(author, 'username_or_name_or_email'),
722 1
723 1
723 ]]
724 ]]
724
725
725 else: # load tree content at path
726 else: # load tree content at path
726 c.file_source_page = 'false'
727 c.file_source_page = 'false'
727 c.authors = []
728 c.authors = []
728 # this loads a simple tree without metadata to speed things up
729 # this loads a simple tree without metadata to speed things up
729 # later via ajax we call repo_nodetree_full and fetch whole
730 # later via ajax we call repo_nodetree_full and fetch whole
730 c.file_tree = self._get_tree_at_commit(c, c.commit.raw_id, f_path, at_rev=at_rev)
731 c.file_tree = self._get_tree_at_commit(c, c.commit.raw_id, f_path, at_rev=at_rev)
731
732
732 c.readme_data, c.readme_file = \
733 c.readme_data, c.readme_file = \
733 self._get_readme_data(self.db_repo, c.visual.default_renderer,
734 self._get_readme_data(self.db_repo, c.visual.default_renderer,
734 c.commit.raw_id, f_path)
735 c.commit.raw_id, f_path)
735
736
736 except RepositoryError as e:
737 except RepositoryError as e:
737 h.flash(h.escape(safe_str(e)), category='error')
738 h.flash(h.escape(safe_str(e)), category='error')
738 raise HTTPNotFound()
739 raise HTTPNotFound()
739
740
740 if self.request.environ.get('HTTP_X_PJAX'):
741 if self.request.environ.get('HTTP_X_PJAX'):
741 html = render('rhodecode:templates/files/files_pjax.mako',
742 html = render('rhodecode:templates/files/files_pjax.mako',
742 self._get_template_context(c), self.request)
743 self._get_template_context(c), self.request)
743 else:
744 else:
744 html = render('rhodecode:templates/files/files.mako',
745 html = render('rhodecode:templates/files/files.mako',
745 self._get_template_context(c), self.request)
746 self._get_template_context(c), self.request)
746 return Response(html)
747 return Response(html)
747
748
748 @HasRepoPermissionAnyDecorator(
749 @HasRepoPermissionAnyDecorator(
749 'repository.read', 'repository.write', 'repository.admin')
750 'repository.read', 'repository.write', 'repository.admin')
750 def repo_files_annotated_previous(self):
751 def repo_files_annotated_previous(self):
751 self.load_default_context()
752 self.load_default_context()
752
753
753 commit_id, f_path = self._get_commit_and_path()
754 commit_id, f_path = self._get_commit_and_path()
754 commit = self._get_commit_or_redirect(commit_id)
755 commit = self._get_commit_or_redirect(commit_id)
755 prev_commit_id = commit.raw_id
756 prev_commit_id = commit.raw_id
756 line_anchor = self.request.GET.get('line_anchor')
757 line_anchor = self.request.GET.get('line_anchor')
757 is_file = False
758 is_file = False
758 try:
759 try:
759 _file = commit.get_node(f_path)
760 _file = commit.get_node(f_path)
760 is_file = _file.is_file()
761 is_file = _file.is_file()
761 except (NodeDoesNotExistError, CommitDoesNotExistError, VCSError):
762 except (NodeDoesNotExistError, CommitDoesNotExistError, VCSError):
762 pass
763 pass
763
764
764 if is_file:
765 if is_file:
765 history = commit.get_path_history(f_path)
766 history = commit.get_path_history(f_path)
766 prev_commit_id = history[1].raw_id \
767 prev_commit_id = history[1].raw_id \
767 if len(history) > 1 else prev_commit_id
768 if len(history) > 1 else prev_commit_id
768 prev_url = h.route_path(
769 prev_url = h.route_path(
769 'repo_files:annotated', repo_name=self.db_repo_name,
770 'repo_files:annotated', repo_name=self.db_repo_name,
770 commit_id=prev_commit_id, f_path=f_path,
771 commit_id=prev_commit_id, f_path=f_path,
771 _anchor=f'L{line_anchor}')
772 _anchor=f'L{line_anchor}')
772
773
773 raise HTTPFound(prev_url)
774 raise HTTPFound(prev_url)
774
775
775 @LoginRequired()
776 @LoginRequired()
776 @HasRepoPermissionAnyDecorator(
777 @HasRepoPermissionAnyDecorator(
777 'repository.read', 'repository.write', 'repository.admin')
778 'repository.read', 'repository.write', 'repository.admin')
778 def repo_nodetree_full(self):
779 def repo_nodetree_full(self):
779 """
780 """
780 Returns rendered html of file tree that contains commit date,
781 Returns rendered html of file tree that contains commit date,
781 author, commit_id for the specified combination of
782 author, commit_id for the specified combination of
782 repo, commit_id and file path
783 repo, commit_id and file path
783 """
784 """
784 c = self.load_default_context()
785 c = self.load_default_context()
785
786
786 commit_id, f_path = self._get_commit_and_path()
787 commit_id, f_path = self._get_commit_and_path()
787 commit = self._get_commit_or_redirect(commit_id)
788 commit = self._get_commit_or_redirect(commit_id)
788 try:
789 try:
789 dir_node = commit.get_node(f_path)
790 dir_node = commit.get_node(f_path)
790 except RepositoryError as e:
791 except RepositoryError as e:
791 return Response(f'error: {h.escape(safe_str(e))}')
792 return Response(f'error: {h.escape(safe_str(e))}')
792
793
793 if dir_node.is_file():
794 if dir_node.is_file():
794 return Response('')
795 return Response('')
795
796
796 c.file = dir_node
797 c.file = dir_node
797 c.commit = commit
798 c.commit = commit
798 at_rev = self.request.GET.get('at')
799 at_rev = self.request.GET.get('at')
799
800
800 html = self._get_tree_at_commit(
801 html = self._get_tree_at_commit(
801 c, commit.raw_id, dir_node.path, full_load=True, at_rev=at_rev)
802 c, commit.raw_id, dir_node.path, full_load=True, at_rev=at_rev)
802
803
803 return Response(html)
804 return Response(html)
804
805
805 def _get_attachement_headers(self, f_path):
806 def _get_attachement_headers(self, f_path):
806 f_name = safe_str(f_path.split(Repository.NAME_SEP)[-1])
807 f_name = safe_str(f_path.split(Repository.NAME_SEP)[-1])
807 safe_path = f_name.replace('"', '\\"')
808 safe_path = f_name.replace('"', '\\"')
808 encoded_path = urllib.parse.quote(f_name)
809 encoded_path = urllib.parse.quote(f_name)
809
810
810 return "attachment; " \
811 return "attachment; " \
811 "filename=\"{}\"; " \
812 "filename=\"{}\"; " \
812 "filename*=UTF-8\'\'{}".format(safe_path, encoded_path)
813 "filename*=UTF-8\'\'{}".format(safe_path, encoded_path)
813
814
814 @LoginRequired()
815 @LoginRequired()
815 @HasRepoPermissionAnyDecorator(
816 @HasRepoPermissionAnyDecorator(
816 'repository.read', 'repository.write', 'repository.admin')
817 'repository.read', 'repository.write', 'repository.admin')
817 def repo_file_raw(self):
818 def repo_file_raw(self):
818 """
819 """
819 Action for show as raw, some mimetypes are "rendered",
820 Action for show as raw, some mimetypes are "rendered",
820 those include images, icons.
821 those include images, icons.
821 """
822 """
822 c = self.load_default_context()
823 c = self.load_default_context()
823
824
824 commit_id, f_path = self._get_commit_and_path()
825 commit_id, f_path = self._get_commit_and_path()
825 commit = self._get_commit_or_redirect(commit_id)
826 commit = self._get_commit_or_redirect(commit_id)
826 file_node = self._get_filenode_or_redirect(commit, f_path)
827 file_node = self._get_filenode_or_redirect(commit, f_path)
827
828
828 raw_mimetype_mapping = {
829 raw_mimetype_mapping = {
829 # map original mimetype to a mimetype used for "show as raw"
830 # map original mimetype to a mimetype used for "show as raw"
830 # you can also provide a content-disposition to override the
831 # you can also provide a content-disposition to override the
831 # default "attachment" disposition.
832 # default "attachment" disposition.
832 # orig_type: (new_type, new_dispo)
833 # orig_type: (new_type, new_dispo)
833
834
834 # show images inline:
835 # show images inline:
835 # Do not re-add SVG: it is unsafe and permits XSS attacks. One can
836 # Do not re-add SVG: it is unsafe and permits XSS attacks. One can
836 # for example render an SVG with javascript inside or even render
837 # for example render an SVG with javascript inside or even render
837 # HTML.
838 # HTML.
838 'image/x-icon': ('image/x-icon', 'inline'),
839 'image/x-icon': ('image/x-icon', 'inline'),
839 'image/png': ('image/png', 'inline'),
840 'image/png': ('image/png', 'inline'),
840 'image/gif': ('image/gif', 'inline'),
841 'image/gif': ('image/gif', 'inline'),
841 'image/jpeg': ('image/jpeg', 'inline'),
842 'image/jpeg': ('image/jpeg', 'inline'),
842 'application/pdf': ('application/pdf', 'inline'),
843 'application/pdf': ('application/pdf', 'inline'),
843 }
844 }
844
845
845 mimetype = file_node.mimetype
846 mimetype = file_node.mimetype
846 try:
847 try:
847 mimetype, disposition = raw_mimetype_mapping[mimetype]
848 mimetype, disposition = raw_mimetype_mapping[mimetype]
848 except KeyError:
849 except KeyError:
849 # we don't know anything special about this, handle it safely
850 # we don't know anything special about this, handle it safely
850 if file_node.is_binary:
851 if file_node.is_binary:
851 # do same as download raw for binary files
852 # do same as download raw for binary files
852 mimetype, disposition = 'application/octet-stream', 'attachment'
853 mimetype, disposition = 'application/octet-stream', 'attachment'
853 else:
854 else:
854 # do not just use the original mimetype, but force text/plain,
855 # do not just use the original mimetype, but force text/plain,
855 # otherwise it would serve text/html and that might be unsafe.
856 # otherwise it would serve text/html and that might be unsafe.
856 # Note: underlying vcs library fakes text/plain mimetype if the
857 # Note: underlying vcs library fakes text/plain mimetype if the
857 # mimetype can not be determined and it thinks it is not
858 # mimetype can not be determined and it thinks it is not
858 # binary.This might lead to erroneous text display in some
859 # binary.This might lead to erroneous text display in some
859 # cases, but helps in other cases, like with text files
860 # cases, but helps in other cases, like with text files
860 # without extension.
861 # without extension.
861 mimetype, disposition = 'text/plain', 'inline'
862 mimetype, disposition = 'text/plain', 'inline'
862
863
863 if disposition == 'attachment':
864 if disposition == 'attachment':
864 disposition = self._get_attachement_headers(f_path)
865 disposition = self._get_attachement_headers(f_path)
865
866
866 stream_content = file_node.stream_bytes()
867 stream_content = file_node.stream_bytes()
867
868
868 response = Response(app_iter=stream_content)
869 response = Response(app_iter=stream_content)
869 response.content_disposition = disposition
870 response.content_disposition = disposition
870 response.content_type = mimetype
871 response.content_type = mimetype
871
872
872 charset = self._get_default_encoding(c)
873 charset = self._get_default_encoding(c)
873 if charset:
874 if charset:
874 response.charset = charset
875 response.charset = charset
875
876
876 return response
877 return response
877
878
878 @LoginRequired()
879 @LoginRequired()
879 @HasRepoPermissionAnyDecorator(
880 @HasRepoPermissionAnyDecorator(
880 'repository.read', 'repository.write', 'repository.admin')
881 'repository.read', 'repository.write', 'repository.admin')
881 def repo_file_download(self):
882 def repo_file_download(self):
882 c = self.load_default_context()
883 c = self.load_default_context()
883
884
884 commit_id, f_path = self._get_commit_and_path()
885 commit_id, f_path = self._get_commit_and_path()
885 commit = self._get_commit_or_redirect(commit_id)
886 commit = self._get_commit_or_redirect(commit_id)
886 file_node = self._get_filenode_or_redirect(commit, f_path)
887 file_node = self._get_filenode_or_redirect(commit, f_path)
887
888
888 if self.request.GET.get('lf'):
889 if self.request.GET.get('lf'):
889 # only if lf get flag is passed, we download this file
890 # only if lf get flag is passed, we download this file
890 # as LFS/Largefile
891 # as LFS/Largefile
891 lf_node = file_node.get_largefile_node()
892 lf_node = file_node.get_largefile_node()
892 if lf_node:
893 if lf_node:
893 # overwrite our pointer with the REAL large-file
894 # overwrite our pointer with the REAL large-file
894 file_node = lf_node
895 file_node = lf_node
895
896
896 disposition = self._get_attachement_headers(f_path)
897 disposition = self._get_attachement_headers(f_path)
897
898
898 stream_content = file_node.stream_bytes()
899 stream_content = file_node.stream_bytes()
899
900
900 response = Response(app_iter=stream_content)
901 response = Response(app_iter=stream_content)
901 response.content_disposition = disposition
902 response.content_disposition = disposition
902 response.content_type = file_node.mimetype
903 response.content_type = file_node.mimetype
903
904
904 charset = self._get_default_encoding(c)
905 charset = self._get_default_encoding(c)
905 if charset:
906 if charset:
906 response.charset = charset
907 response.charset = charset
907
908
908 return response
909 return response
909
910
910 def _get_nodelist_at_commit(self, repo_name, repo_id, commit_id, f_path):
911 def _get_nodelist_at_commit(self, repo_name, repo_id, commit_id, f_path):
911
912
912 cache_seconds = safe_int(
913 cache_seconds = safe_int(
913 rhodecode.CONFIG.get('rc_cache.cache_repo.expiration_time'))
914 rhodecode.CONFIG.get('rc_cache.cache_repo.expiration_time'))
914 cache_on = cache_seconds > 0
915 cache_on = cache_seconds > 0
915 log.debug(
916 log.debug(
916 'Computing FILE SEARCH for repo_id %s commit_id `%s` and path `%s`'
917 'Computing FILE SEARCH for repo_id %s commit_id `%s` and path `%s`'
917 'with caching: %s[TTL: %ss]' % (
918 'with caching: %s[TTL: %ss]' % (
918 repo_id, commit_id, f_path, cache_on, cache_seconds or 0))
919 repo_id, commit_id, f_path, cache_on, cache_seconds or 0))
919
920
920 cache_namespace_uid = f'repo.{repo_id}'
921 cache_namespace_uid = f'repo.{repo_id}'
921 region = rc_cache.get_or_create_region('cache_repo', cache_namespace_uid)
922 region = rc_cache.get_or_create_region('cache_repo', cache_namespace_uid)
922
923
923 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid, condition=cache_on)
924 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid, condition=cache_on)
924 def compute_file_search(_name_hash, _repo_id, _commit_id, _f_path):
925 def compute_file_search(_name_hash, _repo_id, _commit_id, _f_path):
925 log.debug('Generating cached nodelist for repo_id:%s, %s, %s',
926 log.debug('Generating cached nodelist for repo_id:%s, %s, %s',
926 _repo_id, commit_id, f_path)
927 _repo_id, commit_id, f_path)
927 try:
928 try:
928 _d, _f = ScmModel().get_quick_filter_nodes(repo_name, _commit_id, _f_path)
929 _d, _f = ScmModel().get_quick_filter_nodes(repo_name, _commit_id, _f_path)
929 except (RepositoryError, CommitDoesNotExistError, Exception) as e:
930 except (RepositoryError, CommitDoesNotExistError, Exception) as e:
930 log.exception(safe_str(e))
931 log.exception(safe_str(e))
931 h.flash(h.escape(safe_str(e)), category='error')
932 h.flash(h.escape(safe_str(e)), category='error')
932 raise HTTPFound(h.route_path(
933 raise HTTPFound(h.route_path(
933 'repo_files', repo_name=self.db_repo_name,
934 'repo_files', repo_name=self.db_repo_name,
934 commit_id='tip', f_path='/'))
935 commit_id='tip', f_path='/'))
935
936
936 return _d + _f
937 return _d + _f
937
938
938 result = compute_file_search(self.db_repo.repo_name_hash, self.db_repo.repo_id,
939 result = compute_file_search(self.db_repo.repo_name_hash, self.db_repo.repo_id,
939 commit_id, f_path)
940 commit_id, f_path)
940 return filter(lambda n: self.path_filter.path_access_allowed(n['name']), result)
941 return filter(lambda n: self.path_filter.path_access_allowed(n['name']), result)
941
942
942 @LoginRequired()
943 @LoginRequired()
943 @HasRepoPermissionAnyDecorator(
944 @HasRepoPermissionAnyDecorator(
944 'repository.read', 'repository.write', 'repository.admin')
945 'repository.read', 'repository.write', 'repository.admin')
945 def repo_nodelist(self):
946 def repo_nodelist(self):
946 self.load_default_context()
947 self.load_default_context()
947
948
948 commit_id, f_path = self._get_commit_and_path()
949 commit_id, f_path = self._get_commit_and_path()
949 commit = self._get_commit_or_redirect(commit_id)
950 commit = self._get_commit_or_redirect(commit_id)
950
951
951 metadata = self._get_nodelist_at_commit(
952 metadata = self._get_nodelist_at_commit(
952 self.db_repo_name, self.db_repo.repo_id, commit.raw_id, f_path)
953 self.db_repo_name, self.db_repo.repo_id, commit.raw_id, f_path)
953 return {'nodes': [x for x in metadata]}
954 return {'nodes': [x for x in metadata]}
954
955
955 def _create_references(self, branches_or_tags, symbolic_reference, f_path, ref_type):
956 def _create_references(self, branches_or_tags, symbolic_reference, f_path, ref_type):
956 items = []
957 items = []
957 for name, commit_id in branches_or_tags.items():
958 for name, commit_id in branches_or_tags.items():
958 sym_ref = symbolic_reference(commit_id, name, f_path, ref_type)
959 sym_ref = symbolic_reference(commit_id, name, f_path, ref_type)
959 items.append((sym_ref, name, ref_type))
960 items.append((sym_ref, name, ref_type))
960 return items
961 return items
961
962
962 def _symbolic_reference(self, commit_id, name, f_path, ref_type):
963 def _symbolic_reference(self, commit_id, name, f_path, ref_type):
963 return commit_id
964 return commit_id
964
965
965 def _symbolic_reference_svn(self, commit_id, name, f_path, ref_type):
966 def _symbolic_reference_svn(self, commit_id, name, f_path, ref_type):
966 return commit_id
967 return commit_id
967
968
968 # NOTE(dan): old code we used in "diff" mode compare
969 # NOTE(dan): old code we used in "diff" mode compare
969 new_f_path = vcspath.join(name, f_path)
970 new_f_path = vcspath.join(name, f_path)
970 return f'{new_f_path}@{commit_id}'
971 return f'{new_f_path}@{commit_id}'
971
972
972 def _get_node_history(self, commit_obj, f_path, commits=None):
973 def _get_node_history(self, commit_obj, f_path, commits=None):
973 """
974 """
974 get commit history for given node
975 get commit history for given node
975
976
976 :param commit_obj: commit to calculate history
977 :param commit_obj: commit to calculate history
977 :param f_path: path for node to calculate history for
978 :param f_path: path for node to calculate history for
978 :param commits: if passed don't calculate history and take
979 :param commits: if passed don't calculate history and take
979 commits defined in this list
980 commits defined in this list
980 """
981 """
981 _ = self.request.translate
982 _ = self.request.translate
982
983
983 # calculate history based on tip
984 # calculate history based on tip
984 tip = self.rhodecode_vcs_repo.get_commit()
985 tip = self.rhodecode_vcs_repo.get_commit()
985 if commits is None:
986 if commits is None:
986 pre_load = ["author", "branch"]
987 pre_load = ["author", "branch"]
987 try:
988 try:
988 commits = tip.get_path_history(f_path, pre_load=pre_load)
989 commits = tip.get_path_history(f_path, pre_load=pre_load)
989 except (NodeDoesNotExistError, CommitError):
990 except (NodeDoesNotExistError, CommitError):
990 # this node is not present at tip!
991 # this node is not present at tip!
991 commits = commit_obj.get_path_history(f_path, pre_load=pre_load)
992 commits = commit_obj.get_path_history(f_path, pre_load=pre_load)
992
993
993 history = []
994 history = []
994 commits_group = ([], _("Changesets"))
995 commits_group = ([], _("Changesets"))
995 for commit in commits:
996 for commit in commits:
996 branch = ' (%s)' % commit.branch if commit.branch else ''
997 branch = ' (%s)' % commit.branch if commit.branch else ''
997 n_desc = f'r{commit.idx}:{commit.short_id}{branch}'
998 n_desc = f'r{commit.idx}:{commit.short_id}{branch}'
998 commits_group[0].append((commit.raw_id, n_desc, 'sha'))
999 commits_group[0].append((commit.raw_id, n_desc, 'sha'))
999 history.append(commits_group)
1000 history.append(commits_group)
1000
1001
1001 symbolic_reference = self._symbolic_reference
1002 symbolic_reference = self._symbolic_reference
1002
1003
1003 if self.rhodecode_vcs_repo.alias == 'svn':
1004 if self.rhodecode_vcs_repo.alias == 'svn':
1004 adjusted_f_path = RepoFilesView.adjust_file_path_for_svn(
1005 adjusted_f_path = RepoFilesView.adjust_file_path_for_svn(
1005 f_path, self.rhodecode_vcs_repo)
1006 f_path, self.rhodecode_vcs_repo)
1006 if adjusted_f_path != f_path:
1007 if adjusted_f_path != f_path:
1007 log.debug(
1008 log.debug(
1008 'Recognized svn tag or branch in file "%s", using svn '
1009 'Recognized svn tag or branch in file "%s", using svn '
1009 'specific symbolic references', f_path)
1010 'specific symbolic references', f_path)
1010 f_path = adjusted_f_path
1011 f_path = adjusted_f_path
1011 symbolic_reference = self._symbolic_reference_svn
1012 symbolic_reference = self._symbolic_reference_svn
1012
1013
1013 branches = self._create_references(
1014 branches = self._create_references(
1014 self.rhodecode_vcs_repo.branches, symbolic_reference, f_path, 'branch')
1015 self.rhodecode_vcs_repo.branches, symbolic_reference, f_path, 'branch')
1015 branches_group = (branches, _("Branches"))
1016 branches_group = (branches, _("Branches"))
1016
1017
1017 tags = self._create_references(
1018 tags = self._create_references(
1018 self.rhodecode_vcs_repo.tags, symbolic_reference, f_path, 'tag')
1019 self.rhodecode_vcs_repo.tags, symbolic_reference, f_path, 'tag')
1019 tags_group = (tags, _("Tags"))
1020 tags_group = (tags, _("Tags"))
1020
1021
1021 history.append(branches_group)
1022 history.append(branches_group)
1022 history.append(tags_group)
1023 history.append(tags_group)
1023
1024
1024 return history, commits
1025 return history, commits
1025
1026
1026 @LoginRequired()
1027 @LoginRequired()
1027 @HasRepoPermissionAnyDecorator(
1028 @HasRepoPermissionAnyDecorator(
1028 'repository.read', 'repository.write', 'repository.admin')
1029 'repository.read', 'repository.write', 'repository.admin')
1029 def repo_file_history(self):
1030 def repo_file_history(self):
1030 self.load_default_context()
1031 self.load_default_context()
1031
1032
1032 commit_id, f_path = self._get_commit_and_path()
1033 commit_id, f_path = self._get_commit_and_path()
1033 commit = self._get_commit_or_redirect(commit_id)
1034 commit = self._get_commit_or_redirect(commit_id)
1034 file_node = self._get_filenode_or_redirect(commit, f_path)
1035 file_node = self._get_filenode_or_redirect(commit, f_path)
1035
1036
1036 if file_node.is_file():
1037 if file_node.is_file():
1037 file_history, _hist = self._get_node_history(commit, f_path)
1038 file_history, _hist = self._get_node_history(commit, f_path)
1038
1039
1039 res = []
1040 res = []
1040 for section_items, section in file_history:
1041 for section_items, section in file_history:
1041 items = []
1042 items = []
1042 for obj_id, obj_text, obj_type in section_items:
1043 for obj_id, obj_text, obj_type in section_items:
1043 at_rev = ''
1044 at_rev = ''
1044 if obj_type in ['branch', 'bookmark', 'tag']:
1045 if obj_type in ['branch', 'bookmark', 'tag']:
1045 at_rev = obj_text
1046 at_rev = obj_text
1046 entry = {
1047 entry = {
1047 'id': obj_id,
1048 'id': obj_id,
1048 'text': obj_text,
1049 'text': obj_text,
1049 'type': obj_type,
1050 'type': obj_type,
1050 'at_rev': at_rev
1051 'at_rev': at_rev
1051 }
1052 }
1052
1053
1053 items.append(entry)
1054 items.append(entry)
1054
1055
1055 res.append({
1056 res.append({
1056 'text': section,
1057 'text': section,
1057 'children': items
1058 'children': items
1058 })
1059 })
1059
1060
1060 data = {
1061 data = {
1061 'more': False,
1062 'more': False,
1062 'results': res
1063 'results': res
1063 }
1064 }
1064 return data
1065 return data
1065
1066
1066 log.warning('Cannot fetch history for directory')
1067 log.warning('Cannot fetch history for directory')
1067 raise HTTPBadRequest()
1068 raise HTTPBadRequest()
1068
1069
1069 @LoginRequired()
1070 @LoginRequired()
1070 @HasRepoPermissionAnyDecorator(
1071 @HasRepoPermissionAnyDecorator(
1071 'repository.read', 'repository.write', 'repository.admin')
1072 'repository.read', 'repository.write', 'repository.admin')
1072 def repo_file_authors(self):
1073 def repo_file_authors(self):
1073 c = self.load_default_context()
1074 c = self.load_default_context()
1074
1075
1075 commit_id, f_path = self._get_commit_and_path()
1076 commit_id, f_path = self._get_commit_and_path()
1076 commit = self._get_commit_or_redirect(commit_id)
1077 commit = self._get_commit_or_redirect(commit_id)
1077 file_node = self._get_filenode_or_redirect(commit, f_path)
1078 file_node = self._get_filenode_or_redirect(commit, f_path)
1078
1079
1079 if not file_node.is_file():
1080 if not file_node.is_file():
1080 raise HTTPBadRequest()
1081 raise HTTPBadRequest()
1081
1082
1082 c.file_last_commit = file_node.last_commit
1083 c.file_last_commit = file_node.last_commit
1083 if self.request.GET.get('annotate') == '1':
1084 if self.request.GET.get('annotate') == '1':
1084 # use _hist from annotation if annotation mode is on
1085 # use _hist from annotation if annotation mode is on
1085 commit_ids = {x[1] for x in file_node.annotate}
1086 commit_ids = {x[1] for x in file_node.annotate}
1086 _hist = (
1087 _hist = (
1087 self.rhodecode_vcs_repo.get_commit(commit_id)
1088 self.rhodecode_vcs_repo.get_commit(commit_id)
1088 for commit_id in commit_ids)
1089 for commit_id in commit_ids)
1089 else:
1090 else:
1090 _f_history, _hist = self._get_node_history(commit, f_path)
1091 _f_history, _hist = self._get_node_history(commit, f_path)
1091 c.file_author = False
1092 c.file_author = False
1092
1093
1093 unique = collections.OrderedDict()
1094 unique = collections.OrderedDict()
1094 for commit in _hist:
1095 for commit in _hist:
1095 author = commit.author
1096 author = commit.author
1096 if author not in unique:
1097 if author not in unique:
1097 unique[commit.author] = [
1098 unique[commit.author] = [
1098 h.email(author),
1099 h.email(author),
1099 h.person(author, 'username_or_name_or_email'),
1100 h.person(author, 'username_or_name_or_email'),
1100 1 # counter
1101 1 # counter
1101 ]
1102 ]
1102
1103
1103 else:
1104 else:
1104 # increase counter
1105 # increase counter
1105 unique[commit.author][2] += 1
1106 unique[commit.author][2] += 1
1106
1107
1107 c.authors = [val for val in unique.values()]
1108 c.authors = [val for val in unique.values()]
1108
1109
1109 return self._get_template_context(c)
1110 return self._get_template_context(c)
1110
1111
1111 @LoginRequired()
1112 @LoginRequired()
1112 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1113 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1113 def repo_files_check_head(self):
1114 def repo_files_check_head(self):
1114 self.load_default_context()
1115 self.load_default_context()
1115
1116
1116 commit_id, f_path = self._get_commit_and_path()
1117 commit_id, f_path = self._get_commit_and_path()
1117 _branch_name, _sha_commit_id, is_head = \
1118 _branch_name, _sha_commit_id, is_head = \
1118 self._is_valid_head(commit_id, self.rhodecode_vcs_repo,
1119 self._is_valid_head(commit_id, self.rhodecode_vcs_repo,
1119 landing_ref=self.db_repo.landing_ref_name)
1120 landing_ref=self.db_repo.landing_ref_name)
1120
1121
1121 new_path = self.request.POST.get('path')
1122 new_path = self.request.POST.get('path')
1122 operation = self.request.POST.get('operation')
1123 operation = self.request.POST.get('operation')
1123 path_exist = ''
1124 path_exist = ''
1124
1125
1125 if new_path and operation in ['create', 'upload']:
1126 if new_path and operation in ['create', 'upload']:
1126 new_f_path = os.path.join(f_path.lstrip('/'), new_path)
1127 new_f_path = os.path.join(f_path.lstrip('/'), new_path)
1127 try:
1128 try:
1128 commit_obj = self.rhodecode_vcs_repo.get_commit(commit_id)
1129 commit_obj = self.rhodecode_vcs_repo.get_commit(commit_id)
1129 # NOTE(dan): construct whole path without leading /
1130 # NOTE(dan): construct whole path without leading /
1130 file_node = commit_obj.get_node(new_f_path)
1131 file_node = commit_obj.get_node(new_f_path)
1131 if file_node is not None:
1132 if file_node is not None:
1132 path_exist = new_f_path
1133 path_exist = new_f_path
1133 except EmptyRepositoryError:
1134 except EmptyRepositoryError:
1134 pass
1135 pass
1135 except Exception:
1136 except Exception:
1136 pass
1137 pass
1137
1138
1138 return {
1139 return {
1139 'branch': _branch_name,
1140 'branch': _branch_name,
1140 'sha': _sha_commit_id,
1141 'sha': _sha_commit_id,
1141 'is_head': is_head,
1142 'is_head': is_head,
1142 'path_exists': path_exist
1143 'path_exists': path_exist
1143 }
1144 }
1144
1145
1145 @LoginRequired()
1146 @LoginRequired()
1146 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1147 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1147 def repo_files_remove_file(self):
1148 def repo_files_remove_file(self):
1148 _ = self.request.translate
1149 _ = self.request.translate
1149 c = self.load_default_context()
1150 c = self.load_default_context()
1150 commit_id, f_path = self._get_commit_and_path()
1151 commit_id, f_path = self._get_commit_and_path()
1151
1152
1152 self._ensure_not_locked()
1153 self._ensure_not_locked()
1153 _branch_name, _sha_commit_id, is_head = \
1154 _branch_name, _sha_commit_id, is_head = \
1154 self._is_valid_head(commit_id, self.rhodecode_vcs_repo,
1155 self._is_valid_head(commit_id, self.rhodecode_vcs_repo,
1155 landing_ref=self.db_repo.landing_ref_name)
1156 landing_ref=self.db_repo.landing_ref_name)
1156
1157
1157 self.forbid_non_head(is_head, f_path)
1158 self.forbid_non_head(is_head, f_path)
1158 self.check_branch_permission(_branch_name)
1159 self.check_branch_permission(_branch_name)
1159
1160
1160 c.commit = self._get_commit_or_redirect(commit_id)
1161 c.commit = self._get_commit_or_redirect(commit_id)
1161 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1162 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1162
1163
1163 c.default_message = _(
1164 c.default_message = _(
1164 'Deleted file {} via RhodeCode Enterprise').format(f_path)
1165 'Deleted file {} via RhodeCode Enterprise').format(f_path)
1165 c.f_path = f_path
1166 c.f_path = f_path
1166
1167
1167 return self._get_template_context(c)
1168 return self._get_template_context(c)
1168
1169
1169 @LoginRequired()
1170 @LoginRequired()
1170 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1171 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1171 @CSRFRequired()
1172 @CSRFRequired()
1172 def repo_files_delete_file(self):
1173 def repo_files_delete_file(self):
1173 _ = self.request.translate
1174 _ = self.request.translate
1174
1175
1175 c = self.load_default_context()
1176 c = self.load_default_context()
1176 commit_id, f_path = self._get_commit_and_path()
1177 commit_id, f_path = self._get_commit_and_path()
1177
1178
1178 self._ensure_not_locked()
1179 self._ensure_not_locked()
1179 _branch_name, _sha_commit_id, is_head = \
1180 _branch_name, _sha_commit_id, is_head = \
1180 self._is_valid_head(commit_id, self.rhodecode_vcs_repo,
1181 self._is_valid_head(commit_id, self.rhodecode_vcs_repo,
1181 landing_ref=self.db_repo.landing_ref_name)
1182 landing_ref=self.db_repo.landing_ref_name)
1182
1183
1183 self.forbid_non_head(is_head, f_path)
1184 self.forbid_non_head(is_head, f_path)
1184 self.check_branch_permission(_branch_name)
1185 self.check_branch_permission(_branch_name)
1185
1186
1186 c.commit = self._get_commit_or_redirect(commit_id)
1187 c.commit = self._get_commit_or_redirect(commit_id)
1187 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1188 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1188
1189
1189 c.default_message = _(
1190 c.default_message = _(
1190 'Deleted file {} via RhodeCode Enterprise').format(f_path)
1191 'Deleted file {} via RhodeCode Enterprise').format(f_path)
1191 c.f_path = f_path
1192 c.f_path = f_path
1192 node_path = f_path
1193 node_path = f_path
1193 author = self._rhodecode_db_user.full_contact
1194 author = self._rhodecode_db_user.full_contact
1194 message = self.request.POST.get('message') or c.default_message
1195 message = self.request.POST.get('message') or c.default_message
1195 try:
1196 try:
1196 nodes = {
1197 nodes = {
1197 safe_bytes(node_path): {
1198 safe_bytes(node_path): {
1198 'content': b''
1199 'content': b''
1199 }
1200 }
1200 }
1201 }
1201 ScmModel().delete_nodes(
1202 ScmModel().delete_nodes(
1202 user=self._rhodecode_db_user.user_id, repo=self.db_repo,
1203 user=self._rhodecode_db_user.user_id, repo=self.db_repo,
1203 message=message,
1204 message=message,
1204 nodes=nodes,
1205 nodes=nodes,
1205 parent_commit=c.commit,
1206 parent_commit=c.commit,
1206 author=author,
1207 author=author,
1207 )
1208 )
1208
1209
1209 h.flash(
1210 h.flash(
1210 _('Successfully deleted file `{}`').format(
1211 _('Successfully deleted file `{}`').format(
1211 h.escape(f_path)), category='success')
1212 h.escape(f_path)), category='success')
1212 except Exception:
1213 except Exception:
1213 log.exception('Error during commit operation')
1214 log.exception('Error during commit operation')
1214 h.flash(_('Error occurred during commit'), category='error')
1215 h.flash(_('Error occurred during commit'), category='error')
1215 raise HTTPFound(
1216 raise HTTPFound(
1216 h.route_path('repo_commit', repo_name=self.db_repo_name,
1217 h.route_path('repo_commit', repo_name=self.db_repo_name,
1217 commit_id='tip'))
1218 commit_id='tip'))
1218
1219
1219 @LoginRequired()
1220 @LoginRequired()
1220 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1221 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1221 def repo_files_edit_file(self):
1222 def repo_files_edit_file(self):
1222 _ = self.request.translate
1223 _ = self.request.translate
1223 c = self.load_default_context()
1224 c = self.load_default_context()
1224 commit_id, f_path = self._get_commit_and_path()
1225 commit_id, f_path = self._get_commit_and_path()
1225
1226
1226 self._ensure_not_locked()
1227 self._ensure_not_locked()
1227 _branch_name, _sha_commit_id, is_head = \
1228 _branch_name, _sha_commit_id, is_head = \
1228 self._is_valid_head(commit_id, self.rhodecode_vcs_repo,
1229 self._is_valid_head(commit_id, self.rhodecode_vcs_repo,
1229 landing_ref=self.db_repo.landing_ref_name)
1230 landing_ref=self.db_repo.landing_ref_name)
1230
1231
1231 self.forbid_non_head(is_head, f_path, commit_id=commit_id)
1232 self.forbid_non_head(is_head, f_path, commit_id=commit_id)
1232 self.check_branch_permission(_branch_name, commit_id=commit_id)
1233 self.check_branch_permission(_branch_name, commit_id=commit_id)
1233
1234
1234 c.commit = self._get_commit_or_redirect(commit_id)
1235 c.commit = self._get_commit_or_redirect(commit_id)
1235 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1236 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1236
1237
1237 if c.file.is_binary:
1238 if c.file.is_binary:
1238 files_url = h.route_path(
1239 files_url = h.route_path(
1239 'repo_files',
1240 'repo_files',
1240 repo_name=self.db_repo_name,
1241 repo_name=self.db_repo_name,
1241 commit_id=c.commit.raw_id, f_path=f_path)
1242 commit_id=c.commit.raw_id, f_path=f_path)
1242 raise HTTPFound(files_url)
1243 raise HTTPFound(files_url)
1243
1244
1244 c.default_message = _('Edited file {} via RhodeCode Enterprise').format(f_path)
1245 c.default_message = _('Edited file {} via RhodeCode Enterprise').format(f_path)
1245 c.f_path = f_path
1246 c.f_path = f_path
1246
1247
1247 return self._get_template_context(c)
1248 return self._get_template_context(c)
1248
1249
1249 @LoginRequired()
1250 @LoginRequired()
1250 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1251 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1251 @CSRFRequired()
1252 @CSRFRequired()
1252 def repo_files_update_file(self):
1253 def repo_files_update_file(self):
1253 _ = self.request.translate
1254 _ = self.request.translate
1254 c = self.load_default_context()
1255 c = self.load_default_context()
1255 commit_id, f_path = self._get_commit_and_path()
1256 commit_id, f_path = self._get_commit_and_path()
1256
1257
1257 self._ensure_not_locked()
1258 self._ensure_not_locked()
1258
1259
1259 c.commit = self._get_commit_or_redirect(commit_id)
1260 c.commit = self._get_commit_or_redirect(commit_id)
1260 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1261 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1261
1262
1262 if c.file.is_binary:
1263 if c.file.is_binary:
1263 raise HTTPFound(h.route_path('repo_files', repo_name=self.db_repo_name,
1264 raise HTTPFound(h.route_path('repo_files', repo_name=self.db_repo_name,
1264 commit_id=c.commit.raw_id, f_path=f_path))
1265 commit_id=c.commit.raw_id, f_path=f_path))
1265
1266
1266 _branch_name, _sha_commit_id, is_head = \
1267 _branch_name, _sha_commit_id, is_head = \
1267 self._is_valid_head(commit_id, self.rhodecode_vcs_repo,
1268 self._is_valid_head(commit_id, self.rhodecode_vcs_repo,
1268 landing_ref=self.db_repo.landing_ref_name)
1269 landing_ref=self.db_repo.landing_ref_name)
1269
1270
1270 self.forbid_non_head(is_head, f_path, commit_id=commit_id)
1271 self.forbid_non_head(is_head, f_path, commit_id=commit_id)
1271 self.check_branch_permission(_branch_name, commit_id=commit_id)
1272 self.check_branch_permission(_branch_name, commit_id=commit_id)
1272
1273
1273 c.default_message = _('Edited file {} via RhodeCode Enterprise').format(f_path)
1274 c.default_message = _('Edited file {} via RhodeCode Enterprise').format(f_path)
1274 c.f_path = f_path
1275 c.f_path = f_path
1275
1276
1276 old_content = c.file.str_content
1277 old_content = c.file.str_content
1277 sl = old_content.splitlines(1)
1278 sl = old_content.splitlines(1)
1278 first_line = sl[0] if sl else ''
1279 first_line = sl[0] if sl else ''
1279
1280
1280 r_post = self.request.POST
1281 r_post = self.request.POST
1281 # line endings: 0 - Unix, 1 - Mac, 2 - DOS
1282 # line endings: 0 - Unix, 1 - Mac, 2 - DOS
1282 line_ending_mode = detect_mode(first_line, 0)
1283 line_ending_mode = detect_mode(first_line, 0)
1283 content = convert_line_endings(r_post.get('content', ''), line_ending_mode)
1284 content = convert_line_endings(r_post.get('content', ''), line_ending_mode)
1284
1285
1285 message = r_post.get('message') or c.default_message
1286 message = r_post.get('message') or c.default_message
1286
1287
1287 org_node_path = c.file.str_path
1288 org_node_path = c.file.str_path
1288 filename = r_post['filename']
1289 filename = r_post['filename']
1289
1290
1290 root_path = c.file.dir_path
1291 root_path = c.file.dir_path
1291 pure_path = self.create_pure_path(root_path, filename)
1292 pure_path = self.create_pure_path(root_path, filename)
1292 node_path = pure_path.as_posix()
1293 node_path = pure_path.as_posix()
1293
1294
1294 default_redirect_url = h.route_path('repo_commit', repo_name=self.db_repo_name,
1295 default_redirect_url = h.route_path('repo_commit', repo_name=self.db_repo_name,
1295 commit_id=commit_id)
1296 commit_id=commit_id)
1296 if content == old_content and node_path == org_node_path:
1297 if content == old_content and node_path == org_node_path:
1297 h.flash(_('No changes detected on {}').format(h.escape(org_node_path)),
1298 h.flash(_('No changes detected on {}').format(h.escape(org_node_path)),
1298 category='warning')
1299 category='warning')
1299 raise HTTPFound(default_redirect_url)
1300 raise HTTPFound(default_redirect_url)
1300
1301
1301 try:
1302 try:
1302 mapping = {
1303 mapping = {
1303 c.file.bytes_path: {
1304 c.file.bytes_path: {
1304 'org_filename': org_node_path,
1305 'org_filename': org_node_path,
1305 'filename': safe_bytes(node_path),
1306 'filename': safe_bytes(node_path),
1306 'content': safe_bytes(content),
1307 'content': safe_bytes(content),
1307 'lexer': '',
1308 'lexer': '',
1308 'op': 'mod',
1309 'op': 'mod',
1309 'mode': c.file.mode
1310 'mode': c.file.mode
1310 }
1311 }
1311 }
1312 }
1312
1313
1313 commit = ScmModel().update_nodes(
1314 commit = ScmModel().update_nodes(
1314 user=self._rhodecode_db_user.user_id,
1315 user=self._rhodecode_db_user.user_id,
1315 repo=self.db_repo,
1316 repo=self.db_repo,
1316 message=message,
1317 message=message,
1317 nodes=mapping,
1318 nodes=mapping,
1318 parent_commit=c.commit,
1319 parent_commit=c.commit,
1319 )
1320 )
1320
1321
1321 h.flash(_('Successfully committed changes to file `{}`').format(
1322 h.flash(_('Successfully committed changes to file `{}`').format(
1322 h.escape(f_path)), category='success')
1323 h.escape(f_path)), category='success')
1323 default_redirect_url = h.route_path(
1324 default_redirect_url = h.route_path(
1324 'repo_commit', repo_name=self.db_repo_name, commit_id=commit.raw_id)
1325 'repo_commit', repo_name=self.db_repo_name, commit_id=commit.raw_id)
1325
1326
1326 except Exception:
1327 except Exception:
1327 log.exception('Error occurred during commit')
1328 log.exception('Error occurred during commit')
1328 h.flash(_('Error occurred during commit'), category='error')
1329 h.flash(_('Error occurred during commit'), category='error')
1329
1330
1330 raise HTTPFound(default_redirect_url)
1331 raise HTTPFound(default_redirect_url)
1331
1332
1332 @LoginRequired()
1333 @LoginRequired()
1333 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1334 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1334 def repo_files_add_file(self):
1335 def repo_files_add_file(self):
1335 _ = self.request.translate
1336 _ = self.request.translate
1336 c = self.load_default_context()
1337 c = self.load_default_context()
1337 commit_id, f_path = self._get_commit_and_path()
1338 commit_id, f_path = self._get_commit_and_path()
1338
1339
1339 self._ensure_not_locked()
1340 self._ensure_not_locked()
1340
1341
1341 c.commit = self._get_commit_or_redirect(commit_id, redirect_after=False)
1342 c.commit = self._get_commit_or_redirect(commit_id, redirect_after=False)
1342 if c.commit is None:
1343 if c.commit is None:
1343 c.commit = EmptyCommit(alias=self.rhodecode_vcs_repo.alias)
1344 c.commit = EmptyCommit(alias=self.rhodecode_vcs_repo.alias)
1344
1345
1345 if self.rhodecode_vcs_repo.is_empty():
1346 if self.rhodecode_vcs_repo.is_empty():
1346 # for empty repository we cannot check for current branch, we rely on
1347 # for empty repository we cannot check for current branch, we rely on
1347 # c.commit.branch instead
1348 # c.commit.branch instead
1348 _branch_name, _sha_commit_id, is_head = c.commit.branch, '', True
1349 _branch_name, _sha_commit_id, is_head = c.commit.branch, '', True
1349 else:
1350 else:
1350 _branch_name, _sha_commit_id, is_head = \
1351 _branch_name, _sha_commit_id, is_head = \
1351 self._is_valid_head(commit_id, self.rhodecode_vcs_repo,
1352 self._is_valid_head(commit_id, self.rhodecode_vcs_repo,
1352 landing_ref=self.db_repo.landing_ref_name)
1353 landing_ref=self.db_repo.landing_ref_name)
1353
1354
1354 self.forbid_non_head(is_head, f_path, commit_id=commit_id)
1355 self.forbid_non_head(is_head, f_path, commit_id=commit_id)
1355 self.check_branch_permission(_branch_name, commit_id=commit_id)
1356 self.check_branch_permission(_branch_name, commit_id=commit_id)
1356
1357
1357 c.default_message = (_('Added file via RhodeCode Enterprise'))
1358 c.default_message = (_('Added file via RhodeCode Enterprise'))
1358 c.f_path = f_path.lstrip('/') # ensure not relative path
1359 c.f_path = f_path.lstrip('/') # ensure not relative path
1359
1360
1360 return self._get_template_context(c)
1361 return self._get_template_context(c)
1361
1362
1362 @LoginRequired()
1363 @LoginRequired()
1363 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1364 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1364 @CSRFRequired()
1365 @CSRFRequired()
1365 def repo_files_create_file(self):
1366 def repo_files_create_file(self):
1366 _ = self.request.translate
1367 _ = self.request.translate
1367 c = self.load_default_context()
1368 c = self.load_default_context()
1368 commit_id, f_path = self._get_commit_and_path()
1369 commit_id, f_path = self._get_commit_and_path()
1369
1370
1370 self._ensure_not_locked()
1371 self._ensure_not_locked()
1371
1372
1372 c.commit = self._get_commit_or_redirect(commit_id, redirect_after=False)
1373 c.commit = self._get_commit_or_redirect(commit_id, redirect_after=False)
1373 if c.commit is None:
1374 if c.commit is None:
1374 c.commit = EmptyCommit(alias=self.rhodecode_vcs_repo.alias)
1375 c.commit = EmptyCommit(alias=self.rhodecode_vcs_repo.alias)
1375
1376
1376 # calculate redirect URL
1377 # calculate redirect URL
1377 if self.rhodecode_vcs_repo.is_empty():
1378 if self.rhodecode_vcs_repo.is_empty():
1378 default_redirect_url = h.route_path(
1379 default_redirect_url = h.route_path(
1379 'repo_summary', repo_name=self.db_repo_name)
1380 'repo_summary', repo_name=self.db_repo_name)
1380 else:
1381 else:
1381 default_redirect_url = h.route_path(
1382 default_redirect_url = h.route_path(
1382 'repo_commit', repo_name=self.db_repo_name, commit_id='tip')
1383 'repo_commit', repo_name=self.db_repo_name, commit_id='tip')
1383
1384
1384 if self.rhodecode_vcs_repo.is_empty():
1385 if self.rhodecode_vcs_repo.is_empty():
1385 # for empty repository we cannot check for current branch, we rely on
1386 # for empty repository we cannot check for current branch, we rely on
1386 # c.commit.branch instead
1387 # c.commit.branch instead
1387 _branch_name, _sha_commit_id, is_head = c.commit.branch, '', True
1388 _branch_name, _sha_commit_id, is_head = c.commit.branch, '', True
1388 else:
1389 else:
1389 _branch_name, _sha_commit_id, is_head = \
1390 _branch_name, _sha_commit_id, is_head = \
1390 self._is_valid_head(commit_id, self.rhodecode_vcs_repo,
1391 self._is_valid_head(commit_id, self.rhodecode_vcs_repo,
1391 landing_ref=self.db_repo.landing_ref_name)
1392 landing_ref=self.db_repo.landing_ref_name)
1392
1393
1393 self.forbid_non_head(is_head, f_path, commit_id=commit_id)
1394 self.forbid_non_head(is_head, f_path, commit_id=commit_id)
1394 self.check_branch_permission(_branch_name, commit_id=commit_id)
1395 self.check_branch_permission(_branch_name, commit_id=commit_id)
1395
1396
1396 c.default_message = (_('Added file via RhodeCode Enterprise'))
1397 c.default_message = (_('Added file via RhodeCode Enterprise'))
1397 c.f_path = f_path
1398 c.f_path = f_path
1398
1399
1399 r_post = self.request.POST
1400 r_post = self.request.POST
1400 message = r_post.get('message') or c.default_message
1401 message = r_post.get('message') or c.default_message
1401 filename = r_post.get('filename')
1402 filename = r_post.get('filename')
1402 unix_mode = 0
1403 unix_mode = 0
1403
1404
1404 if not filename:
1405 if not filename:
1405 # If there's no commit, redirect to repo summary
1406 # If there's no commit, redirect to repo summary
1406 if type(c.commit) is EmptyCommit:
1407 if type(c.commit) is EmptyCommit:
1407 redirect_url = h.route_path(
1408 redirect_url = h.route_path(
1408 'repo_summary', repo_name=self.db_repo_name)
1409 'repo_summary', repo_name=self.db_repo_name)
1409 else:
1410 else:
1410 redirect_url = default_redirect_url
1411 redirect_url = default_redirect_url
1411 h.flash(_('No filename specified'), category='warning')
1412 h.flash(_('No filename specified'), category='warning')
1412 raise HTTPFound(redirect_url)
1413 raise HTTPFound(redirect_url)
1413
1414
1414 root_path = f_path
1415 root_path = f_path
1415 pure_path = self.create_pure_path(root_path, filename)
1416 pure_path = self.create_pure_path(root_path, filename)
1416 node_path = pure_path.as_posix().lstrip('/')
1417 node_path = pure_path.as_posix().lstrip('/')
1417
1418
1418 author = self._rhodecode_db_user.full_contact
1419 author = self._rhodecode_db_user.full_contact
1419 content = convert_line_endings(r_post.get('content', ''), unix_mode)
1420 content = convert_line_endings(r_post.get('content', ''), unix_mode)
1420 nodes = {
1421 nodes = {
1421 safe_bytes(node_path): {
1422 safe_bytes(node_path): {
1422 'content': safe_bytes(content)
1423 'content': safe_bytes(content)
1423 }
1424 }
1424 }
1425 }
1425
1426
1426 try:
1427 try:
1427
1428
1428 commit = ScmModel().create_nodes(
1429 commit = ScmModel().create_nodes(
1429 user=self._rhodecode_db_user.user_id,
1430 user=self._rhodecode_db_user.user_id,
1430 repo=self.db_repo,
1431 repo=self.db_repo,
1431 message=message,
1432 message=message,
1432 nodes=nodes,
1433 nodes=nodes,
1433 parent_commit=c.commit,
1434 parent_commit=c.commit,
1434 author=author,
1435 author=author,
1435 )
1436 )
1436
1437
1437 h.flash(_('Successfully committed new file `{}`').format(
1438 h.flash(_('Successfully committed new file `{}`').format(
1438 h.escape(node_path)), category='success')
1439 h.escape(node_path)), category='success')
1439
1440
1440 default_redirect_url = h.route_path(
1441 default_redirect_url = h.route_path(
1441 'repo_commit', repo_name=self.db_repo_name, commit_id=commit.raw_id)
1442 'repo_commit', repo_name=self.db_repo_name, commit_id=commit.raw_id)
1442
1443
1443 except NonRelativePathError:
1444 except NonRelativePathError:
1444 log.exception('Non Relative path found')
1445 log.exception('Non Relative path found')
1445 h.flash(_('The location specified must be a relative path and must not '
1446 h.flash(_('The location specified must be a relative path and must not '
1446 'contain .. in the path'), category='warning')
1447 'contain .. in the path'), category='warning')
1447 raise HTTPFound(default_redirect_url)
1448 raise HTTPFound(default_redirect_url)
1448 except (NodeError, NodeAlreadyExistsError) as e:
1449 except (NodeError, NodeAlreadyExistsError) as e:
1449 h.flash(h.escape(safe_str(e)), category='error')
1450 h.flash(h.escape(safe_str(e)), category='error')
1450 except Exception:
1451 except Exception:
1451 log.exception('Error occurred during commit')
1452 log.exception('Error occurred during commit')
1452 h.flash(_('Error occurred during commit'), category='error')
1453 h.flash(_('Error occurred during commit'), category='error')
1453
1454
1454 raise HTTPFound(default_redirect_url)
1455 raise HTTPFound(default_redirect_url)
1455
1456
1456 @LoginRequired()
1457 @LoginRequired()
1457 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1458 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1458 @CSRFRequired()
1459 @CSRFRequired()
1459 def repo_files_upload_file(self):
1460 def repo_files_upload_file(self):
1460 _ = self.request.translate
1461 _ = self.request.translate
1461 c = self.load_default_context()
1462 c = self.load_default_context()
1462 commit_id, f_path = self._get_commit_and_path()
1463 commit_id, f_path = self._get_commit_and_path()
1463
1464
1464 self._ensure_not_locked()
1465 self._ensure_not_locked()
1465
1466
1466 c.commit = self._get_commit_or_redirect(commit_id, redirect_after=False)
1467 c.commit = self._get_commit_or_redirect(commit_id, redirect_after=False)
1467 if c.commit is None:
1468 if c.commit is None:
1468 c.commit = EmptyCommit(alias=self.rhodecode_vcs_repo.alias)
1469 c.commit = EmptyCommit(alias=self.rhodecode_vcs_repo.alias)
1469
1470
1470 # calculate redirect URL
1471 # calculate redirect URL
1471 if self.rhodecode_vcs_repo.is_empty():
1472 if self.rhodecode_vcs_repo.is_empty():
1472 default_redirect_url = h.route_path(
1473 default_redirect_url = h.route_path(
1473 'repo_summary', repo_name=self.db_repo_name)
1474 'repo_summary', repo_name=self.db_repo_name)
1474 else:
1475 else:
1475 default_redirect_url = h.route_path(
1476 default_redirect_url = h.route_path(
1476 'repo_commit', repo_name=self.db_repo_name, commit_id='tip')
1477 'repo_commit', repo_name=self.db_repo_name, commit_id='tip')
1477
1478
1478 if self.rhodecode_vcs_repo.is_empty():
1479 if self.rhodecode_vcs_repo.is_empty():
1479 # for empty repository we cannot check for current branch, we rely on
1480 # for empty repository we cannot check for current branch, we rely on
1480 # c.commit.branch instead
1481 # c.commit.branch instead
1481 _branch_name, _sha_commit_id, is_head = c.commit.branch, '', True
1482 _branch_name, _sha_commit_id, is_head = c.commit.branch, '', True
1482 else:
1483 else:
1483 _branch_name, _sha_commit_id, is_head = \
1484 _branch_name, _sha_commit_id, is_head = \
1484 self._is_valid_head(commit_id, self.rhodecode_vcs_repo,
1485 self._is_valid_head(commit_id, self.rhodecode_vcs_repo,
1485 landing_ref=self.db_repo.landing_ref_name)
1486 landing_ref=self.db_repo.landing_ref_name)
1486
1487
1487 error = self.forbid_non_head(is_head, f_path, json_mode=True)
1488 error = self.forbid_non_head(is_head, f_path, json_mode=True)
1488 if error:
1489 if error:
1489 return {
1490 return {
1490 'error': error,
1491 'error': error,
1491 'redirect_url': default_redirect_url
1492 'redirect_url': default_redirect_url
1492 }
1493 }
1493 error = self.check_branch_permission(_branch_name, json_mode=True)
1494 error = self.check_branch_permission(_branch_name, json_mode=True)
1494 if error:
1495 if error:
1495 return {
1496 return {
1496 'error': error,
1497 'error': error,
1497 'redirect_url': default_redirect_url
1498 'redirect_url': default_redirect_url
1498 }
1499 }
1499
1500
1500 c.default_message = (_('Uploaded file via RhodeCode Enterprise'))
1501 c.default_message = (_('Uploaded file via RhodeCode Enterprise'))
1501 c.f_path = f_path
1502 c.f_path = f_path
1502
1503
1503 r_post = self.request.POST
1504 r_post = self.request.POST
1504
1505
1505 message = c.default_message
1506 message = c.default_message
1506 user_message = r_post.getall('message')
1507 user_message = r_post.getall('message')
1507 if isinstance(user_message, list) and user_message:
1508 if isinstance(user_message, list) and user_message:
1508 # we take the first from duplicated results if it's not empty
1509 # we take the first from duplicated results if it's not empty
1509 message = user_message[0] if user_message[0] else message
1510 message = user_message[0] if user_message[0] else message
1510
1511
1511 nodes = {}
1512 nodes = {}
1512
1513
1513 for file_obj in r_post.getall('files_upload') or []:
1514 for file_obj in r_post.getall('files_upload') or []:
1514 content = file_obj.file
1515 content = file_obj.file
1515 filename = file_obj.filename
1516 filename = file_obj.filename
1516
1517
1517 root_path = f_path
1518 root_path = f_path
1518 pure_path = self.create_pure_path(root_path, filename)
1519 pure_path = self.create_pure_path(root_path, filename)
1519 node_path = pure_path.as_posix().lstrip('/')
1520 node_path = pure_path.as_posix().lstrip('/')
1520
1521
1521 nodes[safe_bytes(node_path)] = {
1522 nodes[safe_bytes(node_path)] = {
1522 'content': content
1523 'content': content
1523 }
1524 }
1524
1525
1525 if not nodes:
1526 if not nodes:
1526 error = 'missing files'
1527 error = 'missing files'
1527 return {
1528 return {
1528 'error': error,
1529 'error': error,
1529 'redirect_url': default_redirect_url
1530 'redirect_url': default_redirect_url
1530 }
1531 }
1531
1532
1532 author = self._rhodecode_db_user.full_contact
1533 author = self._rhodecode_db_user.full_contact
1533
1534
1534 try:
1535 try:
1535 commit = ScmModel().create_nodes(
1536 commit = ScmModel().create_nodes(
1536 user=self._rhodecode_db_user.user_id,
1537 user=self._rhodecode_db_user.user_id,
1537 repo=self.db_repo,
1538 repo=self.db_repo,
1538 message=message,
1539 message=message,
1539 nodes=nodes,
1540 nodes=nodes,
1540 parent_commit=c.commit,
1541 parent_commit=c.commit,
1541 author=author,
1542 author=author,
1542 )
1543 )
1543 if len(nodes) == 1:
1544 if len(nodes) == 1:
1544 flash_message = _('Successfully committed {} new files').format(len(nodes))
1545 flash_message = _('Successfully committed {} new files').format(len(nodes))
1545 else:
1546 else:
1546 flash_message = _('Successfully committed 1 new file')
1547 flash_message = _('Successfully committed 1 new file')
1547
1548
1548 h.flash(flash_message, category='success')
1549 h.flash(flash_message, category='success')
1549
1550
1550 default_redirect_url = h.route_path(
1551 default_redirect_url = h.route_path(
1551 'repo_commit', repo_name=self.db_repo_name, commit_id=commit.raw_id)
1552 'repo_commit', repo_name=self.db_repo_name, commit_id=commit.raw_id)
1552
1553
1553 except NonRelativePathError:
1554 except NonRelativePathError:
1554 log.exception('Non Relative path found')
1555 log.exception('Non Relative path found')
1555 error = _('The location specified must be a relative path and must not '
1556 error = _('The location specified must be a relative path and must not '
1556 'contain .. in the path')
1557 'contain .. in the path')
1557 h.flash(error, category='warning')
1558 h.flash(error, category='warning')
1558
1559
1559 return {
1560 return {
1560 'error': error,
1561 'error': error,
1561 'redirect_url': default_redirect_url
1562 'redirect_url': default_redirect_url
1562 }
1563 }
1563 except (NodeError, NodeAlreadyExistsError) as e:
1564 except (NodeError, NodeAlreadyExistsError) as e:
1564 error = h.escape(e)
1565 error = h.escape(e)
1565 h.flash(error, category='error')
1566 h.flash(error, category='error')
1566
1567
1567 return {
1568 return {
1568 'error': error,
1569 'error': error,
1569 'redirect_url': default_redirect_url
1570 'redirect_url': default_redirect_url
1570 }
1571 }
1571 except Exception:
1572 except Exception:
1572 log.exception('Error occurred during commit')
1573 log.exception('Error occurred during commit')
1573 error = _('Error occurred during commit')
1574 error = _('Error occurred during commit')
1574 h.flash(error, category='error')
1575 h.flash(error, category='error')
1575 return {
1576 return {
1576 'error': error,
1577 'error': error,
1577 'redirect_url': default_redirect_url
1578 'redirect_url': default_redirect_url
1578 }
1579 }
1579
1580
1580 return {
1581 return {
1581 'error': None,
1582 'error': None,
1582 'redirect_url': default_redirect_url
1583 'redirect_url': default_redirect_url
1583 }
1584 }
@@ -1,1198 +1,1203 b''
1 # Copyright (C) 2010-2023 RhodeCode GmbH
1 # Copyright (C) 2010-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 os
19 import os
20 import re
20 import re
21 import shutil
21 import shutil
22 import time
22 import time
23 import logging
23 import logging
24 import traceback
24 import traceback
25 import datetime
25 import datetime
26
26
27 from pyramid.threadlocal import get_current_request
27 from pyramid.threadlocal import get_current_request
28 from sqlalchemy.orm import aliased
28 from sqlalchemy.orm import aliased
29 from zope.cachedescriptors.property import Lazy as LazyProperty
29 from zope.cachedescriptors.property import Lazy as LazyProperty
30
30
31 from rhodecode import events
31 from rhodecode import events
32 from rhodecode.lib.auth import HasUserGroupPermissionAny
32 from rhodecode.lib.auth import HasUserGroupPermissionAny
33 from rhodecode.lib.caching_query import FromCache
33 from rhodecode.lib.caching_query import FromCache
34 from rhodecode.lib.exceptions import AttachedForksError, AttachedPullRequestsError
34 from rhodecode.lib.exceptions import AttachedForksError, AttachedPullRequestsError
35 from rhodecode.lib import hooks_base
35 from rhodecode.lib import hooks_base
36 from rhodecode.lib.user_log_filter import user_log_filter
36 from rhodecode.lib.user_log_filter import user_log_filter
37 from rhodecode.lib.utils import make_db_config
37 from rhodecode.lib.utils import make_db_config
38 from rhodecode.lib.utils2 import (
38 from rhodecode.lib.utils2 import (
39 safe_str, remove_prefix, obfuscate_url_pw,
39 safe_str, remove_prefix, obfuscate_url_pw,
40 get_current_rhodecode_user, safe_int, action_logger_generic)
40 get_current_rhodecode_user, safe_int, action_logger_generic)
41 from rhodecode.lib.vcs.backends import get_backend
41 from rhodecode.lib.vcs.backends import get_backend
42 from rhodecode.lib.vcs.nodes import NodeKind
42 from rhodecode.model import BaseModel
43 from rhodecode.model import BaseModel
43 from rhodecode.model.db import (
44 from rhodecode.model.db import (
44 _hash_key, func, case, joinedload, or_, in_filter_generator,
45 _hash_key, func, case, joinedload, or_, in_filter_generator,
45 Session, Repository, UserRepoToPerm, UserGroupRepoToPerm,
46 Session, Repository, UserRepoToPerm, UserGroupRepoToPerm,
46 UserRepoGroupToPerm, UserGroupRepoGroupToPerm, User, Permission,
47 UserRepoGroupToPerm, UserGroupRepoGroupToPerm, User, Permission,
47 Statistics, UserGroup, RepoGroup, RepositoryField, UserLog)
48 Statistics, UserGroup, RepoGroup, RepositoryField, UserLog)
48 from rhodecode.model.permission import PermissionModel
49 from rhodecode.model.permission import PermissionModel
49 from rhodecode.model.settings import VcsSettingsModel
50 from rhodecode.model.settings import VcsSettingsModel
50
51
51 log = logging.getLogger(__name__)
52 log = logging.getLogger(__name__)
52
53
53
54
54 class RepoModel(BaseModel):
55 class RepoModel(BaseModel):
55
56
56 cls = Repository
57 cls = Repository
57
58
58 def _get_user_group(self, users_group):
59 def _get_user_group(self, users_group):
59 return self._get_instance(UserGroup, users_group,
60 return self._get_instance(UserGroup, users_group,
60 callback=UserGroup.get_by_group_name)
61 callback=UserGroup.get_by_group_name)
61
62
62 def _get_repo_group(self, repo_group):
63 def _get_repo_group(self, repo_group):
63 return self._get_instance(RepoGroup, repo_group,
64 return self._get_instance(RepoGroup, repo_group,
64 callback=RepoGroup.get_by_group_name)
65 callback=RepoGroup.get_by_group_name)
65
66
66 def _create_default_perms(self, repository, private):
67 def _create_default_perms(self, repository, private):
67 # create default permission
68 # create default permission
68 default = 'repository.read'
69 default = 'repository.read'
69 def_user = User.get_default_user()
70 def_user = User.get_default_user()
70 for p in def_user.user_perms:
71 for p in def_user.user_perms:
71 if p.permission.permission_name.startswith('repository.'):
72 if p.permission.permission_name.startswith('repository.'):
72 default = p.permission.permission_name
73 default = p.permission.permission_name
73 break
74 break
74
75
75 default_perm = 'repository.none' if private else default
76 default_perm = 'repository.none' if private else default
76
77
77 repo_to_perm = UserRepoToPerm()
78 repo_to_perm = UserRepoToPerm()
78 repo_to_perm.permission = Permission.get_by_key(default_perm)
79 repo_to_perm.permission = Permission.get_by_key(default_perm)
79
80
80 repo_to_perm.repository = repository
81 repo_to_perm.repository = repository
81 repo_to_perm.user = def_user
82 repo_to_perm.user = def_user
82
83
83 return repo_to_perm
84 return repo_to_perm
84
85
85 @LazyProperty
86 @LazyProperty
86 def repos_path(self):
87 def repos_path(self):
87 """
88 """
88 Gets the repositories root path from database
89 Gets the repositories root path from database
89 """
90 """
90 settings_model = VcsSettingsModel(sa=self.sa)
91 settings_model = VcsSettingsModel(sa=self.sa)
91 return settings_model.get_repos_location()
92 return settings_model.get_repos_location()
92
93
93 def get(self, repo_id):
94 def get(self, repo_id):
94 repo = self.sa.query(Repository) \
95 repo = self.sa.query(Repository) \
95 .filter(Repository.repo_id == repo_id)
96 .filter(Repository.repo_id == repo_id)
96
97
97 return repo.scalar()
98 return repo.scalar()
98
99
99 def get_repo(self, repository):
100 def get_repo(self, repository):
100 return self._get_repo(repository)
101 return self._get_repo(repository)
101
102
102 def get_by_repo_name(self, repo_name, cache=False):
103 def get_by_repo_name(self, repo_name, cache=False):
103 repo = self.sa.query(Repository) \
104 repo = self.sa.query(Repository) \
104 .filter(Repository.repo_name == repo_name)
105 .filter(Repository.repo_name == repo_name)
105
106
106 if cache:
107 if cache:
107 name_key = _hash_key(repo_name)
108 name_key = _hash_key(repo_name)
108 repo = repo.options(
109 repo = repo.options(
109 FromCache("sql_cache_short", f"get_repo_{name_key}"))
110 FromCache("sql_cache_short", f"get_repo_{name_key}"))
110 return repo.scalar()
111 return repo.scalar()
111
112
112 def _extract_id_from_repo_name(self, repo_name):
113 def _extract_id_from_repo_name(self, repo_name):
113 if repo_name.startswith('/'):
114 if repo_name.startswith('/'):
114 repo_name = repo_name.lstrip('/')
115 repo_name = repo_name.lstrip('/')
115 by_id_match = re.match(r'^_(\d+)', repo_name)
116 by_id_match = re.match(r'^_(\d+)', repo_name)
116 if by_id_match:
117 if by_id_match:
117 return by_id_match.groups()[0]
118 return by_id_match.groups()[0]
118
119
119 def get_repo_by_id(self, repo_name):
120 def get_repo_by_id(self, repo_name):
120 """
121 """
121 Extracts repo_name by id from special urls.
122 Extracts repo_name by id from special urls.
122 Example url is _11/repo_name
123 Example url is _11/repo_name
123
124
124 :param repo_name:
125 :param repo_name:
125 :return: repo object if matched else None
126 :return: repo object if matched else None
126 """
127 """
127 _repo_id = None
128 _repo_id = None
128 try:
129 try:
129 _repo_id = self._extract_id_from_repo_name(repo_name)
130 _repo_id = self._extract_id_from_repo_name(repo_name)
130 if _repo_id:
131 if _repo_id:
131 return self.get(_repo_id)
132 return self.get(_repo_id)
132 except Exception:
133 except Exception:
133 log.exception('Failed to extract repo_name from URL')
134 log.exception('Failed to extract repo_name from URL')
134 if _repo_id:
135 if _repo_id:
135 Session().rollback()
136 Session().rollback()
136
137
137 return None
138 return None
138
139
139 def get_repos_for_root(self, root, traverse=False):
140 def get_repos_for_root(self, root, traverse=False):
140 if traverse:
141 if traverse:
141 like_expression = u'{}%'.format(safe_str(root))
142 like_expression = u'{}%'.format(safe_str(root))
142 repos = Repository.query().filter(
143 repos = Repository.query().filter(
143 Repository.repo_name.like(like_expression)).all()
144 Repository.repo_name.like(like_expression)).all()
144 else:
145 else:
145 if root and not isinstance(root, RepoGroup):
146 if root and not isinstance(root, RepoGroup):
146 raise ValueError(
147 raise ValueError(
147 'Root must be an instance '
148 'Root must be an instance '
148 'of RepoGroup, got:{} instead'.format(type(root)))
149 'of RepoGroup, got:{} instead'.format(type(root)))
149 repos = Repository.query().filter(Repository.group == root).all()
150 repos = Repository.query().filter(Repository.group == root).all()
150 return repos
151 return repos
151
152
152 def get_url(self, repo, request=None, permalink=False):
153 def get_url(self, repo, request=None, permalink=False):
153 if not request:
154 if not request:
154 request = get_current_request()
155 request = get_current_request()
155
156
156 if not request:
157 if not request:
157 return
158 return
158
159
159 if permalink:
160 if permalink:
160 return request.route_url(
161 return request.route_url(
161 'repo_summary', repo_name='_{}'.format(safe_str(repo.repo_id)))
162 'repo_summary', repo_name='_{}'.format(safe_str(repo.repo_id)))
162 else:
163 else:
163 return request.route_url(
164 return request.route_url(
164 'repo_summary', repo_name=safe_str(repo.repo_name))
165 'repo_summary', repo_name=safe_str(repo.repo_name))
165
166
166 def get_commit_url(self, repo, commit_id, request=None, permalink=False):
167 def get_commit_url(self, repo, commit_id, request=None, permalink=False):
167 if not request:
168 if not request:
168 request = get_current_request()
169 request = get_current_request()
169
170
170 if not request:
171 if not request:
171 return
172 return
172
173
173 if permalink:
174 if permalink:
174 return request.route_url(
175 return request.route_url(
175 'repo_commit', repo_name=safe_str(repo.repo_id),
176 'repo_commit', repo_name=safe_str(repo.repo_id),
176 commit_id=commit_id)
177 commit_id=commit_id)
177
178
178 else:
179 else:
179 return request.route_url(
180 return request.route_url(
180 'repo_commit', repo_name=safe_str(repo.repo_name),
181 'repo_commit', repo_name=safe_str(repo.repo_name),
181 commit_id=commit_id)
182 commit_id=commit_id)
182
183
183 def get_repo_log(self, repo, filter_term):
184 def get_repo_log(self, repo, filter_term):
184 repo_log = UserLog.query()\
185 repo_log = UserLog.query()\
185 .filter(or_(UserLog.repository_id == repo.repo_id,
186 .filter(or_(UserLog.repository_id == repo.repo_id,
186 UserLog.repository_name == repo.repo_name))\
187 UserLog.repository_name == repo.repo_name))\
187 .options(joinedload(UserLog.user))\
188 .options(joinedload(UserLog.user))\
188 .options(joinedload(UserLog.repository))\
189 .options(joinedload(UserLog.repository))\
189 .order_by(UserLog.action_date.desc())
190 .order_by(UserLog.action_date.desc())
190
191
191 repo_log = user_log_filter(repo_log, filter_term)
192 repo_log = user_log_filter(repo_log, filter_term)
192 return repo_log
193 return repo_log
193
194
194 @classmethod
195 @classmethod
195 def update_commit_cache(cls, repositories=None):
196 def update_commit_cache(cls, repositories=None):
196 if not repositories:
197 if not repositories:
197 repositories = Repository.getAll()
198 repositories = Repository.getAll()
198 for repo in repositories:
199 for repo in repositories:
199 repo.update_commit_cache()
200 repo.update_commit_cache()
200
201
201 def get_repos_as_dict(self, repo_list=None, admin=False,
202 def get_repos_as_dict(self, repo_list=None, admin=False,
202 super_user_actions=False, short_name=None):
203 super_user_actions=False, short_name=None):
203
204
204 _render = get_current_request().get_partial_renderer(
205 _render = get_current_request().get_partial_renderer(
205 'rhodecode:templates/data_table/_dt_elements.mako')
206 'rhodecode:templates/data_table/_dt_elements.mako')
206 c = _render.get_call_context()
207 c = _render.get_call_context()
207 h = _render.get_helpers()
208 h = _render.get_helpers()
208
209
209 def quick_menu(repo_name):
210 def quick_menu(repo_name):
210 return _render('quick_menu', repo_name)
211 return _render('quick_menu', repo_name)
211
212
212 def repo_lnk(name, rtype, rstate, private, archived, fork_repo_name):
213 def repo_lnk(name, rtype, rstate, private, archived, fork_repo_name):
213 if short_name is not None:
214 if short_name is not None:
214 short_name_var = short_name
215 short_name_var = short_name
215 else:
216 else:
216 short_name_var = not admin
217 short_name_var = not admin
217 return _render('repo_name', name, rtype, rstate, private, archived, fork_repo_name,
218 return _render('repo_name', name, rtype, rstate, private, archived, fork_repo_name,
218 short_name=short_name_var, admin=False)
219 short_name=short_name_var, admin=False)
219
220
220 def last_change(last_change):
221 def last_change(last_change):
221 if admin and isinstance(last_change, datetime.datetime) and not last_change.tzinfo:
222 if admin and isinstance(last_change, datetime.datetime) and not last_change.tzinfo:
222 ts = time.time()
223 ts = time.time()
223 utc_offset = (datetime.datetime.fromtimestamp(ts)
224 utc_offset = (datetime.datetime.fromtimestamp(ts)
224 - datetime.datetime.utcfromtimestamp(ts)).total_seconds()
225 - datetime.datetime.utcfromtimestamp(ts)).total_seconds()
225 last_change = last_change + datetime.timedelta(seconds=utc_offset)
226 last_change = last_change + datetime.timedelta(seconds=utc_offset)
226
227
227 return _render("last_change", last_change)
228 return _render("last_change", last_change)
228
229
229 def rss_lnk(repo_name):
230 def rss_lnk(repo_name):
230 return _render("rss", repo_name)
231 return _render("rss", repo_name)
231
232
232 def atom_lnk(repo_name):
233 def atom_lnk(repo_name):
233 return _render("atom", repo_name)
234 return _render("atom", repo_name)
234
235
235 def last_rev(repo_name, cs_cache):
236 def last_rev(repo_name, cs_cache):
236 return _render('revision', repo_name, cs_cache.get('revision'),
237 return _render('revision', repo_name, cs_cache.get('revision'),
237 cs_cache.get('raw_id'), cs_cache.get('author'),
238 cs_cache.get('raw_id'), cs_cache.get('author'),
238 cs_cache.get('message'), cs_cache.get('date'))
239 cs_cache.get('message'), cs_cache.get('date'))
239
240
240 def desc(desc):
241 def desc(desc):
241 return _render('repo_desc', desc, c.visual.stylify_metatags)
242 return _render('repo_desc', desc, c.visual.stylify_metatags)
242
243
243 def state(repo_state):
244 def state(repo_state):
244 return _render("repo_state", repo_state)
245 return _render("repo_state", repo_state)
245
246
246 def repo_actions(repo_name):
247 def repo_actions(repo_name):
247 return _render('repo_actions', repo_name, super_user_actions)
248 return _render('repo_actions', repo_name, super_user_actions)
248
249
249 def user_profile(username):
250 def user_profile(username):
250 return _render('user_profile', username)
251 return _render('user_profile', username)
251
252
252 repos_data = []
253 repos_data = []
253 for repo in repo_list:
254 for repo in repo_list:
254 # NOTE(marcink): because we use only raw column we need to load it like that
255 # NOTE(marcink): because we use only raw column we need to load it like that
255 changeset_cache = Repository._load_changeset_cache(
256 changeset_cache = Repository._load_changeset_cache(
256 repo.repo_id, repo._changeset_cache)
257 repo.repo_id, repo._changeset_cache)
257
258
258 row = {
259 row = {
259 "menu": quick_menu(repo.repo_name),
260 "menu": quick_menu(repo.repo_name),
260
261
261 "name": repo_lnk(repo.repo_name, repo.repo_type, repo.repo_state,
262 "name": repo_lnk(repo.repo_name, repo.repo_type, repo.repo_state,
262 repo.private, repo.archived, repo.fork_repo_name),
263 repo.private, repo.archived, repo.fork_repo_name),
263
264
264 "desc": desc(h.escape(repo.description)),
265 "desc": desc(h.escape(repo.description)),
265
266
266 "last_change": last_change(repo.updated_on),
267 "last_change": last_change(repo.updated_on),
267
268
268 "last_changeset": last_rev(repo.repo_name, changeset_cache),
269 "last_changeset": last_rev(repo.repo_name, changeset_cache),
269 "last_changeset_raw": changeset_cache.get('revision'),
270 "last_changeset_raw": changeset_cache.get('revision'),
270
271
271 "owner": user_profile(repo.owner_username),
272 "owner": user_profile(repo.owner_username),
272
273
273 "state": state(repo.repo_state),
274 "state": state(repo.repo_state),
274 "rss": rss_lnk(repo.repo_name),
275 "rss": rss_lnk(repo.repo_name),
275 "atom": atom_lnk(repo.repo_name),
276 "atom": atom_lnk(repo.repo_name),
276 }
277 }
277 if admin:
278 if admin:
278 row.update({
279 row.update({
279 "action": repo_actions(repo.repo_name),
280 "action": repo_actions(repo.repo_name),
280 })
281 })
281 repos_data.append(row)
282 repos_data.append(row)
282
283
283 return repos_data
284 return repos_data
284
285
285 def get_repos_data_table(
286 def get_repos_data_table(
286 self, draw, start, limit,
287 self, draw, start, limit,
287 search_q, order_by, order_dir,
288 search_q, order_by, order_dir,
288 auth_user, repo_group_id):
289 auth_user, repo_group_id):
289 from rhodecode.model.scm import RepoList
290 from rhodecode.model.scm import RepoList
290
291
291 _perms = ['repository.read', 'repository.write', 'repository.admin']
292 _perms = ['repository.read', 'repository.write', 'repository.admin']
292
293
293 repos = Repository.query() \
294 repos = Repository.query() \
294 .filter(Repository.group_id == repo_group_id) \
295 .filter(Repository.group_id == repo_group_id) \
295 .all()
296 .all()
296 auth_repo_list = RepoList(
297 auth_repo_list = RepoList(
297 repos, perm_set=_perms,
298 repos, perm_set=_perms,
298 extra_kwargs=dict(user=auth_user))
299 extra_kwargs=dict(user=auth_user))
299
300
300 allowed_ids = [-1]
301 allowed_ids = [-1]
301 for repo in auth_repo_list:
302 for repo in auth_repo_list:
302 allowed_ids.append(repo.repo_id)
303 allowed_ids.append(repo.repo_id)
303
304
304 repos_data_total_count = Repository.query() \
305 repos_data_total_count = Repository.query() \
305 .filter(Repository.group_id == repo_group_id) \
306 .filter(Repository.group_id == repo_group_id) \
306 .filter(or_(
307 .filter(or_(
307 # generate multiple IN to fix limitation problems
308 # generate multiple IN to fix limitation problems
308 *in_filter_generator(Repository.repo_id, allowed_ids))
309 *in_filter_generator(Repository.repo_id, allowed_ids))
309 ) \
310 ) \
310 .count()
311 .count()
311
312
312 RepoFork = aliased(Repository)
313 RepoFork = aliased(Repository)
313 OwnerUser = aliased(User)
314 OwnerUser = aliased(User)
314 base_q = Session.query(
315 base_q = Session.query(
315 Repository.repo_id,
316 Repository.repo_id,
316 Repository.repo_name,
317 Repository.repo_name,
317 Repository.description,
318 Repository.description,
318 Repository.repo_type,
319 Repository.repo_type,
319 Repository.repo_state,
320 Repository.repo_state,
320 Repository.private,
321 Repository.private,
321 Repository.archived,
322 Repository.archived,
322 Repository.updated_on,
323 Repository.updated_on,
323 Repository._changeset_cache,
324 Repository._changeset_cache,
324 RepoFork.repo_name.label('fork_repo_name'),
325 RepoFork.repo_name.label('fork_repo_name'),
325 OwnerUser.username.label('owner_username'),
326 OwnerUser.username.label('owner_username'),
326 ) \
327 ) \
327 .filter(Repository.group_id == repo_group_id) \
328 .filter(Repository.group_id == repo_group_id) \
328 .filter(or_(
329 .filter(or_(
329 # generate multiple IN to fix limitation problems
330 # generate multiple IN to fix limitation problems
330 *in_filter_generator(Repository.repo_id, allowed_ids))
331 *in_filter_generator(Repository.repo_id, allowed_ids))
331 ) \
332 ) \
332 .outerjoin(RepoFork, Repository.fork_id == RepoFork.repo_id) \
333 .outerjoin(RepoFork, Repository.fork_id == RepoFork.repo_id) \
333 .join(OwnerUser, Repository.user_id == OwnerUser.user_id)
334 .join(OwnerUser, Repository.user_id == OwnerUser.user_id)
334
335
335 repos_data_total_filtered_count = base_q.count()
336 repos_data_total_filtered_count = base_q.count()
336
337
337 sort_defined = False
338 sort_defined = False
338 if order_by == 'repo_name':
339 if order_by == 'repo_name':
339 sort_col = func.lower(Repository.repo_name)
340 sort_col = func.lower(Repository.repo_name)
340 sort_defined = True
341 sort_defined = True
341 elif order_by == 'user_username':
342 elif order_by == 'user_username':
342 sort_col = User.username
343 sort_col = User.username
343 else:
344 else:
344 sort_col = getattr(Repository, order_by, None)
345 sort_col = getattr(Repository, order_by, None)
345
346
346 if sort_defined or sort_col:
347 if sort_defined or sort_col:
347 if order_dir == 'asc':
348 if order_dir == 'asc':
348 sort_col = sort_col.asc()
349 sort_col = sort_col.asc()
349 else:
350 else:
350 sort_col = sort_col.desc()
351 sort_col = sort_col.desc()
351
352
352 base_q = base_q.order_by(sort_col)
353 base_q = base_q.order_by(sort_col)
353 base_q = base_q.offset(start).limit(limit)
354 base_q = base_q.offset(start).limit(limit)
354
355
355 repos_list = base_q.all()
356 repos_list = base_q.all()
356
357
357 repos_data = RepoModel().get_repos_as_dict(
358 repos_data = RepoModel().get_repos_as_dict(
358 repo_list=repos_list, admin=False)
359 repo_list=repos_list, admin=False)
359
360
360 data = ({
361 data = ({
361 'draw': draw,
362 'draw': draw,
362 'data': repos_data,
363 'data': repos_data,
363 'recordsTotal': repos_data_total_count,
364 'recordsTotal': repos_data_total_count,
364 'recordsFiltered': repos_data_total_filtered_count,
365 'recordsFiltered': repos_data_total_filtered_count,
365 })
366 })
366 return data
367 return data
367
368
368 def _get_defaults(self, repo_name):
369 def _get_defaults(self, repo_name):
369 """
370 """
370 Gets information about repository, and returns a dict for
371 Gets information about repository, and returns a dict for
371 usage in forms
372 usage in forms
372
373
373 :param repo_name:
374 :param repo_name:
374 """
375 """
375
376
376 repo_info = Repository.get_by_repo_name(repo_name)
377 repo_info = Repository.get_by_repo_name(repo_name)
377
378
378 if repo_info is None:
379 if repo_info is None:
379 return None
380 return None
380
381
381 defaults = repo_info.get_dict()
382 defaults = repo_info.get_dict()
382 defaults['repo_name'] = repo_info.just_name
383 defaults['repo_name'] = repo_info.just_name
383
384
384 groups = repo_info.groups_with_parents
385 groups = repo_info.groups_with_parents
385 parent_group = groups[-1] if groups else None
386 parent_group = groups[-1] if groups else None
386
387
387 # we use -1 as this is how in HTML, we mark an empty group
388 # we use -1 as this is how in HTML, we mark an empty group
388 defaults['repo_group'] = getattr(parent_group, 'group_id', -1)
389 defaults['repo_group'] = getattr(parent_group, 'group_id', -1)
389
390
390 keys_to_process = (
391 keys_to_process = (
391 {'k': 'repo_type', 'strip': False},
392 {'k': 'repo_type', 'strip': False},
392 {'k': 'repo_enable_downloads', 'strip': True},
393 {'k': 'repo_enable_downloads', 'strip': True},
393 {'k': 'repo_description', 'strip': True},
394 {'k': 'repo_description', 'strip': True},
394 {'k': 'repo_enable_locking', 'strip': True},
395 {'k': 'repo_enable_locking', 'strip': True},
395 {'k': 'repo_landing_rev', 'strip': True},
396 {'k': 'repo_landing_rev', 'strip': True},
396 {'k': 'clone_uri', 'strip': False},
397 {'k': 'clone_uri', 'strip': False},
397 {'k': 'push_uri', 'strip': False},
398 {'k': 'push_uri', 'strip': False},
398 {'k': 'repo_private', 'strip': True},
399 {'k': 'repo_private', 'strip': True},
399 {'k': 'repo_enable_statistics', 'strip': True}
400 {'k': 'repo_enable_statistics', 'strip': True}
400 )
401 )
401
402
402 for item in keys_to_process:
403 for item in keys_to_process:
403 attr = item['k']
404 attr = item['k']
404 if item['strip']:
405 if item['strip']:
405 attr = remove_prefix(item['k'], 'repo_')
406 attr = remove_prefix(item['k'], 'repo_')
406
407
407 val = defaults[attr]
408 val = defaults[attr]
408 if item['k'] == 'repo_landing_rev':
409 if item['k'] == 'repo_landing_rev':
409 val = ':'.join(defaults[attr])
410 val = ':'.join(defaults[attr])
410 defaults[item['k']] = val
411 defaults[item['k']] = val
411 if item['k'] == 'clone_uri':
412 if item['k'] == 'clone_uri':
412 defaults['clone_uri_hidden'] = repo_info.clone_uri_hidden
413 defaults['clone_uri_hidden'] = repo_info.clone_uri_hidden
413 if item['k'] == 'push_uri':
414 if item['k'] == 'push_uri':
414 defaults['push_uri_hidden'] = repo_info.push_uri_hidden
415 defaults['push_uri_hidden'] = repo_info.push_uri_hidden
415
416
416 # fill owner
417 # fill owner
417 if repo_info.user:
418 if repo_info.user:
418 defaults.update({'user': repo_info.user.username})
419 defaults.update({'user': repo_info.user.username})
419 else:
420 else:
420 replacement_user = User.get_first_super_admin().username
421 replacement_user = User.get_first_super_admin().username
421 defaults.update({'user': replacement_user})
422 defaults.update({'user': replacement_user})
422
423
423 return defaults
424 return defaults
424
425
425 def update(self, repo, **kwargs):
426 def update(self, repo, **kwargs):
426 try:
427 try:
427 cur_repo = self._get_repo(repo)
428 cur_repo = self._get_repo(repo)
428 source_repo_name = cur_repo.repo_name
429 source_repo_name = cur_repo.repo_name
429
430
430 affected_user_ids = []
431 affected_user_ids = []
431 if 'user' in kwargs:
432 if 'user' in kwargs:
432 old_owner_id = cur_repo.user.user_id
433 old_owner_id = cur_repo.user.user_id
433 new_owner = User.get_by_username(kwargs['user'])
434 new_owner = User.get_by_username(kwargs['user'])
434 cur_repo.user = new_owner
435 cur_repo.user = new_owner
435
436
436 if old_owner_id != new_owner.user_id:
437 if old_owner_id != new_owner.user_id:
437 affected_user_ids = [new_owner.user_id, old_owner_id]
438 affected_user_ids = [new_owner.user_id, old_owner_id]
438
439
439 if 'repo_group' in kwargs:
440 if 'repo_group' in kwargs:
440 cur_repo.group = RepoGroup.get(kwargs['repo_group'])
441 cur_repo.group = RepoGroup.get(kwargs['repo_group'])
441 log.debug('Updating repo %s with params:%s', cur_repo, kwargs)
442 log.debug('Updating repo %s with params:%s', cur_repo, kwargs)
442
443
443 update_keys = [
444 update_keys = [
444 (1, 'repo_description'),
445 (1, 'repo_description'),
445 (1, 'repo_landing_rev'),
446 (1, 'repo_landing_rev'),
446 (1, 'repo_private'),
447 (1, 'repo_private'),
447 (1, 'repo_enable_downloads'),
448 (1, 'repo_enable_downloads'),
448 (1, 'repo_enable_locking'),
449 (1, 'repo_enable_locking'),
449 (1, 'repo_enable_statistics'),
450 (1, 'repo_enable_statistics'),
450 (0, 'clone_uri'),
451 (0, 'clone_uri'),
451 (0, 'push_uri'),
452 (0, 'push_uri'),
452 (0, 'fork_id')
453 (0, 'fork_id')
453 ]
454 ]
454 for strip, k in update_keys:
455 for strip, k in update_keys:
455 if k in kwargs:
456 if k in kwargs:
456 val = kwargs[k]
457 val = kwargs[k]
457 if strip:
458 if strip:
458 k = remove_prefix(k, 'repo_')
459 k = remove_prefix(k, 'repo_')
459
460
460 setattr(cur_repo, k, val)
461 setattr(cur_repo, k, val)
461
462
462 new_name = cur_repo.get_new_name(kwargs['repo_name'])
463 new_name = cur_repo.get_new_name(kwargs['repo_name'])
463 cur_repo.repo_name = new_name
464 cur_repo.repo_name = new_name
464
465
465 # if private flag is set, reset default permission to NONE
466 # if private flag is set, reset default permission to NONE
466 if kwargs.get('repo_private'):
467 if kwargs.get('repo_private'):
467 EMPTY_PERM = 'repository.none'
468 EMPTY_PERM = 'repository.none'
468 RepoModel().grant_user_permission(
469 RepoModel().grant_user_permission(
469 repo=cur_repo, user=User.DEFAULT_USER, perm=EMPTY_PERM
470 repo=cur_repo, user=User.DEFAULT_USER, perm=EMPTY_PERM
470 )
471 )
471 if kwargs.get('repo_landing_rev'):
472 if kwargs.get('repo_landing_rev'):
472 landing_rev_val = kwargs['repo_landing_rev']
473 landing_rev_val = kwargs['repo_landing_rev']
473 RepoModel().set_landing_rev(cur_repo, landing_rev_val)
474 RepoModel().set_landing_rev(cur_repo, landing_rev_val)
474
475
475 # handle extra fields
476 # handle extra fields
476 for field in filter(lambda k: k.startswith(RepositoryField.PREFIX), kwargs):
477 for field in filter(lambda k: k.startswith(RepositoryField.PREFIX), kwargs):
477 k = RepositoryField.un_prefix_key(field)
478 k = RepositoryField.un_prefix_key(field)
478 ex_field = RepositoryField.get_by_key_name(
479 ex_field = RepositoryField.get_by_key_name(
479 key=k, repo=cur_repo)
480 key=k, repo=cur_repo)
480 if ex_field:
481 if ex_field:
481 ex_field.field_value = kwargs[field]
482 ex_field.field_value = kwargs[field]
482 self.sa.add(ex_field)
483 self.sa.add(ex_field)
483
484
484 self.sa.add(cur_repo)
485 self.sa.add(cur_repo)
485
486
486 if source_repo_name != new_name:
487 if source_repo_name != new_name:
487 # rename repository
488 # rename repository
488 self._rename_filesystem_repo(
489 self._rename_filesystem_repo(
489 old=source_repo_name, new=new_name)
490 old=source_repo_name, new=new_name)
490
491
491 if affected_user_ids:
492 if affected_user_ids:
492 PermissionModel().trigger_permission_flush(affected_user_ids)
493 PermissionModel().trigger_permission_flush(affected_user_ids)
493
494
494 return cur_repo
495 return cur_repo
495 except Exception:
496 except Exception:
496 log.error(traceback.format_exc())
497 log.error(traceback.format_exc())
497 raise
498 raise
498
499
499 def _create_repo(self, repo_name, repo_type, description, owner,
500 def _create_repo(self, repo_name, repo_type, description, owner,
500 private=False, clone_uri=None, repo_group=None,
501 private=False, clone_uri=None, repo_group=None,
501 landing_rev=None, fork_of=None,
502 landing_rev=None, fork_of=None,
502 copy_fork_permissions=False, enable_statistics=False,
503 copy_fork_permissions=False, enable_statistics=False,
503 enable_locking=False, enable_downloads=False,
504 enable_locking=False, enable_downloads=False,
504 copy_group_permissions=False,
505 copy_group_permissions=False,
505 state=Repository.STATE_PENDING):
506 state=Repository.STATE_PENDING):
506 """
507 """
507 Create repository inside database with PENDING state, this should be
508 Create repository inside database with PENDING state, this should be
508 only executed by create() repo. With exception of importing existing
509 only executed by create() repo. With exception of importing existing
509 repos
510 repos
510 """
511 """
511 from rhodecode.model.scm import ScmModel
512 from rhodecode.model.scm import ScmModel
512
513
513 owner = self._get_user(owner)
514 owner = self._get_user(owner)
514 fork_of = self._get_repo(fork_of)
515 fork_of = self._get_repo(fork_of)
515 repo_group = self._get_repo_group(safe_int(repo_group))
516 repo_group = self._get_repo_group(safe_int(repo_group))
516 default_landing_ref, _lbl = ScmModel.backend_landing_ref(repo_type)
517 default_landing_ref, _lbl = ScmModel.backend_landing_ref(repo_type)
517 landing_rev = landing_rev or default_landing_ref
518 landing_rev = landing_rev or default_landing_ref
518
519
519 try:
520 try:
520 repo_name = safe_str(repo_name)
521 repo_name = safe_str(repo_name)
521 description = safe_str(description)
522 description = safe_str(description)
522 # repo name is just a name of repository
523 # repo name is just a name of repository
523 # while repo_name_full is a full qualified name that is combined
524 # while repo_name_full is a full qualified name that is combined
524 # with name and path of group
525 # with name and path of group
525 repo_name_full = repo_name
526 repo_name_full = repo_name
526 repo_name = repo_name.split(Repository.NAME_SEP)[-1]
527 repo_name = repo_name.split(Repository.NAME_SEP)[-1]
527
528
528 new_repo = Repository()
529 new_repo = Repository()
529 new_repo.repo_state = state
530 new_repo.repo_state = state
530 new_repo.enable_statistics = False
531 new_repo.enable_statistics = False
531 new_repo.repo_name = repo_name_full
532 new_repo.repo_name = repo_name_full
532 new_repo.repo_type = repo_type
533 new_repo.repo_type = repo_type
533 new_repo.user = owner
534 new_repo.user = owner
534 new_repo.group = repo_group
535 new_repo.group = repo_group
535 new_repo.description = description or repo_name
536 new_repo.description = description or repo_name
536 new_repo.private = private
537 new_repo.private = private
537 new_repo.archived = False
538 new_repo.archived = False
538 new_repo.clone_uri = clone_uri
539 new_repo.clone_uri = clone_uri
539 new_repo.landing_rev = landing_rev
540 new_repo.landing_rev = landing_rev
540
541
541 new_repo.enable_statistics = enable_statistics
542 new_repo.enable_statistics = enable_statistics
542 new_repo.enable_locking = enable_locking
543 new_repo.enable_locking = enable_locking
543 new_repo.enable_downloads = enable_downloads
544 new_repo.enable_downloads = enable_downloads
544
545
545 if repo_group:
546 if repo_group:
546 new_repo.enable_locking = repo_group.enable_locking
547 new_repo.enable_locking = repo_group.enable_locking
547
548
548 if fork_of:
549 if fork_of:
549 parent_repo = fork_of
550 parent_repo = fork_of
550 new_repo.fork = parent_repo
551 new_repo.fork = parent_repo
551
552
552 events.trigger(events.RepoPreCreateEvent(new_repo))
553 events.trigger(events.RepoPreCreateEvent(new_repo))
553
554
554 self.sa.add(new_repo)
555 self.sa.add(new_repo)
555
556
556 EMPTY_PERM = 'repository.none'
557 EMPTY_PERM = 'repository.none'
557 if fork_of and copy_fork_permissions:
558 if fork_of and copy_fork_permissions:
558 repo = fork_of
559 repo = fork_of
559 user_perms = UserRepoToPerm.query() \
560 user_perms = UserRepoToPerm.query() \
560 .filter(UserRepoToPerm.repository == repo).all()
561 .filter(UserRepoToPerm.repository == repo).all()
561 group_perms = UserGroupRepoToPerm.query() \
562 group_perms = UserGroupRepoToPerm.query() \
562 .filter(UserGroupRepoToPerm.repository == repo).all()
563 .filter(UserGroupRepoToPerm.repository == repo).all()
563
564
564 for perm in user_perms:
565 for perm in user_perms:
565 UserRepoToPerm.create(
566 UserRepoToPerm.create(
566 perm.user, new_repo, perm.permission)
567 perm.user, new_repo, perm.permission)
567
568
568 for perm in group_perms:
569 for perm in group_perms:
569 UserGroupRepoToPerm.create(
570 UserGroupRepoToPerm.create(
570 perm.users_group, new_repo, perm.permission)
571 perm.users_group, new_repo, perm.permission)
571 # in case we copy permissions and also set this repo to private
572 # in case we copy permissions and also set this repo to private
572 # override the default user permission to make it a private repo
573 # override the default user permission to make it a private repo
573 if private:
574 if private:
574 RepoModel(self.sa).grant_user_permission(
575 RepoModel(self.sa).grant_user_permission(
575 repo=new_repo, user=User.DEFAULT_USER, perm=EMPTY_PERM)
576 repo=new_repo, user=User.DEFAULT_USER, perm=EMPTY_PERM)
576
577
577 elif repo_group and copy_group_permissions:
578 elif repo_group and copy_group_permissions:
578 user_perms = UserRepoGroupToPerm.query() \
579 user_perms = UserRepoGroupToPerm.query() \
579 .filter(UserRepoGroupToPerm.group == repo_group).all()
580 .filter(UserRepoGroupToPerm.group == repo_group).all()
580
581
581 group_perms = UserGroupRepoGroupToPerm.query() \
582 group_perms = UserGroupRepoGroupToPerm.query() \
582 .filter(UserGroupRepoGroupToPerm.group == repo_group).all()
583 .filter(UserGroupRepoGroupToPerm.group == repo_group).all()
583
584
584 for perm in user_perms:
585 for perm in user_perms:
585 perm_name = perm.permission.permission_name.replace(
586 perm_name = perm.permission.permission_name.replace(
586 'group.', 'repository.')
587 'group.', 'repository.')
587 perm_obj = Permission.get_by_key(perm_name)
588 perm_obj = Permission.get_by_key(perm_name)
588 UserRepoToPerm.create(perm.user, new_repo, perm_obj)
589 UserRepoToPerm.create(perm.user, new_repo, perm_obj)
589
590
590 for perm in group_perms:
591 for perm in group_perms:
591 perm_name = perm.permission.permission_name.replace(
592 perm_name = perm.permission.permission_name.replace(
592 'group.', 'repository.')
593 'group.', 'repository.')
593 perm_obj = Permission.get_by_key(perm_name)
594 perm_obj = Permission.get_by_key(perm_name)
594 UserGroupRepoToPerm.create(perm.users_group, new_repo, perm_obj)
595 UserGroupRepoToPerm.create(perm.users_group, new_repo, perm_obj)
595
596
596 if private:
597 if private:
597 RepoModel(self.sa).grant_user_permission(
598 RepoModel(self.sa).grant_user_permission(
598 repo=new_repo, user=User.DEFAULT_USER, perm=EMPTY_PERM)
599 repo=new_repo, user=User.DEFAULT_USER, perm=EMPTY_PERM)
599
600
600 else:
601 else:
601 perm_obj = self._create_default_perms(new_repo, private)
602 perm_obj = self._create_default_perms(new_repo, private)
602 self.sa.add(perm_obj)
603 self.sa.add(perm_obj)
603
604
604 # now automatically start following this repository as owner
605 # now automatically start following this repository as owner
605 ScmModel(self.sa).toggle_following_repo(new_repo.repo_id, owner.user_id)
606 ScmModel(self.sa).toggle_following_repo(new_repo.repo_id, owner.user_id)
606
607
607 # we need to flush here, in order to check if database won't
608 # we need to flush here, in order to check if database won't
608 # throw any exceptions, create filesystem dirs at the very end
609 # throw any exceptions, create filesystem dirs at the very end
609 self.sa.flush()
610 self.sa.flush()
610 events.trigger(events.RepoCreateEvent(new_repo))
611 events.trigger(events.RepoCreateEvent(new_repo))
611 return new_repo
612 return new_repo
612
613
613 except Exception:
614 except Exception:
614 log.error(traceback.format_exc())
615 log.error(traceback.format_exc())
615 raise
616 raise
616
617
617 def create(self, form_data, cur_user):
618 def create(self, form_data, cur_user):
618 """
619 """
619 Create repository using celery tasks
620 Create repository using celery tasks
620
621
621 :param form_data:
622 :param form_data:
622 :param cur_user:
623 :param cur_user:
623 """
624 """
624 from rhodecode.lib.celerylib import tasks, run_task
625 from rhodecode.lib.celerylib import tasks, run_task
625 return run_task(tasks.create_repo, form_data, cur_user)
626 return run_task(tasks.create_repo, form_data, cur_user)
626
627
627 def update_permissions(self, repo, perm_additions=None, perm_updates=None,
628 def update_permissions(self, repo, perm_additions=None, perm_updates=None,
628 perm_deletions=None, check_perms=True,
629 perm_deletions=None, check_perms=True,
629 cur_user=None):
630 cur_user=None):
630 if not perm_additions:
631 if not perm_additions:
631 perm_additions = []
632 perm_additions = []
632 if not perm_updates:
633 if not perm_updates:
633 perm_updates = []
634 perm_updates = []
634 if not perm_deletions:
635 if not perm_deletions:
635 perm_deletions = []
636 perm_deletions = []
636
637
637 req_perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin')
638 req_perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin')
638
639
639 changes = {
640 changes = {
640 'added': [],
641 'added': [],
641 'updated': [],
642 'updated': [],
642 'deleted': [],
643 'deleted': [],
643 'default_user_changed': None
644 'default_user_changed': None
644 }
645 }
645
646
646 repo = self._get_repo(repo)
647 repo = self._get_repo(repo)
647
648
648 # update permissions
649 # update permissions
649 for member_id, perm, member_type in perm_updates:
650 for member_id, perm, member_type in perm_updates:
650 member_id = int(member_id)
651 member_id = int(member_id)
651 if member_type == 'user':
652 if member_type == 'user':
652 member_name = User.get(member_id).username
653 member_name = User.get(member_id).username
653 if member_name == User.DEFAULT_USER:
654 if member_name == User.DEFAULT_USER:
654 # NOTE(dan): detect if we changed permissions for default user
655 # NOTE(dan): detect if we changed permissions for default user
655 perm_obj = self.sa.query(UserRepoToPerm) \
656 perm_obj = self.sa.query(UserRepoToPerm) \
656 .filter(UserRepoToPerm.user_id == member_id) \
657 .filter(UserRepoToPerm.user_id == member_id) \
657 .filter(UserRepoToPerm.repository == repo) \
658 .filter(UserRepoToPerm.repository == repo) \
658 .scalar()
659 .scalar()
659 if perm_obj and perm_obj.permission.permission_name != perm:
660 if perm_obj and perm_obj.permission.permission_name != perm:
660 changes['default_user_changed'] = True
661 changes['default_user_changed'] = True
661
662
662 # this updates also current one if found
663 # this updates also current one if found
663 self.grant_user_permission(
664 self.grant_user_permission(
664 repo=repo, user=member_id, perm=perm)
665 repo=repo, user=member_id, perm=perm)
665 elif member_type == 'user_group':
666 elif member_type == 'user_group':
666 # check if we have permissions to alter this usergroup
667 # check if we have permissions to alter this usergroup
667 member_name = UserGroup.get(member_id).users_group_name
668 member_name = UserGroup.get(member_id).users_group_name
668 if not check_perms or HasUserGroupPermissionAny(
669 if not check_perms or HasUserGroupPermissionAny(
669 *req_perms)(member_name, user=cur_user):
670 *req_perms)(member_name, user=cur_user):
670 self.grant_user_group_permission(
671 self.grant_user_group_permission(
671 repo=repo, group_name=member_id, perm=perm)
672 repo=repo, group_name=member_id, perm=perm)
672 else:
673 else:
673 raise ValueError("member_type must be 'user' or 'user_group' "
674 raise ValueError("member_type must be 'user' or 'user_group' "
674 "got {} instead".format(member_type))
675 "got {} instead".format(member_type))
675 changes['updated'].append({'type': member_type, 'id': member_id,
676 changes['updated'].append({'type': member_type, 'id': member_id,
676 'name': member_name, 'new_perm': perm})
677 'name': member_name, 'new_perm': perm})
677
678
678 # set new permissions
679 # set new permissions
679 for member_id, perm, member_type in perm_additions:
680 for member_id, perm, member_type in perm_additions:
680 member_id = int(member_id)
681 member_id = int(member_id)
681 if member_type == 'user':
682 if member_type == 'user':
682 member_name = User.get(member_id).username
683 member_name = User.get(member_id).username
683 self.grant_user_permission(
684 self.grant_user_permission(
684 repo=repo, user=member_id, perm=perm)
685 repo=repo, user=member_id, perm=perm)
685 elif member_type == 'user_group':
686 elif member_type == 'user_group':
686 # check if we have permissions to alter this usergroup
687 # check if we have permissions to alter this usergroup
687 member_name = UserGroup.get(member_id).users_group_name
688 member_name = UserGroup.get(member_id).users_group_name
688 if not check_perms or HasUserGroupPermissionAny(
689 if not check_perms or HasUserGroupPermissionAny(
689 *req_perms)(member_name, user=cur_user):
690 *req_perms)(member_name, user=cur_user):
690 self.grant_user_group_permission(
691 self.grant_user_group_permission(
691 repo=repo, group_name=member_id, perm=perm)
692 repo=repo, group_name=member_id, perm=perm)
692 else:
693 else:
693 raise ValueError("member_type must be 'user' or 'user_group' "
694 raise ValueError("member_type must be 'user' or 'user_group' "
694 "got {} instead".format(member_type))
695 "got {} instead".format(member_type))
695
696
696 changes['added'].append({'type': member_type, 'id': member_id,
697 changes['added'].append({'type': member_type, 'id': member_id,
697 'name': member_name, 'new_perm': perm})
698 'name': member_name, 'new_perm': perm})
698 # delete permissions
699 # delete permissions
699 for member_id, perm, member_type in perm_deletions:
700 for member_id, perm, member_type in perm_deletions:
700 member_id = int(member_id)
701 member_id = int(member_id)
701 if member_type == 'user':
702 if member_type == 'user':
702 member_name = User.get(member_id).username
703 member_name = User.get(member_id).username
703 self.revoke_user_permission(repo=repo, user=member_id)
704 self.revoke_user_permission(repo=repo, user=member_id)
704 elif member_type == 'user_group':
705 elif member_type == 'user_group':
705 # check if we have permissions to alter this usergroup
706 # check if we have permissions to alter this usergroup
706 member_name = UserGroup.get(member_id).users_group_name
707 member_name = UserGroup.get(member_id).users_group_name
707 if not check_perms or HasUserGroupPermissionAny(
708 if not check_perms or HasUserGroupPermissionAny(
708 *req_perms)(member_name, user=cur_user):
709 *req_perms)(member_name, user=cur_user):
709 self.revoke_user_group_permission(
710 self.revoke_user_group_permission(
710 repo=repo, group_name=member_id)
711 repo=repo, group_name=member_id)
711 else:
712 else:
712 raise ValueError("member_type must be 'user' or 'user_group' "
713 raise ValueError("member_type must be 'user' or 'user_group' "
713 "got {} instead".format(member_type))
714 "got {} instead".format(member_type))
714
715
715 changes['deleted'].append({'type': member_type, 'id': member_id,
716 changes['deleted'].append({'type': member_type, 'id': member_id,
716 'name': member_name, 'new_perm': perm})
717 'name': member_name, 'new_perm': perm})
717 return changes
718 return changes
718
719
719 def create_fork(self, form_data, cur_user):
720 def create_fork(self, form_data, cur_user):
720 """
721 """
721 Simple wrapper into executing celery task for fork creation
722 Simple wrapper into executing celery task for fork creation
722
723
723 :param form_data:
724 :param form_data:
724 :param cur_user:
725 :param cur_user:
725 """
726 """
726 from rhodecode.lib.celerylib import tasks, run_task
727 from rhodecode.lib.celerylib import tasks, run_task
727 return run_task(tasks.create_repo_fork, form_data, cur_user)
728 return run_task(tasks.create_repo_fork, form_data, cur_user)
728
729
729 def archive(self, repo):
730 def archive(self, repo):
730 """
731 """
731 Archive given repository. Set archive flag.
732 Archive given repository. Set archive flag.
732
733
733 :param repo:
734 :param repo:
734 """
735 """
735 repo = self._get_repo(repo)
736 repo = self._get_repo(repo)
736 if repo:
737 if repo:
737
738
738 try:
739 try:
739 repo.archived = True
740 repo.archived = True
740 self.sa.add(repo)
741 self.sa.add(repo)
741 self.sa.commit()
742 self.sa.commit()
742 except Exception:
743 except Exception:
743 log.error(traceback.format_exc())
744 log.error(traceback.format_exc())
744 raise
745 raise
745
746
746 def delete(self, repo, forks=None, pull_requests=None, fs_remove=True, cur_user=None):
747 def delete(self, repo, forks=None, pull_requests=None, fs_remove=True, cur_user=None):
747 """
748 """
748 Delete given repository, forks parameter defines what do do with
749 Delete given repository, forks parameter defines what do do with
749 attached forks. Throws AttachedForksError if deleted repo has attached
750 attached forks. Throws AttachedForksError if deleted repo has attached
750 forks
751 forks
751
752
752 :param repo:
753 :param repo:
753 :param forks: str 'delete' or 'detach'
754 :param forks: str 'delete' or 'detach'
754 :param pull_requests: str 'delete' or None
755 :param pull_requests: str 'delete' or None
755 :param fs_remove: remove(archive) repo from filesystem
756 :param fs_remove: remove(archive) repo from filesystem
756 """
757 """
757 if not cur_user:
758 if not cur_user:
758 cur_user = getattr(get_current_rhodecode_user(), 'username', None)
759 cur_user = getattr(get_current_rhodecode_user(), 'username', None)
759 repo = self._get_repo(repo)
760 repo = self._get_repo(repo)
760 if repo:
761 if repo:
761 if forks == 'detach':
762 if forks == 'detach':
762 for r in repo.forks:
763 for r in repo.forks:
763 r.fork = None
764 r.fork = None
764 self.sa.add(r)
765 self.sa.add(r)
765 elif forks == 'delete':
766 elif forks == 'delete':
766 for r in repo.forks:
767 for r in repo.forks:
767 self.delete(r, forks='delete')
768 self.delete(r, forks='delete')
768 elif [f for f in repo.forks]:
769 elif [f for f in repo.forks]:
769 raise AttachedForksError()
770 raise AttachedForksError()
770
771
771 # check for pull requests
772 # check for pull requests
772 pr_sources = repo.pull_requests_source
773 pr_sources = repo.pull_requests_source
773 pr_targets = repo.pull_requests_target
774 pr_targets = repo.pull_requests_target
774 if pull_requests != 'delete' and (pr_sources or pr_targets):
775 if pull_requests != 'delete' and (pr_sources or pr_targets):
775 raise AttachedPullRequestsError()
776 raise AttachedPullRequestsError()
776
777
777 old_repo_dict = repo.get_dict()
778 old_repo_dict = repo.get_dict()
778 events.trigger(events.RepoPreDeleteEvent(repo))
779 events.trigger(events.RepoPreDeleteEvent(repo))
779 try:
780 try:
780 self.sa.delete(repo)
781 self.sa.delete(repo)
781 if fs_remove:
782 if fs_remove:
782 self._delete_filesystem_repo(repo)
783 self._delete_filesystem_repo(repo)
783 else:
784 else:
784 log.debug('skipping removal from filesystem')
785 log.debug('skipping removal from filesystem')
785 old_repo_dict.update({
786 old_repo_dict.update({
786 'deleted_by': cur_user,
787 'deleted_by': cur_user,
787 'deleted_on': time.time(),
788 'deleted_on': time.time(),
788 })
789 })
789 hooks_base.delete_repository(**old_repo_dict)
790 hooks_base.delete_repository(**old_repo_dict)
790 events.trigger(events.RepoDeleteEvent(repo))
791 events.trigger(events.RepoDeleteEvent(repo))
791 except Exception:
792 except Exception:
792 log.error(traceback.format_exc())
793 log.error(traceback.format_exc())
793 raise
794 raise
794
795
795 def grant_user_permission(self, repo, user, perm):
796 def grant_user_permission(self, repo, user, perm):
796 """
797 """
797 Grant permission for user on given repository, or update existing one
798 Grant permission for user on given repository, or update existing one
798 if found
799 if found
799
800
800 :param repo: Instance of Repository, repository_id, or repository name
801 :param repo: Instance of Repository, repository_id, or repository name
801 :param user: Instance of User, user_id or username
802 :param user: Instance of User, user_id or username
802 :param perm: Instance of Permission, or permission_name
803 :param perm: Instance of Permission, or permission_name
803 """
804 """
804 user = self._get_user(user)
805 user = self._get_user(user)
805 repo = self._get_repo(repo)
806 repo = self._get_repo(repo)
806 permission = self._get_perm(perm)
807 permission = self._get_perm(perm)
807
808
808 # check if we have that permission already
809 # check if we have that permission already
809 obj = self.sa.query(UserRepoToPerm) \
810 obj = self.sa.query(UserRepoToPerm) \
810 .filter(UserRepoToPerm.user == user) \
811 .filter(UserRepoToPerm.user == user) \
811 .filter(UserRepoToPerm.repository == repo) \
812 .filter(UserRepoToPerm.repository == repo) \
812 .scalar()
813 .scalar()
813 if obj is None:
814 if obj is None:
814 # create new !
815 # create new !
815 obj = UserRepoToPerm()
816 obj = UserRepoToPerm()
816 obj.repository = repo
817 obj.repository = repo
817 obj.user = user
818 obj.user = user
818 obj.permission = permission
819 obj.permission = permission
819 self.sa.add(obj)
820 self.sa.add(obj)
820 log.debug('Granted perm %s to %s on %s', perm, user, repo)
821 log.debug('Granted perm %s to %s on %s', perm, user, repo)
821 action_logger_generic(
822 action_logger_generic(
822 'granted permission: {} to user: {} on repo: {}'.format(
823 'granted permission: {} to user: {} on repo: {}'.format(
823 perm, user, repo), namespace='security.repo')
824 perm, user, repo), namespace='security.repo')
824 return obj
825 return obj
825
826
826 def revoke_user_permission(self, repo, user):
827 def revoke_user_permission(self, repo, user):
827 """
828 """
828 Revoke permission for user on given repository
829 Revoke permission for user on given repository
829
830
830 :param repo: Instance of Repository, repository_id, or repository name
831 :param repo: Instance of Repository, repository_id, or repository name
831 :param user: Instance of User, user_id or username
832 :param user: Instance of User, user_id or username
832 """
833 """
833
834
834 user = self._get_user(user)
835 user = self._get_user(user)
835 repo = self._get_repo(repo)
836 repo = self._get_repo(repo)
836
837
837 obj = self.sa.query(UserRepoToPerm) \
838 obj = self.sa.query(UserRepoToPerm) \
838 .filter(UserRepoToPerm.repository == repo) \
839 .filter(UserRepoToPerm.repository == repo) \
839 .filter(UserRepoToPerm.user == user) \
840 .filter(UserRepoToPerm.user == user) \
840 .scalar()
841 .scalar()
841 if obj:
842 if obj:
842 self.sa.delete(obj)
843 self.sa.delete(obj)
843 log.debug('Revoked perm on %s on %s', repo, user)
844 log.debug('Revoked perm on %s on %s', repo, user)
844 action_logger_generic(
845 action_logger_generic(
845 'revoked permission from user: {} on repo: {}'.format(
846 'revoked permission from user: {} on repo: {}'.format(
846 user, repo), namespace='security.repo')
847 user, repo), namespace='security.repo')
847
848
848 def grant_user_group_permission(self, repo, group_name, perm):
849 def grant_user_group_permission(self, repo, group_name, perm):
849 """
850 """
850 Grant permission for user group on given repository, or update
851 Grant permission for user group on given repository, or update
851 existing one if found
852 existing one if found
852
853
853 :param repo: Instance of Repository, repository_id, or repository name
854 :param repo: Instance of Repository, repository_id, or repository name
854 :param group_name: Instance of UserGroup, users_group_id,
855 :param group_name: Instance of UserGroup, users_group_id,
855 or user group name
856 or user group name
856 :param perm: Instance of Permission, or permission_name
857 :param perm: Instance of Permission, or permission_name
857 """
858 """
858 repo = self._get_repo(repo)
859 repo = self._get_repo(repo)
859 group_name = self._get_user_group(group_name)
860 group_name = self._get_user_group(group_name)
860 permission = self._get_perm(perm)
861 permission = self._get_perm(perm)
861
862
862 # check if we have that permission already
863 # check if we have that permission already
863 obj = self.sa.query(UserGroupRepoToPerm) \
864 obj = self.sa.query(UserGroupRepoToPerm) \
864 .filter(UserGroupRepoToPerm.users_group == group_name) \
865 .filter(UserGroupRepoToPerm.users_group == group_name) \
865 .filter(UserGroupRepoToPerm.repository == repo) \
866 .filter(UserGroupRepoToPerm.repository == repo) \
866 .scalar()
867 .scalar()
867
868
868 if obj is None:
869 if obj is None:
869 # create new
870 # create new
870 obj = UserGroupRepoToPerm()
871 obj = UserGroupRepoToPerm()
871
872
872 obj.repository = repo
873 obj.repository = repo
873 obj.users_group = group_name
874 obj.users_group = group_name
874 obj.permission = permission
875 obj.permission = permission
875 self.sa.add(obj)
876 self.sa.add(obj)
876 log.debug('Granted perm %s to %s on %s', perm, group_name, repo)
877 log.debug('Granted perm %s to %s on %s', perm, group_name, repo)
877 action_logger_generic(
878 action_logger_generic(
878 'granted permission: {} to usergroup: {} on repo: {}'.format(
879 'granted permission: {} to usergroup: {} on repo: {}'.format(
879 perm, group_name, repo), namespace='security.repo')
880 perm, group_name, repo), namespace='security.repo')
880
881
881 return obj
882 return obj
882
883
883 def revoke_user_group_permission(self, repo, group_name):
884 def revoke_user_group_permission(self, repo, group_name):
884 """
885 """
885 Revoke permission for user group on given repository
886 Revoke permission for user group on given repository
886
887
887 :param repo: Instance of Repository, repository_id, or repository name
888 :param repo: Instance of Repository, repository_id, or repository name
888 :param group_name: Instance of UserGroup, users_group_id,
889 :param group_name: Instance of UserGroup, users_group_id,
889 or user group name
890 or user group name
890 """
891 """
891 repo = self._get_repo(repo)
892 repo = self._get_repo(repo)
892 group_name = self._get_user_group(group_name)
893 group_name = self._get_user_group(group_name)
893
894
894 obj = self.sa.query(UserGroupRepoToPerm) \
895 obj = self.sa.query(UserGroupRepoToPerm) \
895 .filter(UserGroupRepoToPerm.repository == repo) \
896 .filter(UserGroupRepoToPerm.repository == repo) \
896 .filter(UserGroupRepoToPerm.users_group == group_name) \
897 .filter(UserGroupRepoToPerm.users_group == group_name) \
897 .scalar()
898 .scalar()
898 if obj:
899 if obj:
899 self.sa.delete(obj)
900 self.sa.delete(obj)
900 log.debug('Revoked perm to %s on %s', repo, group_name)
901 log.debug('Revoked perm to %s on %s', repo, group_name)
901 action_logger_generic(
902 action_logger_generic(
902 'revoked permission from usergroup: {} on repo: {}'.format(
903 'revoked permission from usergroup: {} on repo: {}'.format(
903 group_name, repo), namespace='security.repo')
904 group_name, repo), namespace='security.repo')
904
905
905 def delete_stats(self, repo_name):
906 def delete_stats(self, repo_name):
906 """
907 """
907 removes stats for given repo
908 removes stats for given repo
908
909
909 :param repo_name:
910 :param repo_name:
910 """
911 """
911 repo = self._get_repo(repo_name)
912 repo = self._get_repo(repo_name)
912 try:
913 try:
913 obj = self.sa.query(Statistics) \
914 obj = self.sa.query(Statistics) \
914 .filter(Statistics.repository == repo).scalar()
915 .filter(Statistics.repository == repo).scalar()
915 if obj:
916 if obj:
916 self.sa.delete(obj)
917 self.sa.delete(obj)
917 except Exception:
918 except Exception:
918 log.error(traceback.format_exc())
919 log.error(traceback.format_exc())
919 raise
920 raise
920
921
921 def add_repo_field(self, repo_name, field_key, field_label, field_value='',
922 def add_repo_field(self, repo_name, field_key, field_label, field_value='',
922 field_type='str', field_desc=''):
923 field_type='str', field_desc=''):
923
924
924 repo = self._get_repo(repo_name)
925 repo = self._get_repo(repo_name)
925
926
926 new_field = RepositoryField()
927 new_field = RepositoryField()
927 new_field.repository = repo
928 new_field.repository = repo
928 new_field.field_key = field_key
929 new_field.field_key = field_key
929 new_field.field_type = field_type # python type
930 new_field.field_type = field_type # python type
930 new_field.field_value = field_value
931 new_field.field_value = field_value
931 new_field.field_desc = field_desc
932 new_field.field_desc = field_desc
932 new_field.field_label = field_label
933 new_field.field_label = field_label
933 self.sa.add(new_field)
934 self.sa.add(new_field)
934 return new_field
935 return new_field
935
936
936 def delete_repo_field(self, repo_name, field_key):
937 def delete_repo_field(self, repo_name, field_key):
937 repo = self._get_repo(repo_name)
938 repo = self._get_repo(repo_name)
938 field = RepositoryField.get_by_key_name(field_key, repo)
939 field = RepositoryField.get_by_key_name(field_key, repo)
939 if field:
940 if field:
940 self.sa.delete(field)
941 self.sa.delete(field)
941
942
942 def set_landing_rev(self, repo, landing_rev_name):
943 def set_landing_rev(self, repo, landing_rev_name):
943 if landing_rev_name.startswith('branch:'):
944 if landing_rev_name.startswith('branch:'):
944 landing_rev_name = landing_rev_name.split('branch:')[-1]
945 landing_rev_name = landing_rev_name.split('branch:')[-1]
945 scm_instance = repo.scm_instance()
946 scm_instance = repo.scm_instance()
946 if scm_instance:
947 if scm_instance:
947 return scm_instance._remote.set_head_ref(landing_rev_name)
948 return scm_instance._remote.set_head_ref(landing_rev_name)
948
949
949 def _create_filesystem_repo(self, repo_name, repo_type, repo_group,
950 def _create_filesystem_repo(self, repo_name, repo_type, repo_group,
950 clone_uri=None, repo_store_location=None,
951 clone_uri=None, repo_store_location=None,
951 use_global_config=False, install_hooks=True):
952 use_global_config=False, install_hooks=True):
952 """
953 """
953 makes repository on filesystem. It's group aware means it'll create
954 makes repository on filesystem. It's group aware means it'll create
954 a repository within a group, and alter the paths accordingly of
955 a repository within a group, and alter the paths accordingly of
955 group location
956 group location
956
957
957 :param repo_name:
958 :param repo_name:
958 :param alias:
959 :param alias:
959 :param parent:
960 :param parent:
960 :param clone_uri:
961 :param clone_uri:
961 :param repo_store_location:
962 :param repo_store_location:
962 """
963 """
963 from rhodecode.lib.utils import is_valid_repo, is_valid_repo_group
964 from rhodecode.lib.utils import is_valid_repo, is_valid_repo_group
964 from rhodecode.model.scm import ScmModel
965 from rhodecode.model.scm import ScmModel
965
966
966 if Repository.NAME_SEP in repo_name:
967 if Repository.NAME_SEP in repo_name:
967 raise ValueError(
968 raise ValueError(
968 'repo_name must not contain groups got `%s`' % repo_name)
969 'repo_name must not contain groups got `%s`' % repo_name)
969
970
970 if isinstance(repo_group, RepoGroup):
971 if isinstance(repo_group, RepoGroup):
971 new_parent_path = os.sep.join(repo_group.full_path_splitted)
972 new_parent_path = os.sep.join(repo_group.full_path_splitted)
972 else:
973 else:
973 new_parent_path = repo_group or ''
974 new_parent_path = repo_group or ''
974
975
975 if repo_store_location:
976 if repo_store_location:
976 _paths = [repo_store_location]
977 _paths = [repo_store_location]
977 else:
978 else:
978 _paths = [self.repos_path, new_parent_path, repo_name]
979 _paths = [self.repos_path, new_parent_path, repo_name]
979 # we need to make it str for mercurial
980 # we need to make it str for mercurial
980 repo_path = os.path.join(*map(lambda x: safe_str(x), _paths))
981 repo_path = os.path.join(*map(lambda x: safe_str(x), _paths))
981
982
982 # check if this path is not a repository
983 # check if this path is not a repository
983 if is_valid_repo(repo_path, self.repos_path):
984 if is_valid_repo(repo_path, self.repos_path):
984 raise Exception(f'This path {repo_path} is a valid repository')
985 raise Exception(f'This path {repo_path} is a valid repository')
985
986
986 # check if this path is a group
987 # check if this path is a group
987 if is_valid_repo_group(repo_path, self.repos_path):
988 if is_valid_repo_group(repo_path, self.repos_path):
988 raise Exception(f'This path {repo_path} is a valid group')
989 raise Exception(f'This path {repo_path} is a valid group')
989
990
990 log.info('creating repo %s in %s from url: `%s`',
991 log.info('creating repo %s in %s from url: `%s`',
991 repo_name, safe_str(repo_path),
992 repo_name, safe_str(repo_path),
992 obfuscate_url_pw(clone_uri))
993 obfuscate_url_pw(clone_uri))
993
994
994 backend = get_backend(repo_type)
995 backend = get_backend(repo_type)
995
996
996 config_repo = None if use_global_config else repo_name
997 config_repo = None if use_global_config else repo_name
997 if config_repo and new_parent_path:
998 if config_repo and new_parent_path:
998 config_repo = Repository.NAME_SEP.join(
999 config_repo = Repository.NAME_SEP.join(
999 (new_parent_path, config_repo))
1000 (new_parent_path, config_repo))
1000 config = make_db_config(clear_session=False, repo=config_repo)
1001 config = make_db_config(clear_session=False, repo=config_repo)
1001 config.set('extensions', 'largefiles', '')
1002 config.set('extensions', 'largefiles', '')
1002
1003
1003 # patch and reset hooks section of UI config to not run any
1004 # patch and reset hooks section of UI config to not run any
1004 # hooks on creating remote repo
1005 # hooks on creating remote repo
1005 config.clear_section('hooks')
1006 config.clear_section('hooks')
1006
1007
1007 # TODO: johbo: Unify this, hardcoded "bare=True" does not look nice
1008 # TODO: johbo: Unify this, hardcoded "bare=True" does not look nice
1008 if repo_type == 'git':
1009 if repo_type == 'git':
1009 repo = backend(
1010 repo = backend(
1010 repo_path, config=config, create=True, src_url=clone_uri, bare=True,
1011 repo_path, config=config, create=True, src_url=clone_uri, bare=True,
1011 with_wire={"cache": False})
1012 with_wire={"cache": False})
1012 else:
1013 else:
1013 repo = backend(
1014 repo = backend(
1014 repo_path, config=config, create=True, src_url=clone_uri,
1015 repo_path, config=config, create=True, src_url=clone_uri,
1015 with_wire={"cache": False})
1016 with_wire={"cache": False})
1016
1017
1017 if install_hooks:
1018 if install_hooks:
1018 repo.install_hooks()
1019 repo.install_hooks()
1019
1020
1020 log.debug('Created repo %s with %s backend',
1021 log.debug('Created repo %s with %s backend',
1021 safe_str(repo_name), safe_str(repo_type))
1022 safe_str(repo_name), safe_str(repo_type))
1022 return repo
1023 return repo
1023
1024
1024 def _rename_filesystem_repo(self, old, new):
1025 def _rename_filesystem_repo(self, old, new):
1025 """
1026 """
1026 renames repository on filesystem
1027 renames repository on filesystem
1027
1028
1028 :param old: old name
1029 :param old: old name
1029 :param new: new name
1030 :param new: new name
1030 """
1031 """
1031 log.info('renaming repo from %s to %s', old, new)
1032 log.info('renaming repo from %s to %s', old, new)
1032
1033
1033 old_path = os.path.join(self.repos_path, old)
1034 old_path = os.path.join(self.repos_path, old)
1034 new_path = os.path.join(self.repos_path, new)
1035 new_path = os.path.join(self.repos_path, new)
1035 if os.path.isdir(new_path):
1036 if os.path.isdir(new_path):
1036 raise Exception(
1037 raise Exception(
1037 'Was trying to rename to already existing dir %s' % new_path
1038 'Was trying to rename to already existing dir %s' % new_path
1038 )
1039 )
1039 shutil.move(old_path, new_path)
1040 shutil.move(old_path, new_path)
1040
1041
1041 def _delete_filesystem_repo(self, repo):
1042 def _delete_filesystem_repo(self, repo):
1042 """
1043 """
1043 removes repo from filesystem, the removal is actually made by
1044 removes repo from filesystem, the removal is actually made by
1044 added rm__ prefix into dir, and rename internal .hg/.git dirs so this
1045 added rm__ prefix into dir, and rename internal .hg/.git dirs so this
1045 repository is no longer valid for rhodecode, can be undeleted later on
1046 repository is no longer valid for rhodecode, can be undeleted later on
1046 by reverting the renames on this repository
1047 by reverting the renames on this repository
1047
1048
1048 :param repo: repo object
1049 :param repo: repo object
1049 """
1050 """
1050 rm_path = os.path.join(self.repos_path, repo.repo_name)
1051 rm_path = os.path.join(self.repos_path, repo.repo_name)
1051 repo_group = repo.group
1052 repo_group = repo.group
1052 log.info("delete_filesystem_repo: removing repository %s", rm_path)
1053 log.info("delete_filesystem_repo: removing repository %s", rm_path)
1053 # disable hg/git internal that it doesn't get detected as repo
1054 # disable hg/git internal that it doesn't get detected as repo
1054 alias = repo.repo_type
1055 alias = repo.repo_type
1055
1056
1056 config = make_db_config(clear_session=False)
1057 config = make_db_config(clear_session=False)
1057 config.set('extensions', 'largefiles', '')
1058 config.set('extensions', 'largefiles', '')
1058 bare = getattr(repo.scm_instance(config=config), 'bare', False)
1059 bare = getattr(repo.scm_instance(config=config), 'bare', False)
1059
1060
1060 # skip this for bare git repos
1061 # skip this for bare git repos
1061 if not bare:
1062 if not bare:
1062 # disable VCS repo
1063 # disable VCS repo
1063 vcs_path = os.path.join(rm_path, '.%s' % alias)
1064 vcs_path = os.path.join(rm_path, '.%s' % alias)
1064 if os.path.exists(vcs_path):
1065 if os.path.exists(vcs_path):
1065 shutil.move(vcs_path, os.path.join(rm_path, 'rm__.%s' % alias))
1066 shutil.move(vcs_path, os.path.join(rm_path, 'rm__.%s' % alias))
1066
1067
1067 _now = datetime.datetime.now()
1068 _now = datetime.datetime.now()
1068 _ms = str(_now.microsecond).rjust(6, '0')
1069 _ms = str(_now.microsecond).rjust(6, '0')
1069 _d = 'rm__{}__{}'.format(_now.strftime('%Y%m%d_%H%M%S_' + _ms),
1070 _d = 'rm__{}__{}'.format(_now.strftime('%Y%m%d_%H%M%S_' + _ms),
1070 repo.just_name)
1071 repo.just_name)
1071 if repo_group:
1072 if repo_group:
1072 # if repository is in group, prefix the removal path with the group
1073 # if repository is in group, prefix the removal path with the group
1073 args = repo_group.full_path_splitted + [_d]
1074 args = repo_group.full_path_splitted + [_d]
1074 _d = os.path.join(*args)
1075 _d = os.path.join(*args)
1075
1076
1076 if os.path.isdir(rm_path):
1077 if os.path.isdir(rm_path):
1077 shutil.move(rm_path, os.path.join(self.repos_path, _d))
1078 shutil.move(rm_path, os.path.join(self.repos_path, _d))
1078
1079
1079 # finally cleanup diff-cache if it exists
1080 # finally cleanup diff-cache if it exists
1080 cached_diffs_dir = repo.cached_diffs_dir
1081 cached_diffs_dir = repo.cached_diffs_dir
1081 if os.path.isdir(cached_diffs_dir):
1082 if os.path.isdir(cached_diffs_dir):
1082 shutil.rmtree(cached_diffs_dir)
1083 shutil.rmtree(cached_diffs_dir)
1083
1084
1084
1085
1085 class ReadmeFinder:
1086 class ReadmeFinder:
1086 """
1087 """
1087 Utility which knows how to find a readme for a specific commit.
1088 Utility which knows how to find a readme for a specific commit.
1088
1089
1089 The main idea is that this is a configurable algorithm. When creating an
1090 The main idea is that this is a configurable algorithm. When creating an
1090 instance you can define parameters, currently only the `default_renderer`.
1091 instance you can define parameters, currently only the `default_renderer`.
1091 Based on this configuration the method :meth:`search` behaves slightly
1092 Based on this configuration the method :meth:`search` behaves slightly
1092 different.
1093 different.
1093 """
1094 """
1094
1095
1095 readme_re = re.compile(r'^readme(\.[^\.]+)?$', re.IGNORECASE)
1096 readme_re = re.compile(r'^readme(\.[^\.]+)?$', re.IGNORECASE)
1096 path_re = re.compile(r'^docs?', re.IGNORECASE)
1097 path_re = re.compile(r'^docs?', re.IGNORECASE)
1097
1098
1098 default_priorities = {
1099 default_priorities = {
1099 None: 0,
1100 None: 0,
1100 '.rst': 1,
1101 '.rst': 1,
1101 '.md': 1,
1102 '.md': 1,
1102 '.rest': 2,
1103 '.rest': 2,
1103 '.mkdn': 2,
1104 '.mkdn': 2,
1104 '.text': 2,
1105 '.text': 2,
1105 '.txt': 3,
1106 '.txt': 3,
1106 '.mdown': 3,
1107 '.mdown': 3,
1107 '.markdown': 4,
1108 '.markdown': 4,
1108 }
1109 }
1109
1110
1110 path_priority = {
1111 path_priority = {
1111 'doc': 0,
1112 'doc': 0,
1112 'docs': 1,
1113 'docs': 1,
1113 }
1114 }
1114
1115
1115 FALLBACK_PRIORITY = 99
1116 FALLBACK_PRIORITY = 99
1116
1117
1117 RENDERER_TO_EXTENSION = {
1118 RENDERER_TO_EXTENSION = {
1118 'rst': ['.rst', '.rest'],
1119 'rst': ['.rst', '.rest'],
1119 'markdown': ['.md', 'mkdn', '.mdown', '.markdown'],
1120 'markdown': ['.md', 'mkdn', '.mdown', '.markdown'],
1120 }
1121 }
1121
1122
1122 def __init__(self, default_renderer=None):
1123 def __init__(self, default_renderer=None):
1123 self._default_renderer = default_renderer
1124 self._default_renderer = default_renderer
1124 self._renderer_extensions = self.RENDERER_TO_EXTENSION.get(
1125 self._renderer_extensions = self.RENDERER_TO_EXTENSION.get(
1125 default_renderer, [])
1126 default_renderer, [])
1126
1127
1127 def search(self, commit, path='/'):
1128 def search(self, commit, path='/'):
1128 """
1129 """
1129 Find a readme in the given `commit`.
1130 Find a readme in the given `commit`.
1130 """
1131 """
1132 # firstly, check the PATH type if it is actually a DIR
1133 if commit.get_node(path).kind != NodeKind.DIR:
1134 return None
1135
1131 nodes = commit.get_nodes(path)
1136 nodes = commit.get_nodes(path)
1132 matches = self._match_readmes(nodes)
1137 matches = self._match_readmes(nodes)
1133 matches = self._sort_according_to_priority(matches)
1138 matches = self._sort_according_to_priority(matches)
1134 if matches:
1139 if matches:
1135 return matches[0].node
1140 return matches[0].node
1136
1141
1137 paths = self._match_paths(nodes)
1142 paths = self._match_paths(nodes)
1138 paths = self._sort_paths_according_to_priority(paths)
1143 paths = self._sort_paths_according_to_priority(paths)
1139 for path in paths:
1144 for path in paths:
1140 match = self.search(commit, path=path)
1145 match = self.search(commit, path=path)
1141 if match:
1146 if match:
1142 return match
1147 return match
1143
1148
1144 return None
1149 return None
1145
1150
1146 def _match_readmes(self, nodes):
1151 def _match_readmes(self, nodes):
1147 for node in nodes:
1152 for node in nodes:
1148 if not node.is_file():
1153 if not node.is_file():
1149 continue
1154 continue
1150 path = node.path.rsplit('/', 1)[-1]
1155 path = node.path.rsplit('/', 1)[-1]
1151 match = self.readme_re.match(path)
1156 match = self.readme_re.match(path)
1152 if match:
1157 if match:
1153 extension = match.group(1)
1158 extension = match.group(1)
1154 yield ReadmeMatch(node, match, self._priority(extension))
1159 yield ReadmeMatch(node, match, self._priority(extension))
1155
1160
1156 def _match_paths(self, nodes):
1161 def _match_paths(self, nodes):
1157 for node in nodes:
1162 for node in nodes:
1158 if not node.is_dir():
1163 if not node.is_dir():
1159 continue
1164 continue
1160 match = self.path_re.match(node.path)
1165 match = self.path_re.match(node.path)
1161 if match:
1166 if match:
1162 yield node.path
1167 yield node.path
1163
1168
1164 def _priority(self, extension):
1169 def _priority(self, extension):
1165 renderer_priority = (
1170 renderer_priority = (
1166 0 if extension in self._renderer_extensions else 1)
1171 0 if extension in self._renderer_extensions else 1)
1167 extension_priority = self.default_priorities.get(
1172 extension_priority = self.default_priorities.get(
1168 extension, self.FALLBACK_PRIORITY)
1173 extension, self.FALLBACK_PRIORITY)
1169 return (renderer_priority, extension_priority)
1174 return (renderer_priority, extension_priority)
1170
1175
1171 def _sort_according_to_priority(self, matches):
1176 def _sort_according_to_priority(self, matches):
1172
1177
1173 def priority_and_path(match):
1178 def priority_and_path(match):
1174 return (match.priority, match.path)
1179 return (match.priority, match.path)
1175
1180
1176 return sorted(matches, key=priority_and_path)
1181 return sorted(matches, key=priority_and_path)
1177
1182
1178 def _sort_paths_according_to_priority(self, paths):
1183 def _sort_paths_according_to_priority(self, paths):
1179
1184
1180 def priority_and_path(path):
1185 def priority_and_path(path):
1181 return (self.path_priority.get(path, self.FALLBACK_PRIORITY), path)
1186 return (self.path_priority.get(path, self.FALLBACK_PRIORITY), path)
1182
1187
1183 return sorted(paths, key=priority_and_path)
1188 return sorted(paths, key=priority_and_path)
1184
1189
1185
1190
1186 class ReadmeMatch:
1191 class ReadmeMatch:
1187
1192
1188 def __init__(self, node, match, priority):
1193 def __init__(self, node, match, priority):
1189 self.node = node
1194 self.node = node
1190 self._match = match
1195 self._match = match
1191 self.priority = priority
1196 self.priority = priority
1192
1197
1193 @property
1198 @property
1194 def path(self):
1199 def path(self):
1195 return self.node.path
1200 return self.node.path
1196
1201
1197 def __repr__(self):
1202 def __repr__(self):
1198 return f'<ReadmeMatch {self.path} priority={self.priority}'
1203 return f'<ReadmeMatch {self.path} priority={self.priority}'
@@ -1,278 +1,281 b''
1 <%text>
1 <%text>
2 <div style="display: none">
2 <div style="display: none">
3
3
4 <script>
4 <script>
5 var CG = new ColorGenerator();
5 var CG = new ColorGenerator();
6 </script>
6 </script>
7
7
8 <script id="ejs_gravatarWithUser" type="text/template" class="ejsTemplate">
8 <script id="ejs_gravatarWithUser" type="text/template" class="ejsTemplate">
9
9
10 <%
10 <%
11 if (size > 16) {
11 if (size > 16) {
12 var gravatar_class = 'gravatar gravatar-large';
12 var gravatar_class = 'gravatar gravatar-large';
13 } else {
13 } else {
14 var gravatar_class = 'gravatar';
14 var gravatar_class = 'gravatar';
15 }
15 }
16
16
17 if (tooltip) {
17 if (tooltip) {
18 var gravatar_class = gravatar_class + ' tooltip-hovercard';
18 var gravatar_class = gravatar_class + ' tooltip-hovercard';
19 }
19 }
20
20
21 var data_hovercard_alt = username;
21 var data_hovercard_alt = username;
22
22
23 %>
23 %>
24
24
25 <%
25 <%
26 if (show_disabled) {
26 if (show_disabled) {
27 var user_cls = 'user user-disabled';
27 var user_cls = 'user user-disabled';
28 } else {
28 } else {
29 var user_cls = 'user';
29 var user_cls = 'user';
30 }
30 }
31 var data_hovercard_url = pyroutes.url('hovercard_user', {"user_id": user_id})
31 var data_hovercard_url = pyroutes.url('hovercard_user', {"user_id": user_id})
32 %>
32 %>
33
33
34 <div class="rc-user">
34 <div class="rc-user">
35 <img class="<%= gravatar_class %>" height="<%= size %>" width="<%= size %>" data-hovercard-url="<%= data_hovercard_url %>" data-hovercard-alt="<%= data_hovercard_alt %>" src="<%- gravatar_url -%>">
35 <img class="<%= gravatar_class %>" height="<%= size %>" width="<%= size %>" data-hovercard-url="<%= data_hovercard_url %>" data-hovercard-alt="<%= data_hovercard_alt %>" src="<%- gravatar_url -%>">
36 <span class="<%= user_cls %>"> <%- user_link -%> </span>
36 <span class="<%= user_cls %>"> <%- user_link -%> </span>
37 </div>
37 </div>
38
38
39 </script>
39 </script>
40
40
41 <script id="ejs_reviewMemberEntry" type="text/template" class="ejsTemplate">
41 <script id="ejs_reviewMemberEntry" type="text/template" class="ejsTemplate">
42 <%
42 <%
43 if (create) {
43 if (create) {
44 var edit_visibility = 'visible';
44 var edit_visibility = 'visible';
45 } else {
45 } else {
46 var edit_visibility = 'hidden';
46 var edit_visibility = 'hidden';
47 }
47 }
48
48
49 if (member.user_group && member.user_group.vote_rule) {
49 if (member.user_group && member.user_group.vote_rule) {
50 var reviewGroup = '<i class="icon-user-group"></i>';
50 var reviewGroup = '<i class="icon-user-group"></i>';
51 var reviewGroupColor = CG.asRGB(CG.getColor(member.user_group.vote_rule));
51 var reviewGroupColor = CG.asRGB(CG.getColor(member.user_group.vote_rule));
52 } else {
52 } else {
53 var reviewGroup = null;
53 var reviewGroup = null;
54 var reviewGroupColor = 'transparent';
54 var reviewGroupColor = 'transparent';
55 }
55 }
56 var rule_show = rule_show || false;
56 var rule_show = rule_show || false;
57
57
58 if (rule_show) {
58 if (rule_show) {
59 var rule_visibility = 'table-cell';
59 var rule_visibility = 'table-cell';
60 } else {
60 } else {
61 var rule_visibility = 'none';
61 var rule_visibility = 'none';
62 }
62 }
63
63
64 %>
64 %>
65
65
66 <tr id="reviewer_<%= member.user_id %>" class="reviewer_entry" tooltip="Review Group" data-reviewer-user-id="<%= member.user_id %>">
66 <tr id="reviewer_<%= member.user_id %>" class="reviewer_entry" tooltip="Review Group" data-reviewer-user-id="<%= member.user_id %>">
67
67
68 <% if (create) { %>
68 <% if (create) { %>
69 <td style="width: 1px"></td>
69 <td style="width: 1px"></td>
70 <% } else { %>
70 <% } else { %>
71 <td style="width: 20px">
71 <td style="width: 20px">
72 <div class="tooltip presence-state" style="display: none; position: absolute; left: 2px" title="This users is currently at this page">
72 <div class="tooltip presence-state" style="display: none; position: absolute; left: 2px" title="This users is currently at this page">
73 <i class="icon-eye" style="color: #0ac878"></i>
73 <i class="icon-eye" style="color: #0ac878"></i>
74 </div>
74 </div>
75 <% if (role === 'reviewer') { %>
75 <% if (role === 'reviewer') { %>
76 <div class="reviewer_status tooltip" title="<%= review_status_label %>">
76 <div class="reviewer_status tooltip" title="<%= review_status_label %>">
77 <i class="icon-circle review-status-<%= review_status %>"></i>
77 <i class="icon-circle review-status-<%= review_status %>"></i>
78 </div>
78 </div>
79 <% } else if (role === 'observer') { %>
79 <% } else if (role === 'observer') { %>
80 <div class="tooltip" title="Observer without voting right.">
80 <div class="tooltip" title="Observer without voting right.">
81 <i class="icon-circle-thin"></i>
81 <i class="icon-circle-thin"></i>
82 </div>
82 </div>
83 <% } %>
83 <% } %>
84 </td>
84 </td>
85 <% } %>
85 <% } %>
86
86
87
87
88 <% if (mandatory) { %>
88 <% if (mandatory) { %>
89 <td style="text-align: right;width: 10px;">
89 <td style="text-align: right;width: 10px;">
90 <div class="reviewer_member_mandatory tooltip" title="Mandatory reviewer">
90 <div class="reviewer_member_mandatory tooltip" title="Mandatory reviewer">
91 <i class="icon-lock"></i>
91 <i class="icon-lock"></i>
92 </div>
92 </div>
93 </td>
93 </td>
94
94
95 <% } else { %>
95 <% } else { %>
96 <td style="text-align: right;width: 10px;">
96 <td style="text-align: right;width: 10px;">
97 <% if (allowed_to_update) { %>
97 <% if (allowed_to_update) { %>
98 <div class="<%=role %>_member_remove" onclick="reviewersController.removeMember(<%= member.user_id %>, true)" style="visibility: <%= edit_visibility %>;">
98 <div class="<%=role %>_member_remove" onclick="reviewersController.removeMember(<%= member.user_id %>, true)" style="visibility: <%= edit_visibility %>;">
99 <i class="icon-remove" style="color: #e85e4d;"></i>
99 <i class="icon-remove" style="color: #e85e4d;"></i>
100 </div>
100 </div>
101 <% } %>
101 <% } %>
102 </td>
102 </td>
103 <% } %>
103 <% } %>
104
104
105 <td>
105 <td>
106 <div id="reviewer_<%= member.user_id %>_name" class="reviewer_name">
106 <div id="reviewer_<%= member.user_id %>_name" class="reviewer_name">
107 <%-
107 <%-
108 renderTemplate('gravatarWithUser', {
108 renderTemplate('gravatarWithUser', {
109 'size': 16,
109 'size': 16,
110 'show_disabled': false,
110 'show_disabled': false,
111 'tooltip': true,
111 'tooltip': true,
112 'username': member.username,
112 'username': member.username,
113 'user_id': member.user_id,
113 'user_id': member.user_id,
114 'user_link': member.user_link,
114 'user_link': member.user_link,
115 'gravatar_url': member.gravatar_link
115 'gravatar_url': member.gravatar_link
116 })
116 })
117 %>
117 %>
118 </div>
118 </div>
119 <% if (reviewGroup !== null) { %>
119 <% if (reviewGroup !== null) { %>
120 <span class="tooltip" title="Member of review group from rule: `<%= member.user_group.name %>`" style="color: <%= reviewGroupColor %>">
120 <span class="tooltip" title="Member of review group from rule: `<%= member.user_group.name %>`" style="color: <%= reviewGroupColor %>">
121 <%- reviewGroup %>
121 <%- reviewGroup %>
122 </span>
122 </span>
123 <% } %>
123 <% } %>
124 </td>
124 </td>
125
125
126 </tr>
126 </tr>
127
127
128 <tr id="reviewer_<%= member.user_id %>_rules">
128 <tr id="reviewer_<%= member.user_id %>_rules">
129 <td colspan="4" style="display: <%= rule_visibility %>" class="pr-user-rule-container">
129 <td colspan="4" style="display: <%= rule_visibility %>" class="pr-user-rule-container">
130 <input type="hidden" name="__start__" value="reviewer:mapping">
130 <input type="hidden" name="__start__" value="reviewer:mapping">
131
131
132 <%if (member.user_group && member.user_group.vote_rule) { %>
132 <%if (member.user_group && member.user_group.vote_rule) { %>
133 <div class="reviewer_reason">
133 <div class="reviewer_reason">
134
134
135 <%if (member.user_group.vote_rule == -1) {%>
135 <%if (member.user_group.vote_rule == -1) {%>
136 - group votes required: ALL
136 - group votes required: ALL
137 <%} else {%>
137 <%} else {%>
138 - group votes required: <%= member.user_group.vote_rule %>
138 - group votes required: <%= member.user_group.vote_rule %>
139 <%}%>
139 <%}%>
140 </div>
140 </div>
141 <%} %>
141 <%} %>
142
142
143 <input type="hidden" name="__start__" value="reasons:sequence">
143 <input type="hidden" name="__start__" value="reasons:sequence">
144 <% for (var i = 0; i < reasons.length; i++) { %>
144 <% for (var i = 0; i < reasons.length; i++) { %>
145 <% var reason = reasons[i] %>
145 <% var reason = reasons[i] %>
146 <div class="reviewer_reason">- <%= reason %></div>
146 <div class="reviewer_reason">- <%= reason %></div>
147 <input type="hidden" name="reason" value="<%= reason %>">
147 <input type="hidden" name="reason" value="<%= reason %>">
148 <% } %>
148 <% } %>
149 <input type="hidden" name="__end__" value="reasons:sequence">
149 <input type="hidden" name="__end__" value="reasons:sequence">
150
150
151 <input type="hidden" name="__start__" value="rules:sequence">
151 <input type="hidden" name="__start__" value="rules:sequence">
152 <% for (var i = 0; i < member.rules.length; i++) { %>
152 <% for (var i = 0; i < member.rules.length; i++) { %>
153 <% var rule = member.rules[i] %>
153 <% var rule = member.rules[i] %>
154 <input type="hidden" name="rule_id" value="<%= rule %>">
154 <input type="hidden" name="rule_id" value="<%= rule %>">
155 <% } %>
155 <% } %>
156 <input type="hidden" name="__end__" value="rules:sequence">
156 <input type="hidden" name="__end__" value="rules:sequence">
157
157
158 <input id="reviewer_<%= member.user_id %>_input" type="hidden" value="<%= member.user_id %>" name="user_id" />
158 <input id="reviewer_<%= member.user_id %>_input" type="hidden" value="<%= member.user_id %>" name="user_id" />
159 <input type="hidden" name="mandatory" value="<%= mandatory %>"/>
159 <input type="hidden" name="mandatory" value="<%= mandatory %>"/>
160 <input type="hidden" name="role" value="<%= role %>"/>
160 <input type="hidden" name="role" value="<%= role %>"/>
161
161
162 <input type="hidden" name="__end__" value="reviewer:mapping">
162 <input type="hidden" name="__end__" value="reviewer:mapping">
163 </td>
163 </td>
164 </tr>
164 </tr>
165
165
166 </script>
166 </script>
167
167
168 <script id="ejs_commentVersion" type="text/template" class="ejsTemplate">
168 <script id="ejs_commentVersion" type="text/template" class="ejsTemplate">
169
169
170 <%
170 <%
171 if (size > 16) {
171 if (size > 16) {
172 var gravatar_class = 'gravatar gravatar-large';
172 var gravatar_class = 'gravatar gravatar-large';
173 } else {
173 } else {
174 var gravatar_class = 'gravatar';
174 var gravatar_class = 'gravatar';
175 }
175 }
176
176
177 %>
177 %>
178
178
179 <%
179 <%
180 if (show_disabled) {
180 if (show_disabled) {
181 var user_cls = 'user user-disabled';
181 var user_cls = 'user user-disabled';
182 } else {
182 } else {
183 var user_cls = 'user';
183 var user_cls = 'user';
184 }
184 }
185
185
186 %>
186 %>
187
187
188 <div style='line-height: 20px'>
188 <div style='line-height: 20px'>
189 <img style="margin: -3px 0" class="<%= gravatar_class %>" height="<%= size %>" width="<%= size %>" src="<%- gravatar_url -%>">
189 <img style="margin: -3px 0" class="<%= gravatar_class %>" height="<%= size %>" width="<%= size %>" src="<%- gravatar_url -%>">
190 <strong><%- user_name -%></strong>, <code>v<%- version -%></code> edited <%- timeago_component -%>
190 <strong><%- user_name -%></strong>, <code>v<%- version -%></code> edited <%- timeago_component -%>
191 </div>
191 </div>
192
192
193 </script>
193 </script>
194
194
195
195
196 <script id="ejs_sideBarCommentHovercard" type="text/template" class="ejsTemplate">
196 <script id="ejs_sideBarCommentHovercard" type="text/template" class="ejsTemplate">
197
197
198 <div>
198 <div>
199
199
200 <% if (is_todo) { %>
200 <% if (is_todo) { %>
201 <% if (inline) { %>
201 <% if (inline) { %>
202 <strong>Inline</strong> TODO (<code>#<%- comment_id -%></code>) on line: <%= line_no %>
202 <strong>Inline</strong> TODO (<code>#<%- comment_id -%></code>) on line: <%= line_no %>
203 <% if (version_info) { %>
203 <% if (version_info) { %>
204 <%= version_info %>
204 <%= version_info %>
205 <% } %>
205 <% } %>
206 <br/>
206 <br/>
207 File: <code><%- file_name -%></code>
207 File: <code><%- file_name -%></code>
208 <% } else { %>
208 <% } else { %>
209 <% if (review_status) { %>
209 <% if (review_status) { %>
210 <i class="icon-circle review-status-<%= review_status %>"></i>
210 <i class="icon-circle review-status-<%= review_status %>"></i>
211 <% } %>
211 <% } %>
212 <strong>General</strong> TODO (<code>#<%- comment_id -%></code>)
212 <strong>General</strong> TODO (<code>#<%- comment_id -%></code>)
213 <% if (version_info) { %>
213 <% if (version_info) { %>
214 <%= version_info %>
214 <%= version_info %>
215 <% } %>
215 <% } %>
216 <% } %>
216 <% } %>
217 <% } else { %>
217 <% } else { %>
218 <% if (inline) { %>
218 <% if (inline) { %>
219 <strong>Inline</strong> comment (<code>#<%- comment_id -%></code>) on line: <%= line_no %>
219 <strong>Inline</strong> comment (<code>#<%- comment_id -%></code>) on line: <%= line_no %>
220 <% if (version_info) { %>
220 <% if (version_info) { %>
221 <%= version_info %>
221 <%= version_info %>
222 <% } %>
222 <% } %>
223 <br/>
223 <br/>
224 File: <code><%= file_name -%></code>
224 File: <code><%= file_name -%></code>
225 <% } else { %>
225 <% } else { %>
226 <% if (review_status) { %>
226 <% if (review_status) { %>
227 <i class="icon-circle review-status-<%= review_status %>"></i>
227 <i class="icon-circle review-status-<%= review_status %>"></i>
228 <% } %>
228 <% } %>
229 <strong>General</strong> comment (<code>#<%- comment_id -%></code>)
229 <strong>General</strong> comment (<code>#<%- comment_id -%></code>)
230 <% if (version_info) { %>
230 <% if (version_info) { %>
231 <%= version_info %>
231 <%= version_info %>
232 <% } %>
232 <% } %>
233 <% } %>
233 <% } %>
234 <% } %>
234 <% } %>
235 <br/>
235 <br/>
236 Created:
236 Created:
237 <time class="timeago" title="<%= created_on %>" datetime="<%= datetime %>"><%= $.timeago(datetime) %></time>
237 <time class="timeago" title="<%= created_on %>" datetime="<%= datetime %>"><%= $.timeago(datetime) %></time>
238
238
239 <% if (is_todo) { %>
239 <% if (is_todo) { %>
240 <div style="text-align: left; padding-top: 5px">
240 <div style="text-align: left; padding-top: 5px">
241 <a class="btn btn-sm" href="#resolveTodo<%- comment_id -%>" onclick="Rhodecode.comments.resolveTodo(this, '<%- comment_id -%>'); return false">
241 <a class="btn btn-sm" href="#resolveTodo<%- comment_id -%>" onclick="Rhodecode.comments.resolveTodo(this, '<%- comment_id -%>'); return false">
242 <strong>Resolve TODO</strong>
242 <strong>Resolve TODO</strong>
243 </a>
243 </a>
244 </div>
244 </div>
245 <% } %>
245 <% } %>
246
246
247 </div>
247 </div>
248
248
249 </script>
249 </script>
250
250
251 <script id="ejs_commentHelpHovercard" type="text/template" class="ejsTemplate">
251 <script id="ejs_commentHelpHovercard" type="text/template" class="ejsTemplate">
252
252
253 <div>
253 <div>
254 Use <strong>@username</strong> mention syntax to send direct notification to this RhodeCode user.<br/>
254 Use <strong>@username</strong> mention syntax to send direct notification to this RhodeCode user.<br/>
255 Typing / starts autocomplete for certain action, e.g set review status, or comment type. <br/>
255 Typing / starts autocomplete for certain action, e.g set review status, or comment type. <br/>
256 <br/>
256 <br/>
257 Use <strong>Cmd/ctrl+enter</strong> to submit comment, or <strong>Shift+Cmd/ctrl+enter</strong> to submit a draft.<br/>
257 Use <strong>Cmd/ctrl+enter</strong> to submit comment, or <strong>Shift+Cmd/ctrl+enter</strong> to submit a draft.<br/>
258 <br/>
258 <br/>
259 <strong>Draft comments</strong> are private to the author, and trigger no notification to others.<br/>
259 <strong>Draft comments</strong> are private to the author, and trigger no notification to others.<br/>
260 They are permanent until deleted, or converted to regular comments.<br/>
260 They are permanent until deleted, or converted to regular comments.<br/>
261 <br/>
261 <br/>
262 <br/>
262 <br/>
263 </div>
263 </div>
264
264
265 </script>
265 </script>
266
266
267
267 <script id="ejs_submoduleHovercard" type="text/template" class="ejsTemplate">
268 <strong>Submodule Node</strong><br/>
269 <pre><%= submodule_url %></pre>
270 </script>
268
271
269 ##// END OF EJS Templates
272 ##// END OF EJS Templates
270 </div>
273 </div>
271
274
272
275
273 <script>
276 <script>
274 // registers the templates into global cache
277 // registers the templates into global cache
275 registerTemplates();
278 registerTemplates();
276 </script>
279 </script>
277
280
278 </%text>
281 </%text>
@@ -1,98 +1,112 b''
1 <%namespace name="base" file="/base/base.mako"/>
1 <%namespace name="base" file="/base/base.mako"/>
2
2
3 <%doc>
4 Please note the content of this file is cached, so changes here might not be reflected when editing.
5 add ?no-cache=true into the file url to disable caches.
6
7 e.g
8 http://docker-dev:10020/ipython/files/master/IPython/frontend/html/notebook/static?no-cache=1
9
10 </%doc>
3 <%
11 <%
4 at_ref = request.GET.get('at')
12 at_ref = request.GET.get('at')
5 if at_ref:
13 if at_ref:
6 query={'at': at_ref}
14 query={'at': at_ref}
7 default_landing_ref = at_ref or c.rhodecode_db_repo.landing_ref_name
15 default_landing_ref = at_ref or c.rhodecode_db_repo.landing_ref_name
8 else:
16 else:
9 query=None
17 query=None
10 default_landing_ref = c.commit.raw_id
18 default_landing_ref = c.commit.raw_id
11 %>
19 %>
12 <div id="file-tree-wrapper" class="browser-body ${('full-load' if c.full_load else '')}">
20 <div id="file-tree-wrapper" class="browser-body ${('full-load' if c.full_load else '')}">
13 <table class="code-browser rctable table-bordered">
21 <table class="code-browser rctable table-bordered">
14 <thead>
22 <thead>
15 <tr>
23 <tr>
16 <th>${_('Name')}</th>
24 <th>${_('Name')}</th>
17 <th>${_('Size')}</th>
25 <th>${_('Size')}</th>
18 <th>${_('Modified')}</th>
26 <th>${_('Modified')}</th>
19 <th>${_('Last Commit')}</th>
27 <th>${_('Last Commit')}</th>
20 <th>${_('Author')}</th>
28 <th>${_('Author')}</th>
21 </tr>
29 </tr>
22 </thead>
30 </thead>
23
31
24 <tbody id="tbody">
32 <tbody id="tbody">
25 <tr>
33 <tr>
26 <td colspan="5">
34 <td colspan="5">
27 ${h.files_breadcrumbs(c.repo_name, c.rhodecode_db_repo.repo_type, c.commit.raw_id, c.file.path, c.rhodecode_db_repo.landing_ref_name, request.GET.get('at'), limit_items=True)}
35 ${h.files_breadcrumbs(c.repo_name, c.rhodecode_db_repo.repo_type, c.commit.raw_id, c.file.path, c.rhodecode_db_repo.landing_ref_name, request.GET.get('at'), limit_items=True)}
28 </td>
36 </td>
29 </tr>
37 </tr>
30
38
31 <% has_files = False %>
39 <% has_files = False %>
32 % for cnt,node in enumerate(c.file):
40 % if not c.file.is_submodule():
33 <% has_files = True %>
41 % for cnt, node in enumerate(c.file):
34 <tr class="parity${(cnt % 2)}">
42 <% has_files = True %>
35 <td class="td-componentname">
43 <tr class="parity${(cnt % 2)}">
36 % if node.is_submodule():
44 <td class="td-componentname">
37 <span class="submodule-dir">
45 % if node.is_submodule():
38 % if node.url.startswith('http://') or node.url.startswith('https://'):
46 <span class="submodule-dir">
39 <a href="${node.url}">
47 % if node.url.startswith('http://') or node.url.startswith('https://'):
40 <i class="icon-directory browser-dir"></i>${node.name}
48 <a href="${node.url}">
49 <i class="icon-directory browser-dir"></i><span class="tooltip-hovercard" data-hovercard-alt="${node.url}" data-hovercard-url="javascript:renderTemplate('submoduleHovercard', {'submodule_url':'${node.url}'})">${node.name}</span>
50 </a>
51 % else:
52 <i class="icon-directory browser-dir"></i><span class="tooltip-hovercard" data-hovercard-alt="${node.url}" data-hovercard-url="javascript:renderTemplate('submoduleHovercard', {'submodule_url':'${node.url}'})">${node.name}</span>
53 % endif
54 </span>
55 % else:
56 <a href="${h.repo_files_by_ref_url(c.repo_name, c.rhodecode_db_repo.repo_type, f_path=h.safe_str(node.path), ref_name=default_landing_ref, commit_id=c.commit.raw_id, query=query)}">
57 <i class="${('icon-file-text browser-file' if node.is_file() else 'icon-directory browser-dir')}"></i>${node.name}
41 </a>
58 </a>
42 % else:
43 <i class="icon-directory browser-dir"></i>${node.name}
44 % endif
59 % endif
45 </span>
60 </td>
46 % else:
61 %if node.is_file():
47 <a href="${h.repo_files_by_ref_url(c.repo_name, c.rhodecode_db_repo.repo_type, f_path=h.safe_str(node.path), ref_name=default_landing_ref, commit_id=c.commit.raw_id, query=query)}">
62 <td class="td-size" data-attr-name="size">
48 <i class="${('icon-file-text browser-file' if node.is_file() else 'icon-directory browser-dir')}"></i>${node.name}
63 % if c.full_load:
49 </a>
64 <span data-size="${node.size}">${h.format_byte_size_binary(node.size)}</span>
50 % endif
65 % else:
51 </td>
66 ${_('Loading ...')}
52 %if node.is_file():
67 % endif
53 <td class="td-size" data-attr-name="size">
68 </td>
54 % if c.full_load:
69 <td class="td-time" data-attr-name="modified_at">
55 <span data-size="${node.size}">${h.format_byte_size_binary(node.size)}</span>
70 % if c.full_load:
56 % else:
71 <span data-date="${node.last_commit.date}">${h.age_component(node.last_commit.date)}</span>
57 ${_('Loading ...')}
72 % endif
58 % endif
73 </td>
59 </td>
74 <td class="td-hash" data-attr-name="commit_id">
60 <td class="td-time" data-attr-name="modified_at">
75 % if c.full_load:
61 % if c.full_load:
76 <div class="tooltip-hovercard" data-hovercard-alt="${node.last_commit.message}" data-hovercard-url="${h.route_path('hovercard_repo_commit', repo_name=c.repo_name, commit_id=node.last_commit.raw_id)}">
62 <span data-date="${node.last_commit.date}">${h.age_component(node.last_commit.date)}</span>
77 <pre data-commit-id="${node.last_commit.raw_id}">r${node.last_commit.idx}:${node.last_commit.short_id}</pre>
63 % endif
78 </div>
64 </td>
79 % endif
65 <td class="td-hash" data-attr-name="commit_id">
80 </td>
66 % if c.full_load:
81 <td class="td-user" data-attr-name="author">
67 <div class="tooltip-hovercard" data-hovercard-alt="${node.last_commit.message}" data-hovercard-url="${h.route_path('hovercard_repo_commit', repo_name=c.repo_name, commit_id=node.last_commit.raw_id)}">
82 % if c.full_load:
68 <pre data-commit-id="${node.last_commit.raw_id}">r${node.last_commit.idx}:${node.last_commit.short_id}</pre>
83 <span data-author="${node.last_commit.author}">${h.gravatar_with_user(request, node.last_commit.author, tooltip=True)|n}</span>
69 </div>
84 % endif
70 % endif
85 </td>
71 </td>
86 %else:
72 <td class="td-user" data-attr-name="author">
87 <td></td>
73 % if c.full_load:
88 <td></td>
74 <span data-author="${node.last_commit.author}">${h.gravatar_with_user(request, node.last_commit.author, tooltip=True)|n}</span>
89 <td></td>
75 % endif
90 <td></td>
76 </td>
91 %endif
77 %else:
92 </tr>
78 <td></td>
93 % endfor
79 <td></td>
94 % endif
80 <td></td>
81 <td></td>
82 %endif
83 </tr>
84 % endfor
85
95
86 % if not has_files:
96 % if not has_files:
87 <tr>
97 <tr>
88 <td colspan="5">
98 <td colspan="5">
89 ##empty-dir mostly SVN
99 ##empty-dir mostly SVN
90 &nbsp;
100
101 ## submodule if we somehow endup
102 % if c.file.is_submodule():
103 <span class="submodule-dir">Submodule ${h.escape(c.file.name)}</span>
104 %endif
91 </td>
105 </td>
92 </tr>
106 </tr>
93 % endif
107 % endif
94
108
95 </tbody>
109 </tbody>
96 <tbody id="tbody_filtered"></tbody>
110 <tbody id="tbody_filtered"></tbody>
97 </table>
111 </table>
98 </div>
112 </div>
@@ -1,34 +1,32 b''
1 <%def name="title(*args)">
1 <%def name="title(*args)">
2 ${_('{} Files').format(c.repo_name)}
2 ${_('{} Files').format(c.repo_name)}
3 %if hasattr(c,'file'):
3 %if hasattr(c,'file'):
4 &middot; ${(h.safe_str(c.file.path) or '\\')}
4 &middot; ${(h.safe_str(c.file.path) or '\\')}
5 %endif
5 %endif
6
6
7 %if c.rhodecode_name:
7 %if c.rhodecode_name:
8 &middot; ${h.branding(c.rhodecode_name)}
8 &middot; ${h.branding(c.rhodecode_name)}
9 %endif
9 %endif
10 </%def>
10 </%def>
11
11
12 <div>
12 <div>
13
13
14 <div class="summary-detail">
14 <div class="summary-detail">
15 <div class="summary-detail-header">
15 <div class="summary-detail-header">
16
16
17 </div><!--end summary-detail-header-->
17 </div><!--end summary-detail-header-->
18
18 % if c.file.is_dir() or c.file.is_submodule():
19 % if c.file.is_submodule():
20 <span class="submodule-dir">Submodule ${h.escape(c.file.name)}</span>
21 % elif c.file.is_dir():
22 <%include file='files_tree_header.mako'/>
19 <%include file='files_tree_header.mako'/>
23 % else:
20 % else:
24 <%include file='files_source_header.mako'/>
21 <%include file='files_source_header.mako'/>
25 % endif
22 % endif
26
23
27 </div> <!--end summary-detail-->
24 </div> <!--end summary-detail-->
28 % if c.file.is_dir():
25
26 % if c.file.is_dir() or c.file.is_submodule():
29 <%include file='files_browser.mako'/>
27 <%include file='files_browser.mako'/>
30 % else:
28 % else:
31 <%include file='files_source.mako'/>
29 <%include file='files_source.mako'/>
32 % endif
30 % endif
33
31
34 </div>
32 </div>
General Comments 0
You need to be logged in to leave comments. Login now