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