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