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