##// END OF EJS Templates
cache: turn off caches if expiration_time is 0
marcink -
r2848:daea8b54 default
parent child Browse files
Show More
@@ -1,1278 +1,1297 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2018 RhodeCode GmbH
3 # Copyright (C) 2011-2018 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
27
28 from pyramid.httpexceptions import HTTPNotFound, HTTPBadRequest, HTTPFound
28 from pyramid.httpexceptions import HTTPNotFound, HTTPBadRequest, HTTPFound
29 from pyramid.view import view_config
29 from pyramid.view import view_config
30 from pyramid.renderers import render
30 from pyramid.renderers import render
31 from pyramid.response import Response
31 from pyramid.response import Response
32
32
33 import rhodecode
33 from rhodecode.apps._base import RepoAppView
34 from rhodecode.apps._base import RepoAppView
34
35
35 from rhodecode.controllers.utils import parse_path_ref
36 from rhodecode.controllers.utils import parse_path_ref
36 from rhodecode.lib import diffs, helpers as h, caches, rc_cache
37 from rhodecode.lib import diffs, helpers as h, caches, rc_cache
37 from rhodecode.lib import audit_logger
38 from rhodecode.lib import audit_logger
38 from rhodecode.lib.exceptions import NonRelativePathError
39 from rhodecode.lib.exceptions import NonRelativePathError
39 from rhodecode.lib.codeblocks import (
40 from rhodecode.lib.codeblocks import (
40 filenode_as_lines_tokens, filenode_as_annotated_lines_tokens)
41 filenode_as_lines_tokens, filenode_as_annotated_lines_tokens)
41 from rhodecode.lib.utils2 import (
42 from rhodecode.lib.utils2 import (
42 convert_line_endings, detect_mode, safe_str, str2bool)
43 convert_line_endings, detect_mode, safe_str, str2bool, safe_int)
43 from rhodecode.lib.auth import (
44 from rhodecode.lib.auth import (
44 LoginRequired, HasRepoPermissionAnyDecorator, CSRFRequired)
45 LoginRequired, HasRepoPermissionAnyDecorator, CSRFRequired)
45 from rhodecode.lib.vcs import path as vcspath
46 from rhodecode.lib.vcs import path as vcspath
46 from rhodecode.lib.vcs.backends.base import EmptyCommit
47 from rhodecode.lib.vcs.backends.base import EmptyCommit
47 from rhodecode.lib.vcs.conf import settings
48 from rhodecode.lib.vcs.conf import settings
48 from rhodecode.lib.vcs.nodes import FileNode
49 from rhodecode.lib.vcs.nodes import FileNode
49 from rhodecode.lib.vcs.exceptions import (
50 from rhodecode.lib.vcs.exceptions import (
50 RepositoryError, CommitDoesNotExistError, EmptyRepositoryError,
51 RepositoryError, CommitDoesNotExistError, EmptyRepositoryError,
51 ImproperArchiveTypeError, VCSError, NodeAlreadyExistsError,
52 ImproperArchiveTypeError, VCSError, NodeAlreadyExistsError,
52 NodeDoesNotExistError, CommitError, NodeError)
53 NodeDoesNotExistError, CommitError, NodeError)
53
54
54 from rhodecode.model.scm import ScmModel
55 from rhodecode.model.scm import ScmModel
55 from rhodecode.model.db import Repository
56 from rhodecode.model.db import Repository
56
57
57 log = logging.getLogger(__name__)
58 log = logging.getLogger(__name__)
58
59
59
60
60 class RepoFilesView(RepoAppView):
61 class RepoFilesView(RepoAppView):
61
62
62 @staticmethod
63 @staticmethod
63 def adjust_file_path_for_svn(f_path, repo):
64 def adjust_file_path_for_svn(f_path, repo):
64 """
65 """
65 Computes the relative path of `f_path`.
66 Computes the relative path of `f_path`.
66
67
67 This is mainly based on prefix matching of the recognized tags and
68 This is mainly based on prefix matching of the recognized tags and
68 branches in the underlying repository.
69 branches in the underlying repository.
69 """
70 """
70 tags_and_branches = itertools.chain(
71 tags_and_branches = itertools.chain(
71 repo.branches.iterkeys(),
72 repo.branches.iterkeys(),
72 repo.tags.iterkeys())
73 repo.tags.iterkeys())
73 tags_and_branches = sorted(tags_and_branches, key=len, reverse=True)
74 tags_and_branches = sorted(tags_and_branches, key=len, reverse=True)
74
75
75 for name in tags_and_branches:
76 for name in tags_and_branches:
76 if f_path.startswith('{}/'.format(name)):
77 if f_path.startswith('{}/'.format(name)):
77 f_path = vcspath.relpath(f_path, name)
78 f_path = vcspath.relpath(f_path, name)
78 break
79 break
79 return f_path
80 return f_path
80
81
81 def load_default_context(self):
82 def load_default_context(self):
82 c = self._get_local_tmpl_context(include_app_defaults=True)
83 c = self._get_local_tmpl_context(include_app_defaults=True)
83 c.rhodecode_repo = self.rhodecode_vcs_repo
84 c.rhodecode_repo = self.rhodecode_vcs_repo
84 return c
85 return c
85
86
86 def _ensure_not_locked(self):
87 def _ensure_not_locked(self):
87 _ = self.request.translate
88 _ = self.request.translate
88
89
89 repo = self.db_repo
90 repo = self.db_repo
90 if repo.enable_locking and repo.locked[0]:
91 if repo.enable_locking and repo.locked[0]:
91 h.flash(_('This repository has been locked by %s on %s')
92 h.flash(_('This repository has been locked by %s on %s')
92 % (h.person_by_id(repo.locked[0]),
93 % (h.person_by_id(repo.locked[0]),
93 h.format_date(h.time_to_datetime(repo.locked[1]))),
94 h.format_date(h.time_to_datetime(repo.locked[1]))),
94 'warning')
95 'warning')
95 files_url = h.route_path(
96 files_url = h.route_path(
96 'repo_files:default_path',
97 'repo_files:default_path',
97 repo_name=self.db_repo_name, commit_id='tip')
98 repo_name=self.db_repo_name, commit_id='tip')
98 raise HTTPFound(files_url)
99 raise HTTPFound(files_url)
99
100
100 def _get_commit_and_path(self):
101 def _get_commit_and_path(self):
101 default_commit_id = self.db_repo.landing_rev[1]
102 default_commit_id = self.db_repo.landing_rev[1]
102 default_f_path = '/'
103 default_f_path = '/'
103
104
104 commit_id = self.request.matchdict.get(
105 commit_id = self.request.matchdict.get(
105 'commit_id', default_commit_id)
106 'commit_id', default_commit_id)
106 f_path = self._get_f_path(self.request.matchdict, default_f_path)
107 f_path = self._get_f_path(self.request.matchdict, default_f_path)
107 return commit_id, f_path
108 return commit_id, f_path
108
109
109 def _get_default_encoding(self, c):
110 def _get_default_encoding(self, c):
110 enc_list = getattr(c, 'default_encodings', [])
111 enc_list = getattr(c, 'default_encodings', [])
111 return enc_list[0] if enc_list else 'UTF-8'
112 return enc_list[0] if enc_list else 'UTF-8'
112
113
113 def _get_commit_or_redirect(self, commit_id, redirect_after=True):
114 def _get_commit_or_redirect(self, commit_id, redirect_after=True):
114 """
115 """
115 This is a safe way to get commit. If an error occurs it redirects to
116 This is a safe way to get commit. If an error occurs it redirects to
116 tip with proper message
117 tip with proper message
117
118
118 :param commit_id: id of commit to fetch
119 :param commit_id: id of commit to fetch
119 :param redirect_after: toggle redirection
120 :param redirect_after: toggle redirection
120 """
121 """
121 _ = self.request.translate
122 _ = self.request.translate
122
123
123 try:
124 try:
124 return self.rhodecode_vcs_repo.get_commit(commit_id)
125 return self.rhodecode_vcs_repo.get_commit(commit_id)
125 except EmptyRepositoryError:
126 except EmptyRepositoryError:
126 if not redirect_after:
127 if not redirect_after:
127 return None
128 return None
128
129
129 _url = h.route_path(
130 _url = h.route_path(
130 'repo_files_add_file',
131 'repo_files_add_file',
131 repo_name=self.db_repo_name, commit_id=0, f_path='',
132 repo_name=self.db_repo_name, commit_id=0, f_path='',
132 _anchor='edit')
133 _anchor='edit')
133
134
134 if h.HasRepoPermissionAny(
135 if h.HasRepoPermissionAny(
135 'repository.write', 'repository.admin')(self.db_repo_name):
136 'repository.write', 'repository.admin')(self.db_repo_name):
136 add_new = h.link_to(
137 add_new = h.link_to(
137 _('Click here to add a new file.'), _url, class_="alert-link")
138 _('Click here to add a new file.'), _url, class_="alert-link")
138 else:
139 else:
139 add_new = ""
140 add_new = ""
140
141
141 h.flash(h.literal(
142 h.flash(h.literal(
142 _('There are no files yet. %s') % add_new), category='warning')
143 _('There are no files yet. %s') % add_new), category='warning')
143 raise HTTPFound(
144 raise HTTPFound(
144 h.route_path('repo_summary', repo_name=self.db_repo_name))
145 h.route_path('repo_summary', repo_name=self.db_repo_name))
145
146
146 except (CommitDoesNotExistError, LookupError):
147 except (CommitDoesNotExistError, LookupError):
147 msg = _('No such commit exists for this repository')
148 msg = _('No such commit exists for this repository')
148 h.flash(msg, category='error')
149 h.flash(msg, category='error')
149 raise HTTPNotFound()
150 raise HTTPNotFound()
150 except RepositoryError as e:
151 except RepositoryError as e:
151 h.flash(safe_str(h.escape(e)), category='error')
152 h.flash(safe_str(h.escape(e)), category='error')
152 raise HTTPNotFound()
153 raise HTTPNotFound()
153
154
154 def _get_filenode_or_redirect(self, commit_obj, path):
155 def _get_filenode_or_redirect(self, commit_obj, path):
155 """
156 """
156 Returns file_node, if error occurs or given path is directory,
157 Returns file_node, if error occurs or given path is directory,
157 it'll redirect to top level path
158 it'll redirect to top level path
158 """
159 """
159 _ = self.request.translate
160 _ = self.request.translate
160
161
161 try:
162 try:
162 file_node = commit_obj.get_node(path)
163 file_node = commit_obj.get_node(path)
163 if file_node.is_dir():
164 if file_node.is_dir():
164 raise RepositoryError('The given path is a directory')
165 raise RepositoryError('The given path is a directory')
165 except CommitDoesNotExistError:
166 except CommitDoesNotExistError:
166 log.exception('No such commit exists for this repository')
167 log.exception('No such commit exists for this repository')
167 h.flash(_('No such commit exists for this repository'), category='error')
168 h.flash(_('No such commit exists for this repository'), category='error')
168 raise HTTPNotFound()
169 raise HTTPNotFound()
169 except RepositoryError as e:
170 except RepositoryError as e:
170 log.warning('Repository error while fetching '
171 log.warning('Repository error while fetching '
171 'filenode `%s`. Err:%s', path, e)
172 'filenode `%s`. Err:%s', path, e)
172 h.flash(safe_str(h.escape(e)), category='error')
173 h.flash(safe_str(h.escape(e)), category='error')
173 raise HTTPNotFound()
174 raise HTTPNotFound()
174
175
175 return file_node
176 return file_node
176
177
177 def _is_valid_head(self, commit_id, repo):
178 def _is_valid_head(self, commit_id, repo):
178 # check if commit is a branch identifier- basically we cannot
179 # check if commit is a branch identifier- basically we cannot
179 # create multiple heads via file editing
180 # create multiple heads via file editing
180 valid_heads = repo.branches.keys() + repo.branches.values()
181 valid_heads = repo.branches.keys() + repo.branches.values()
181
182
182 if h.is_svn(repo) and not repo.is_empty():
183 if h.is_svn(repo) and not repo.is_empty():
183 # Note: Subversion only has one head, we add it here in case there
184 # Note: Subversion only has one head, we add it here in case there
184 # is no branch matched.
185 # is no branch matched.
185 valid_heads.append(repo.get_commit(commit_idx=-1).raw_id)
186 valid_heads.append(repo.get_commit(commit_idx=-1).raw_id)
186
187
187 # check if commit is a branch name or branch hash
188 # check if commit is a branch name or branch hash
188 return commit_id in valid_heads
189 return commit_id in valid_heads
189
190
190 def _get_tree_at_commit(
191 def _get_tree_at_commit(
191 self, c, commit_id, f_path, full_load=False):
192 self, c, commit_id, f_path, full_load=False):
192
193
193 repo_id = self.db_repo.repo_id
194 repo_id = self.db_repo.repo_id
194
195
196 cache_seconds = safe_int(
197 rhodecode.CONFIG.get('rc_cache.cache_repo.expiration_time'))
198 cache_on = cache_seconds > 0
199 log.debug(
200 'Computing FILE TREE for repo_id %s commit_id `%s` and path `%s`'
201 'with caching: %s[TTL: %ss]' % (
202 repo_id, commit_id, f_path, cache_on, cache_seconds or 0))
203
195 cache_namespace_uid = 'cache_repo.{}'.format(repo_id)
204 cache_namespace_uid = 'cache_repo.{}'.format(repo_id)
196 region = rc_cache.get_or_create_region('cache_repo', cache_namespace_uid)
205 region = rc_cache.get_or_create_region('cache_repo', cache_namespace_uid)
197
206
198 @region.cache_on_arguments(namespace=cache_namespace_uid)
207 @region.cache_on_arguments(namespace=cache_namespace_uid,
208 should_cache_fn=lambda v: cache_on)
199 def compute_file_tree(repo_id, commit_id, f_path, full_load):
209 def compute_file_tree(repo_id, commit_id, f_path, full_load):
200 log.debug('Generating cached file tree for repo_id: %s, %s, %s',
210 log.debug('Generating cached file tree for repo_id: %s, %s, %s',
201 repo_id, commit_id, f_path)
211 repo_id, commit_id, f_path)
202
212
203 c.full_load = full_load
213 c.full_load = full_load
204 return render(
214 return render(
205 'rhodecode:templates/files/files_browser_tree.mako',
215 'rhodecode:templates/files/files_browser_tree.mako',
206 self._get_template_context(c), self.request)
216 self._get_template_context(c), self.request)
207
217
208 return compute_file_tree(self.db_repo.repo_id, commit_id, f_path, full_load)
218 return compute_file_tree(self.db_repo.repo_id, commit_id, f_path, full_load)
209
219
210 def _get_archive_spec(self, fname):
220 def _get_archive_spec(self, fname):
211 log.debug('Detecting archive spec for: `%s`', fname)
221 log.debug('Detecting archive spec for: `%s`', fname)
212
222
213 fileformat = None
223 fileformat = None
214 ext = None
224 ext = None
215 content_type = None
225 content_type = None
216 for a_type, ext_data in settings.ARCHIVE_SPECS.items():
226 for a_type, ext_data in settings.ARCHIVE_SPECS.items():
217 content_type, extension = ext_data
227 content_type, extension = ext_data
218
228
219 if fname.endswith(extension):
229 if fname.endswith(extension):
220 fileformat = a_type
230 fileformat = a_type
221 log.debug('archive is of type: %s', fileformat)
231 log.debug('archive is of type: %s', fileformat)
222 ext = extension
232 ext = extension
223 break
233 break
224
234
225 if not fileformat:
235 if not fileformat:
226 raise ValueError()
236 raise ValueError()
227
237
228 # left over part of whole fname is the commit
238 # left over part of whole fname is the commit
229 commit_id = fname[:-len(ext)]
239 commit_id = fname[:-len(ext)]
230
240
231 return commit_id, ext, fileformat, content_type
241 return commit_id, ext, fileformat, content_type
232
242
233 @LoginRequired()
243 @LoginRequired()
234 @HasRepoPermissionAnyDecorator(
244 @HasRepoPermissionAnyDecorator(
235 'repository.read', 'repository.write', 'repository.admin')
245 'repository.read', 'repository.write', 'repository.admin')
236 @view_config(
246 @view_config(
237 route_name='repo_archivefile', request_method='GET',
247 route_name='repo_archivefile', request_method='GET',
238 renderer=None)
248 renderer=None)
239 def repo_archivefile(self):
249 def repo_archivefile(self):
240 # archive cache config
250 # archive cache config
241 from rhodecode import CONFIG
251 from rhodecode import CONFIG
242 _ = self.request.translate
252 _ = self.request.translate
243 self.load_default_context()
253 self.load_default_context()
244
254
245 fname = self.request.matchdict['fname']
255 fname = self.request.matchdict['fname']
246 subrepos = self.request.GET.get('subrepos') == 'true'
256 subrepos = self.request.GET.get('subrepos') == 'true'
247
257
248 if not self.db_repo.enable_downloads:
258 if not self.db_repo.enable_downloads:
249 return Response(_('Downloads disabled'))
259 return Response(_('Downloads disabled'))
250
260
251 try:
261 try:
252 commit_id, ext, fileformat, content_type = \
262 commit_id, ext, fileformat, content_type = \
253 self._get_archive_spec(fname)
263 self._get_archive_spec(fname)
254 except ValueError:
264 except ValueError:
255 return Response(_('Unknown archive type for: `{}`').format(
265 return Response(_('Unknown archive type for: `{}`').format(
256 h.escape(fname)))
266 h.escape(fname)))
257
267
258 try:
268 try:
259 commit = self.rhodecode_vcs_repo.get_commit(commit_id)
269 commit = self.rhodecode_vcs_repo.get_commit(commit_id)
260 except CommitDoesNotExistError:
270 except CommitDoesNotExistError:
261 return Response(_('Unknown commit_id {}').format(
271 return Response(_('Unknown commit_id {}').format(
262 h.escape(commit_id)))
272 h.escape(commit_id)))
263 except EmptyRepositoryError:
273 except EmptyRepositoryError:
264 return Response(_('Empty repository'))
274 return Response(_('Empty repository'))
265
275
266 archive_name = '%s-%s%s%s' % (
276 archive_name = '%s-%s%s%s' % (
267 safe_str(self.db_repo_name.replace('/', '_')),
277 safe_str(self.db_repo_name.replace('/', '_')),
268 '-sub' if subrepos else '',
278 '-sub' if subrepos else '',
269 safe_str(commit.short_id), ext)
279 safe_str(commit.short_id), ext)
270
280
271 use_cached_archive = False
281 use_cached_archive = False
272 archive_cache_enabled = CONFIG.get(
282 archive_cache_enabled = CONFIG.get(
273 'archive_cache_dir') and not self.request.GET.get('no_cache')
283 'archive_cache_dir') and not self.request.GET.get('no_cache')
274
284
275 if archive_cache_enabled:
285 if archive_cache_enabled:
276 # check if we it's ok to write
286 # check if we it's ok to write
277 if not os.path.isdir(CONFIG['archive_cache_dir']):
287 if not os.path.isdir(CONFIG['archive_cache_dir']):
278 os.makedirs(CONFIG['archive_cache_dir'])
288 os.makedirs(CONFIG['archive_cache_dir'])
279 cached_archive_path = os.path.join(
289 cached_archive_path = os.path.join(
280 CONFIG['archive_cache_dir'], archive_name)
290 CONFIG['archive_cache_dir'], archive_name)
281 if os.path.isfile(cached_archive_path):
291 if os.path.isfile(cached_archive_path):
282 log.debug('Found cached archive in %s', cached_archive_path)
292 log.debug('Found cached archive in %s', cached_archive_path)
283 fd, archive = None, cached_archive_path
293 fd, archive = None, cached_archive_path
284 use_cached_archive = True
294 use_cached_archive = True
285 else:
295 else:
286 log.debug('Archive %s is not yet cached', archive_name)
296 log.debug('Archive %s is not yet cached', archive_name)
287
297
288 if not use_cached_archive:
298 if not use_cached_archive:
289 # generate new archive
299 # generate new archive
290 fd, archive = tempfile.mkstemp()
300 fd, archive = tempfile.mkstemp()
291 log.debug('Creating new temp archive in %s', archive)
301 log.debug('Creating new temp archive in %s', archive)
292 try:
302 try:
293 commit.archive_repo(archive, kind=fileformat, subrepos=subrepos)
303 commit.archive_repo(archive, kind=fileformat, subrepos=subrepos)
294 except ImproperArchiveTypeError:
304 except ImproperArchiveTypeError:
295 return _('Unknown archive type')
305 return _('Unknown archive type')
296 if archive_cache_enabled:
306 if archive_cache_enabled:
297 # if we generated the archive and we have cache enabled
307 # if we generated the archive and we have cache enabled
298 # let's use this for future
308 # let's use this for future
299 log.debug('Storing new archive in %s', cached_archive_path)
309 log.debug('Storing new archive in %s', cached_archive_path)
300 shutil.move(archive, cached_archive_path)
310 shutil.move(archive, cached_archive_path)
301 archive = cached_archive_path
311 archive = cached_archive_path
302
312
303 # store download action
313 # store download action
304 audit_logger.store_web(
314 audit_logger.store_web(
305 'repo.archive.download', action_data={
315 'repo.archive.download', action_data={
306 'user_agent': self.request.user_agent,
316 'user_agent': self.request.user_agent,
307 'archive_name': archive_name,
317 'archive_name': archive_name,
308 'archive_spec': fname,
318 'archive_spec': fname,
309 'archive_cached': use_cached_archive},
319 'archive_cached': use_cached_archive},
310 user=self._rhodecode_user,
320 user=self._rhodecode_user,
311 repo=self.db_repo,
321 repo=self.db_repo,
312 commit=True
322 commit=True
313 )
323 )
314
324
315 def get_chunked_archive(archive):
325 def get_chunked_archive(archive):
316 with open(archive, 'rb') as stream:
326 with open(archive, 'rb') as stream:
317 while True:
327 while True:
318 data = stream.read(16 * 1024)
328 data = stream.read(16 * 1024)
319 if not data:
329 if not data:
320 if fd: # fd means we used temporary file
330 if fd: # fd means we used temporary file
321 os.close(fd)
331 os.close(fd)
322 if not archive_cache_enabled:
332 if not archive_cache_enabled:
323 log.debug('Destroying temp archive %s', archive)
333 log.debug('Destroying temp archive %s', archive)
324 os.remove(archive)
334 os.remove(archive)
325 break
335 break
326 yield data
336 yield data
327
337
328 response = Response(app_iter=get_chunked_archive(archive))
338 response = Response(app_iter=get_chunked_archive(archive))
329 response.content_disposition = str(
339 response.content_disposition = str(
330 'attachment; filename=%s' % archive_name)
340 'attachment; filename=%s' % archive_name)
331 response.content_type = str(content_type)
341 response.content_type = str(content_type)
332
342
333 return response
343 return response
334
344
335 def _get_file_node(self, commit_id, f_path):
345 def _get_file_node(self, commit_id, f_path):
336 if commit_id not in ['', None, 'None', '0' * 12, '0' * 40]:
346 if commit_id not in ['', None, 'None', '0' * 12, '0' * 40]:
337 commit = self.rhodecode_vcs_repo.get_commit(commit_id=commit_id)
347 commit = self.rhodecode_vcs_repo.get_commit(commit_id=commit_id)
338 try:
348 try:
339 node = commit.get_node(f_path)
349 node = commit.get_node(f_path)
340 if node.is_dir():
350 if node.is_dir():
341 raise NodeError('%s path is a %s not a file'
351 raise NodeError('%s path is a %s not a file'
342 % (node, type(node)))
352 % (node, type(node)))
343 except NodeDoesNotExistError:
353 except NodeDoesNotExistError:
344 commit = EmptyCommit(
354 commit = EmptyCommit(
345 commit_id=commit_id,
355 commit_id=commit_id,
346 idx=commit.idx,
356 idx=commit.idx,
347 repo=commit.repository,
357 repo=commit.repository,
348 alias=commit.repository.alias,
358 alias=commit.repository.alias,
349 message=commit.message,
359 message=commit.message,
350 author=commit.author,
360 author=commit.author,
351 date=commit.date)
361 date=commit.date)
352 node = FileNode(f_path, '', commit=commit)
362 node = FileNode(f_path, '', commit=commit)
353 else:
363 else:
354 commit = EmptyCommit(
364 commit = EmptyCommit(
355 repo=self.rhodecode_vcs_repo,
365 repo=self.rhodecode_vcs_repo,
356 alias=self.rhodecode_vcs_repo.alias)
366 alias=self.rhodecode_vcs_repo.alias)
357 node = FileNode(f_path, '', commit=commit)
367 node = FileNode(f_path, '', commit=commit)
358 return node
368 return node
359
369
360 @LoginRequired()
370 @LoginRequired()
361 @HasRepoPermissionAnyDecorator(
371 @HasRepoPermissionAnyDecorator(
362 'repository.read', 'repository.write', 'repository.admin')
372 'repository.read', 'repository.write', 'repository.admin')
363 @view_config(
373 @view_config(
364 route_name='repo_files_diff', request_method='GET',
374 route_name='repo_files_diff', request_method='GET',
365 renderer=None)
375 renderer=None)
366 def repo_files_diff(self):
376 def repo_files_diff(self):
367 c = self.load_default_context()
377 c = self.load_default_context()
368 f_path = self._get_f_path(self.request.matchdict)
378 f_path = self._get_f_path(self.request.matchdict)
369 diff1 = self.request.GET.get('diff1', '')
379 diff1 = self.request.GET.get('diff1', '')
370 diff2 = self.request.GET.get('diff2', '')
380 diff2 = self.request.GET.get('diff2', '')
371
381
372 path1, diff1 = parse_path_ref(diff1, default_path=f_path)
382 path1, diff1 = parse_path_ref(diff1, default_path=f_path)
373
383
374 ignore_whitespace = str2bool(self.request.GET.get('ignorews'))
384 ignore_whitespace = str2bool(self.request.GET.get('ignorews'))
375 line_context = self.request.GET.get('context', 3)
385 line_context = self.request.GET.get('context', 3)
376
386
377 if not any((diff1, diff2)):
387 if not any((diff1, diff2)):
378 h.flash(
388 h.flash(
379 'Need query parameter "diff1" or "diff2" to generate a diff.',
389 'Need query parameter "diff1" or "diff2" to generate a diff.',
380 category='error')
390 category='error')
381 raise HTTPBadRequest()
391 raise HTTPBadRequest()
382
392
383 c.action = self.request.GET.get('diff')
393 c.action = self.request.GET.get('diff')
384 if c.action not in ['download', 'raw']:
394 if c.action not in ['download', 'raw']:
385 compare_url = h.route_path(
395 compare_url = h.route_path(
386 'repo_compare',
396 'repo_compare',
387 repo_name=self.db_repo_name,
397 repo_name=self.db_repo_name,
388 source_ref_type='rev',
398 source_ref_type='rev',
389 source_ref=diff1,
399 source_ref=diff1,
390 target_repo=self.db_repo_name,
400 target_repo=self.db_repo_name,
391 target_ref_type='rev',
401 target_ref_type='rev',
392 target_ref=diff2,
402 target_ref=diff2,
393 _query=dict(f_path=f_path))
403 _query=dict(f_path=f_path))
394 # redirect to new view if we render diff
404 # redirect to new view if we render diff
395 raise HTTPFound(compare_url)
405 raise HTTPFound(compare_url)
396
406
397 try:
407 try:
398 node1 = self._get_file_node(diff1, path1)
408 node1 = self._get_file_node(diff1, path1)
399 node2 = self._get_file_node(diff2, f_path)
409 node2 = self._get_file_node(diff2, f_path)
400 except (RepositoryError, NodeError):
410 except (RepositoryError, NodeError):
401 log.exception("Exception while trying to get node from repository")
411 log.exception("Exception while trying to get node from repository")
402 raise HTTPFound(
412 raise HTTPFound(
403 h.route_path('repo_files', repo_name=self.db_repo_name,
413 h.route_path('repo_files', repo_name=self.db_repo_name,
404 commit_id='tip', f_path=f_path))
414 commit_id='tip', f_path=f_path))
405
415
406 if all(isinstance(node.commit, EmptyCommit)
416 if all(isinstance(node.commit, EmptyCommit)
407 for node in (node1, node2)):
417 for node in (node1, node2)):
408 raise HTTPNotFound()
418 raise HTTPNotFound()
409
419
410 c.commit_1 = node1.commit
420 c.commit_1 = node1.commit
411 c.commit_2 = node2.commit
421 c.commit_2 = node2.commit
412
422
413 if c.action == 'download':
423 if c.action == 'download':
414 _diff = diffs.get_gitdiff(node1, node2,
424 _diff = diffs.get_gitdiff(node1, node2,
415 ignore_whitespace=ignore_whitespace,
425 ignore_whitespace=ignore_whitespace,
416 context=line_context)
426 context=line_context)
417 diff = diffs.DiffProcessor(_diff, format='gitdiff')
427 diff = diffs.DiffProcessor(_diff, format='gitdiff')
418
428
419 response = Response(self.path_filter.get_raw_patch(diff))
429 response = Response(self.path_filter.get_raw_patch(diff))
420 response.content_type = 'text/plain'
430 response.content_type = 'text/plain'
421 response.content_disposition = (
431 response.content_disposition = (
422 'attachment; filename=%s_%s_vs_%s.diff' % (f_path, diff1, diff2)
432 'attachment; filename=%s_%s_vs_%s.diff' % (f_path, diff1, diff2)
423 )
433 )
424 charset = self._get_default_encoding(c)
434 charset = self._get_default_encoding(c)
425 if charset:
435 if charset:
426 response.charset = charset
436 response.charset = charset
427 return response
437 return response
428
438
429 elif c.action == 'raw':
439 elif c.action == 'raw':
430 _diff = diffs.get_gitdiff(node1, node2,
440 _diff = diffs.get_gitdiff(node1, node2,
431 ignore_whitespace=ignore_whitespace,
441 ignore_whitespace=ignore_whitespace,
432 context=line_context)
442 context=line_context)
433 diff = diffs.DiffProcessor(_diff, format='gitdiff')
443 diff = diffs.DiffProcessor(_diff, format='gitdiff')
434
444
435 response = Response(self.path_filter.get_raw_patch(diff))
445 response = Response(self.path_filter.get_raw_patch(diff))
436 response.content_type = 'text/plain'
446 response.content_type = 'text/plain'
437 charset = self._get_default_encoding(c)
447 charset = self._get_default_encoding(c)
438 if charset:
448 if charset:
439 response.charset = charset
449 response.charset = charset
440 return response
450 return response
441
451
442 # in case we ever end up here
452 # in case we ever end up here
443 raise HTTPNotFound()
453 raise HTTPNotFound()
444
454
445 @LoginRequired()
455 @LoginRequired()
446 @HasRepoPermissionAnyDecorator(
456 @HasRepoPermissionAnyDecorator(
447 'repository.read', 'repository.write', 'repository.admin')
457 'repository.read', 'repository.write', 'repository.admin')
448 @view_config(
458 @view_config(
449 route_name='repo_files_diff_2way_redirect', request_method='GET',
459 route_name='repo_files_diff_2way_redirect', request_method='GET',
450 renderer=None)
460 renderer=None)
451 def repo_files_diff_2way_redirect(self):
461 def repo_files_diff_2way_redirect(self):
452 """
462 """
453 Kept only to make OLD links work
463 Kept only to make OLD links work
454 """
464 """
455 f_path = self._get_f_path_unchecked(self.request.matchdict)
465 f_path = self._get_f_path_unchecked(self.request.matchdict)
456 diff1 = self.request.GET.get('diff1', '')
466 diff1 = self.request.GET.get('diff1', '')
457 diff2 = self.request.GET.get('diff2', '')
467 diff2 = self.request.GET.get('diff2', '')
458
468
459 if not any((diff1, diff2)):
469 if not any((diff1, diff2)):
460 h.flash(
470 h.flash(
461 'Need query parameter "diff1" or "diff2" to generate a diff.',
471 'Need query parameter "diff1" or "diff2" to generate a diff.',
462 category='error')
472 category='error')
463 raise HTTPBadRequest()
473 raise HTTPBadRequest()
464
474
465 compare_url = h.route_path(
475 compare_url = h.route_path(
466 'repo_compare',
476 'repo_compare',
467 repo_name=self.db_repo_name,
477 repo_name=self.db_repo_name,
468 source_ref_type='rev',
478 source_ref_type='rev',
469 source_ref=diff1,
479 source_ref=diff1,
470 target_ref_type='rev',
480 target_ref_type='rev',
471 target_ref=diff2,
481 target_ref=diff2,
472 _query=dict(f_path=f_path, diffmode='sideside',
482 _query=dict(f_path=f_path, diffmode='sideside',
473 target_repo=self.db_repo_name,))
483 target_repo=self.db_repo_name,))
474 raise HTTPFound(compare_url)
484 raise HTTPFound(compare_url)
475
485
476 @LoginRequired()
486 @LoginRequired()
477 @HasRepoPermissionAnyDecorator(
487 @HasRepoPermissionAnyDecorator(
478 'repository.read', 'repository.write', 'repository.admin')
488 'repository.read', 'repository.write', 'repository.admin')
479 @view_config(
489 @view_config(
480 route_name='repo_files', request_method='GET',
490 route_name='repo_files', request_method='GET',
481 renderer=None)
491 renderer=None)
482 @view_config(
492 @view_config(
483 route_name='repo_files:default_path', request_method='GET',
493 route_name='repo_files:default_path', request_method='GET',
484 renderer=None)
494 renderer=None)
485 @view_config(
495 @view_config(
486 route_name='repo_files:default_commit', request_method='GET',
496 route_name='repo_files:default_commit', request_method='GET',
487 renderer=None)
497 renderer=None)
488 @view_config(
498 @view_config(
489 route_name='repo_files:rendered', request_method='GET',
499 route_name='repo_files:rendered', request_method='GET',
490 renderer=None)
500 renderer=None)
491 @view_config(
501 @view_config(
492 route_name='repo_files:annotated', request_method='GET',
502 route_name='repo_files:annotated', request_method='GET',
493 renderer=None)
503 renderer=None)
494 def repo_files(self):
504 def repo_files(self):
495 c = self.load_default_context()
505 c = self.load_default_context()
496
506
497 view_name = getattr(self.request.matched_route, 'name', None)
507 view_name = getattr(self.request.matched_route, 'name', None)
498
508
499 c.annotate = view_name == 'repo_files:annotated'
509 c.annotate = view_name == 'repo_files:annotated'
500 # default is false, but .rst/.md files later are auto rendered, we can
510 # default is false, but .rst/.md files later are auto rendered, we can
501 # overwrite auto rendering by setting this GET flag
511 # overwrite auto rendering by setting this GET flag
502 c.renderer = view_name == 'repo_files:rendered' or \
512 c.renderer = view_name == 'repo_files:rendered' or \
503 not self.request.GET.get('no-render', False)
513 not self.request.GET.get('no-render', False)
504
514
505 # redirect to given commit_id from form if given
515 # redirect to given commit_id from form if given
506 get_commit_id = self.request.GET.get('at_rev', None)
516 get_commit_id = self.request.GET.get('at_rev', None)
507 if get_commit_id:
517 if get_commit_id:
508 self._get_commit_or_redirect(get_commit_id)
518 self._get_commit_or_redirect(get_commit_id)
509
519
510 commit_id, f_path = self._get_commit_and_path()
520 commit_id, f_path = self._get_commit_and_path()
511 c.commit = self._get_commit_or_redirect(commit_id)
521 c.commit = self._get_commit_or_redirect(commit_id)
512 c.branch = self.request.GET.get('branch', None)
522 c.branch = self.request.GET.get('branch', None)
513 c.f_path = f_path
523 c.f_path = f_path
514
524
515 # prev link
525 # prev link
516 try:
526 try:
517 prev_commit = c.commit.prev(c.branch)
527 prev_commit = c.commit.prev(c.branch)
518 c.prev_commit = prev_commit
528 c.prev_commit = prev_commit
519 c.url_prev = h.route_path(
529 c.url_prev = h.route_path(
520 'repo_files', repo_name=self.db_repo_name,
530 'repo_files', repo_name=self.db_repo_name,
521 commit_id=prev_commit.raw_id, f_path=f_path)
531 commit_id=prev_commit.raw_id, f_path=f_path)
522 if c.branch:
532 if c.branch:
523 c.url_prev += '?branch=%s' % c.branch
533 c.url_prev += '?branch=%s' % c.branch
524 except (CommitDoesNotExistError, VCSError):
534 except (CommitDoesNotExistError, VCSError):
525 c.url_prev = '#'
535 c.url_prev = '#'
526 c.prev_commit = EmptyCommit()
536 c.prev_commit = EmptyCommit()
527
537
528 # next link
538 # next link
529 try:
539 try:
530 next_commit = c.commit.next(c.branch)
540 next_commit = c.commit.next(c.branch)
531 c.next_commit = next_commit
541 c.next_commit = next_commit
532 c.url_next = h.route_path(
542 c.url_next = h.route_path(
533 'repo_files', repo_name=self.db_repo_name,
543 'repo_files', repo_name=self.db_repo_name,
534 commit_id=next_commit.raw_id, f_path=f_path)
544 commit_id=next_commit.raw_id, f_path=f_path)
535 if c.branch:
545 if c.branch:
536 c.url_next += '?branch=%s' % c.branch
546 c.url_next += '?branch=%s' % c.branch
537 except (CommitDoesNotExistError, VCSError):
547 except (CommitDoesNotExistError, VCSError):
538 c.url_next = '#'
548 c.url_next = '#'
539 c.next_commit = EmptyCommit()
549 c.next_commit = EmptyCommit()
540
550
541 # files or dirs
551 # files or dirs
542 try:
552 try:
543 c.file = c.commit.get_node(f_path)
553 c.file = c.commit.get_node(f_path)
544 c.file_author = True
554 c.file_author = True
545 c.file_tree = ''
555 c.file_tree = ''
546
556
547 # load file content
557 # load file content
548 if c.file.is_file():
558 if c.file.is_file():
549 c.lf_node = c.file.get_largefile_node()
559 c.lf_node = c.file.get_largefile_node()
550
560
551 c.file_source_page = 'true'
561 c.file_source_page = 'true'
552 c.file_last_commit = c.file.last_commit
562 c.file_last_commit = c.file.last_commit
553 if c.file.size < c.visual.cut_off_limit_diff:
563 if c.file.size < c.visual.cut_off_limit_diff:
554 if c.annotate: # annotation has precedence over renderer
564 if c.annotate: # annotation has precedence over renderer
555 c.annotated_lines = filenode_as_annotated_lines_tokens(
565 c.annotated_lines = filenode_as_annotated_lines_tokens(
556 c.file
566 c.file
557 )
567 )
558 else:
568 else:
559 c.renderer = (
569 c.renderer = (
560 c.renderer and h.renderer_from_filename(c.file.path)
570 c.renderer and h.renderer_from_filename(c.file.path)
561 )
571 )
562 if not c.renderer:
572 if not c.renderer:
563 c.lines = filenode_as_lines_tokens(c.file)
573 c.lines = filenode_as_lines_tokens(c.file)
564
574
565 c.on_branch_head = self._is_valid_head(
575 c.on_branch_head = self._is_valid_head(
566 commit_id, self.rhodecode_vcs_repo)
576 commit_id, self.rhodecode_vcs_repo)
567
577
568 branch = c.commit.branch if (
578 branch = c.commit.branch if (
569 c.commit.branch and '/' not in c.commit.branch) else None
579 c.commit.branch and '/' not in c.commit.branch) else None
570 c.branch_or_raw_id = branch or c.commit.raw_id
580 c.branch_or_raw_id = branch or c.commit.raw_id
571 c.branch_name = c.commit.branch or h.short_id(c.commit.raw_id)
581 c.branch_name = c.commit.branch or h.short_id(c.commit.raw_id)
572
582
573 author = c.file_last_commit.author
583 author = c.file_last_commit.author
574 c.authors = [[
584 c.authors = [[
575 h.email(author),
585 h.email(author),
576 h.person(author, 'username_or_name_or_email'),
586 h.person(author, 'username_or_name_or_email'),
577 1
587 1
578 ]]
588 ]]
579
589
580 else: # load tree content at path
590 else: # load tree content at path
581 c.file_source_page = 'false'
591 c.file_source_page = 'false'
582 c.authors = []
592 c.authors = []
583 # this loads a simple tree without metadata to speed things up
593 # this loads a simple tree without metadata to speed things up
584 # later via ajax we call repo_nodetree_full and fetch whole
594 # later via ajax we call repo_nodetree_full and fetch whole
585 c.file_tree = self._get_tree_at_commit(
595 c.file_tree = self._get_tree_at_commit(
586 c, c.commit.raw_id, f_path)
596 c, c.commit.raw_id, f_path)
587
597
588 except RepositoryError as e:
598 except RepositoryError as e:
589 h.flash(safe_str(h.escape(e)), category='error')
599 h.flash(safe_str(h.escape(e)), category='error')
590 raise HTTPNotFound()
600 raise HTTPNotFound()
591
601
592 if self.request.environ.get('HTTP_X_PJAX'):
602 if self.request.environ.get('HTTP_X_PJAX'):
593 html = render('rhodecode:templates/files/files_pjax.mako',
603 html = render('rhodecode:templates/files/files_pjax.mako',
594 self._get_template_context(c), self.request)
604 self._get_template_context(c), self.request)
595 else:
605 else:
596 html = render('rhodecode:templates/files/files.mako',
606 html = render('rhodecode:templates/files/files.mako',
597 self._get_template_context(c), self.request)
607 self._get_template_context(c), self.request)
598 return Response(html)
608 return Response(html)
599
609
600 @HasRepoPermissionAnyDecorator(
610 @HasRepoPermissionAnyDecorator(
601 'repository.read', 'repository.write', 'repository.admin')
611 'repository.read', 'repository.write', 'repository.admin')
602 @view_config(
612 @view_config(
603 route_name='repo_files:annotated_previous', request_method='GET',
613 route_name='repo_files:annotated_previous', request_method='GET',
604 renderer=None)
614 renderer=None)
605 def repo_files_annotated_previous(self):
615 def repo_files_annotated_previous(self):
606 self.load_default_context()
616 self.load_default_context()
607
617
608 commit_id, f_path = self._get_commit_and_path()
618 commit_id, f_path = self._get_commit_and_path()
609 commit = self._get_commit_or_redirect(commit_id)
619 commit = self._get_commit_or_redirect(commit_id)
610 prev_commit_id = commit.raw_id
620 prev_commit_id = commit.raw_id
611 line_anchor = self.request.GET.get('line_anchor')
621 line_anchor = self.request.GET.get('line_anchor')
612 is_file = False
622 is_file = False
613 try:
623 try:
614 _file = commit.get_node(f_path)
624 _file = commit.get_node(f_path)
615 is_file = _file.is_file()
625 is_file = _file.is_file()
616 except (NodeDoesNotExistError, CommitDoesNotExistError, VCSError):
626 except (NodeDoesNotExistError, CommitDoesNotExistError, VCSError):
617 pass
627 pass
618
628
619 if is_file:
629 if is_file:
620 history = commit.get_file_history(f_path)
630 history = commit.get_file_history(f_path)
621 prev_commit_id = history[1].raw_id \
631 prev_commit_id = history[1].raw_id \
622 if len(history) > 1 else prev_commit_id
632 if len(history) > 1 else prev_commit_id
623 prev_url = h.route_path(
633 prev_url = h.route_path(
624 'repo_files:annotated', repo_name=self.db_repo_name,
634 'repo_files:annotated', repo_name=self.db_repo_name,
625 commit_id=prev_commit_id, f_path=f_path,
635 commit_id=prev_commit_id, f_path=f_path,
626 _anchor='L{}'.format(line_anchor))
636 _anchor='L{}'.format(line_anchor))
627
637
628 raise HTTPFound(prev_url)
638 raise HTTPFound(prev_url)
629
639
630 @LoginRequired()
640 @LoginRequired()
631 @HasRepoPermissionAnyDecorator(
641 @HasRepoPermissionAnyDecorator(
632 'repository.read', 'repository.write', 'repository.admin')
642 'repository.read', 'repository.write', 'repository.admin')
633 @view_config(
643 @view_config(
634 route_name='repo_nodetree_full', request_method='GET',
644 route_name='repo_nodetree_full', request_method='GET',
635 renderer=None, xhr=True)
645 renderer=None, xhr=True)
636 @view_config(
646 @view_config(
637 route_name='repo_nodetree_full:default_path', request_method='GET',
647 route_name='repo_nodetree_full:default_path', request_method='GET',
638 renderer=None, xhr=True)
648 renderer=None, xhr=True)
639 def repo_nodetree_full(self):
649 def repo_nodetree_full(self):
640 """
650 """
641 Returns rendered html of file tree that contains commit date,
651 Returns rendered html of file tree that contains commit date,
642 author, commit_id for the specified combination of
652 author, commit_id for the specified combination of
643 repo, commit_id and file path
653 repo, commit_id and file path
644 """
654 """
645 c = self.load_default_context()
655 c = self.load_default_context()
646
656
647 commit_id, f_path = self._get_commit_and_path()
657 commit_id, f_path = self._get_commit_and_path()
648 commit = self._get_commit_or_redirect(commit_id)
658 commit = self._get_commit_or_redirect(commit_id)
649 try:
659 try:
650 dir_node = commit.get_node(f_path)
660 dir_node = commit.get_node(f_path)
651 except RepositoryError as e:
661 except RepositoryError as e:
652 return Response('error: {}'.format(h.escape(safe_str(e))))
662 return Response('error: {}'.format(h.escape(safe_str(e))))
653
663
654 if dir_node.is_file():
664 if dir_node.is_file():
655 return Response('')
665 return Response('')
656
666
657 c.file = dir_node
667 c.file = dir_node
658 c.commit = commit
668 c.commit = commit
659
669
660 html = self._get_tree_at_commit(
670 html = self._get_tree_at_commit(
661 c, commit.raw_id, dir_node.path, full_load=True)
671 c, commit.raw_id, dir_node.path, full_load=True)
662
672
663 return Response(html)
673 return Response(html)
664
674
665 def _get_attachement_disposition(self, f_path):
675 def _get_attachement_disposition(self, f_path):
666 return 'attachment; filename=%s' % \
676 return 'attachment; filename=%s' % \
667 safe_str(f_path.split(Repository.NAME_SEP)[-1])
677 safe_str(f_path.split(Repository.NAME_SEP)[-1])
668
678
669 @LoginRequired()
679 @LoginRequired()
670 @HasRepoPermissionAnyDecorator(
680 @HasRepoPermissionAnyDecorator(
671 'repository.read', 'repository.write', 'repository.admin')
681 'repository.read', 'repository.write', 'repository.admin')
672 @view_config(
682 @view_config(
673 route_name='repo_file_raw', request_method='GET',
683 route_name='repo_file_raw', request_method='GET',
674 renderer=None)
684 renderer=None)
675 def repo_file_raw(self):
685 def repo_file_raw(self):
676 """
686 """
677 Action for show as raw, some mimetypes are "rendered",
687 Action for show as raw, some mimetypes are "rendered",
678 those include images, icons.
688 those include images, icons.
679 """
689 """
680 c = self.load_default_context()
690 c = self.load_default_context()
681
691
682 commit_id, f_path = self._get_commit_and_path()
692 commit_id, f_path = self._get_commit_and_path()
683 commit = self._get_commit_or_redirect(commit_id)
693 commit = self._get_commit_or_redirect(commit_id)
684 file_node = self._get_filenode_or_redirect(commit, f_path)
694 file_node = self._get_filenode_or_redirect(commit, f_path)
685
695
686 raw_mimetype_mapping = {
696 raw_mimetype_mapping = {
687 # map original mimetype to a mimetype used for "show as raw"
697 # map original mimetype to a mimetype used for "show as raw"
688 # you can also provide a content-disposition to override the
698 # you can also provide a content-disposition to override the
689 # default "attachment" disposition.
699 # default "attachment" disposition.
690 # orig_type: (new_type, new_dispo)
700 # orig_type: (new_type, new_dispo)
691
701
692 # show images inline:
702 # show images inline:
693 # Do not re-add SVG: it is unsafe and permits XSS attacks. One can
703 # Do not re-add SVG: it is unsafe and permits XSS attacks. One can
694 # for example render an SVG with javascript inside or even render
704 # for example render an SVG with javascript inside or even render
695 # HTML.
705 # HTML.
696 'image/x-icon': ('image/x-icon', 'inline'),
706 'image/x-icon': ('image/x-icon', 'inline'),
697 'image/png': ('image/png', 'inline'),
707 'image/png': ('image/png', 'inline'),
698 'image/gif': ('image/gif', 'inline'),
708 'image/gif': ('image/gif', 'inline'),
699 'image/jpeg': ('image/jpeg', 'inline'),
709 'image/jpeg': ('image/jpeg', 'inline'),
700 'application/pdf': ('application/pdf', 'inline'),
710 'application/pdf': ('application/pdf', 'inline'),
701 }
711 }
702
712
703 mimetype = file_node.mimetype
713 mimetype = file_node.mimetype
704 try:
714 try:
705 mimetype, disposition = raw_mimetype_mapping[mimetype]
715 mimetype, disposition = raw_mimetype_mapping[mimetype]
706 except KeyError:
716 except KeyError:
707 # we don't know anything special about this, handle it safely
717 # we don't know anything special about this, handle it safely
708 if file_node.is_binary:
718 if file_node.is_binary:
709 # do same as download raw for binary files
719 # do same as download raw for binary files
710 mimetype, disposition = 'application/octet-stream', 'attachment'
720 mimetype, disposition = 'application/octet-stream', 'attachment'
711 else:
721 else:
712 # do not just use the original mimetype, but force text/plain,
722 # do not just use the original mimetype, but force text/plain,
713 # otherwise it would serve text/html and that might be unsafe.
723 # otherwise it would serve text/html and that might be unsafe.
714 # Note: underlying vcs library fakes text/plain mimetype if the
724 # Note: underlying vcs library fakes text/plain mimetype if the
715 # mimetype can not be determined and it thinks it is not
725 # mimetype can not be determined and it thinks it is not
716 # binary.This might lead to erroneous text display in some
726 # binary.This might lead to erroneous text display in some
717 # cases, but helps in other cases, like with text files
727 # cases, but helps in other cases, like with text files
718 # without extension.
728 # without extension.
719 mimetype, disposition = 'text/plain', 'inline'
729 mimetype, disposition = 'text/plain', 'inline'
720
730
721 if disposition == 'attachment':
731 if disposition == 'attachment':
722 disposition = self._get_attachement_disposition(f_path)
732 disposition = self._get_attachement_disposition(f_path)
723
733
724 def stream_node():
734 def stream_node():
725 yield file_node.raw_bytes
735 yield file_node.raw_bytes
726
736
727 response = Response(app_iter=stream_node())
737 response = Response(app_iter=stream_node())
728 response.content_disposition = disposition
738 response.content_disposition = disposition
729 response.content_type = mimetype
739 response.content_type = mimetype
730
740
731 charset = self._get_default_encoding(c)
741 charset = self._get_default_encoding(c)
732 if charset:
742 if charset:
733 response.charset = charset
743 response.charset = charset
734
744
735 return response
745 return response
736
746
737 @LoginRequired()
747 @LoginRequired()
738 @HasRepoPermissionAnyDecorator(
748 @HasRepoPermissionAnyDecorator(
739 'repository.read', 'repository.write', 'repository.admin')
749 'repository.read', 'repository.write', 'repository.admin')
740 @view_config(
750 @view_config(
741 route_name='repo_file_download', request_method='GET',
751 route_name='repo_file_download', request_method='GET',
742 renderer=None)
752 renderer=None)
743 @view_config(
753 @view_config(
744 route_name='repo_file_download:legacy', request_method='GET',
754 route_name='repo_file_download:legacy', request_method='GET',
745 renderer=None)
755 renderer=None)
746 def repo_file_download(self):
756 def repo_file_download(self):
747 c = self.load_default_context()
757 c = self.load_default_context()
748
758
749 commit_id, f_path = self._get_commit_and_path()
759 commit_id, f_path = self._get_commit_and_path()
750 commit = self._get_commit_or_redirect(commit_id)
760 commit = self._get_commit_or_redirect(commit_id)
751 file_node = self._get_filenode_or_redirect(commit, f_path)
761 file_node = self._get_filenode_or_redirect(commit, f_path)
752
762
753 if self.request.GET.get('lf'):
763 if self.request.GET.get('lf'):
754 # only if lf get flag is passed, we download this file
764 # only if lf get flag is passed, we download this file
755 # as LFS/Largefile
765 # as LFS/Largefile
756 lf_node = file_node.get_largefile_node()
766 lf_node = file_node.get_largefile_node()
757 if lf_node:
767 if lf_node:
758 # overwrite our pointer with the REAL large-file
768 # overwrite our pointer with the REAL large-file
759 file_node = lf_node
769 file_node = lf_node
760
770
761 disposition = self._get_attachement_disposition(f_path)
771 disposition = self._get_attachement_disposition(f_path)
762
772
763 def stream_node():
773 def stream_node():
764 yield file_node.raw_bytes
774 yield file_node.raw_bytes
765
775
766 response = Response(app_iter=stream_node())
776 response = Response(app_iter=stream_node())
767 response.content_disposition = disposition
777 response.content_disposition = disposition
768 response.content_type = file_node.mimetype
778 response.content_type = file_node.mimetype
769
779
770 charset = self._get_default_encoding(c)
780 charset = self._get_default_encoding(c)
771 if charset:
781 if charset:
772 response.charset = charset
782 response.charset = charset
773
783
774 return response
784 return response
775
785
776 def _get_nodelist_at_commit(self, repo_name, repo_id, commit_id, f_path):
786 def _get_nodelist_at_commit(self, repo_name, repo_id, commit_id, f_path):
777
787
788 cache_seconds = safe_int(
789 rhodecode.CONFIG.get('rc_cache.cache_repo.expiration_time'))
790 cache_on = cache_seconds > 0
791 log.debug(
792 'Computing FILE SEARCH for repo_id %s commit_id `%s` and path `%s`'
793 'with caching: %s[TTL: %ss]' % (
794 repo_id, commit_id, f_path, cache_on, cache_seconds or 0))
795
778 cache_namespace_uid = 'cache_repo.{}'.format(repo_id)
796 cache_namespace_uid = 'cache_repo.{}'.format(repo_id)
779 region = rc_cache.get_or_create_region('cache_repo', cache_namespace_uid)
797 region = rc_cache.get_or_create_region('cache_repo', cache_namespace_uid)
780
798
781 @region.cache_on_arguments(namespace=cache_namespace_uid)
799 @region.cache_on_arguments(namespace=cache_namespace_uid,
800 should_cache_fn=lambda v: cache_on)
782 def compute_file_search(repo_id, commit_id, f_path):
801 def compute_file_search(repo_id, commit_id, f_path):
783 log.debug('Generating cached nodelist for repo_id:%s, %s, %s',
802 log.debug('Generating cached nodelist for repo_id:%s, %s, %s',
784 repo_id, commit_id, f_path)
803 repo_id, commit_id, f_path)
785 try:
804 try:
786 _d, _f = ScmModel().get_nodes(
805 _d, _f = ScmModel().get_nodes(
787 repo_name, commit_id, f_path, flat=False)
806 repo_name, commit_id, f_path, flat=False)
788 except (RepositoryError, CommitDoesNotExistError, Exception) as e:
807 except (RepositoryError, CommitDoesNotExistError, Exception) as e:
789 log.exception(safe_str(e))
808 log.exception(safe_str(e))
790 h.flash(safe_str(h.escape(e)), category='error')
809 h.flash(safe_str(h.escape(e)), category='error')
791 raise HTTPFound(h.route_path(
810 raise HTTPFound(h.route_path(
792 'repo_files', repo_name=self.db_repo_name,
811 'repo_files', repo_name=self.db_repo_name,
793 commit_id='tip', f_path='/'))
812 commit_id='tip', f_path='/'))
794 return _d + _f
813 return _d + _f
795
814
796 return compute_file_search(self.db_repo.repo_id, commit_id, f_path)
815 return compute_file_search(self.db_repo.repo_id, commit_id, f_path)
797
816
798 @LoginRequired()
817 @LoginRequired()
799 @HasRepoPermissionAnyDecorator(
818 @HasRepoPermissionAnyDecorator(
800 'repository.read', 'repository.write', 'repository.admin')
819 'repository.read', 'repository.write', 'repository.admin')
801 @view_config(
820 @view_config(
802 route_name='repo_files_nodelist', request_method='GET',
821 route_name='repo_files_nodelist', request_method='GET',
803 renderer='json_ext', xhr=True)
822 renderer='json_ext', xhr=True)
804 def repo_nodelist(self):
823 def repo_nodelist(self):
805 self.load_default_context()
824 self.load_default_context()
806
825
807 commit_id, f_path = self._get_commit_and_path()
826 commit_id, f_path = self._get_commit_and_path()
808 commit = self._get_commit_or_redirect(commit_id)
827 commit = self._get_commit_or_redirect(commit_id)
809
828
810 metadata = self._get_nodelist_at_commit(
829 metadata = self._get_nodelist_at_commit(
811 self.db_repo_name, self.db_repo.repo_id, commit.raw_id, f_path)
830 self.db_repo_name, self.db_repo.repo_id, commit.raw_id, f_path)
812 return {'nodes': metadata}
831 return {'nodes': metadata}
813
832
814 def _create_references(
833 def _create_references(
815 self, branches_or_tags, symbolic_reference, f_path):
834 self, branches_or_tags, symbolic_reference, f_path):
816 items = []
835 items = []
817 for name, commit_id in branches_or_tags.items():
836 for name, commit_id in branches_or_tags.items():
818 sym_ref = symbolic_reference(commit_id, name, f_path)
837 sym_ref = symbolic_reference(commit_id, name, f_path)
819 items.append((sym_ref, name))
838 items.append((sym_ref, name))
820 return items
839 return items
821
840
822 def _symbolic_reference(self, commit_id, name, f_path):
841 def _symbolic_reference(self, commit_id, name, f_path):
823 return commit_id
842 return commit_id
824
843
825 def _symbolic_reference_svn(self, commit_id, name, f_path):
844 def _symbolic_reference_svn(self, commit_id, name, f_path):
826 new_f_path = vcspath.join(name, f_path)
845 new_f_path = vcspath.join(name, f_path)
827 return u'%s@%s' % (new_f_path, commit_id)
846 return u'%s@%s' % (new_f_path, commit_id)
828
847
829 def _get_node_history(self, commit_obj, f_path, commits=None):
848 def _get_node_history(self, commit_obj, f_path, commits=None):
830 """
849 """
831 get commit history for given node
850 get commit history for given node
832
851
833 :param commit_obj: commit to calculate history
852 :param commit_obj: commit to calculate history
834 :param f_path: path for node to calculate history for
853 :param f_path: path for node to calculate history for
835 :param commits: if passed don't calculate history and take
854 :param commits: if passed don't calculate history and take
836 commits defined in this list
855 commits defined in this list
837 """
856 """
838 _ = self.request.translate
857 _ = self.request.translate
839
858
840 # calculate history based on tip
859 # calculate history based on tip
841 tip = self.rhodecode_vcs_repo.get_commit()
860 tip = self.rhodecode_vcs_repo.get_commit()
842 if commits is None:
861 if commits is None:
843 pre_load = ["author", "branch"]
862 pre_load = ["author", "branch"]
844 try:
863 try:
845 commits = tip.get_file_history(f_path, pre_load=pre_load)
864 commits = tip.get_file_history(f_path, pre_load=pre_load)
846 except (NodeDoesNotExistError, CommitError):
865 except (NodeDoesNotExistError, CommitError):
847 # this node is not present at tip!
866 # this node is not present at tip!
848 commits = commit_obj.get_file_history(f_path, pre_load=pre_load)
867 commits = commit_obj.get_file_history(f_path, pre_load=pre_load)
849
868
850 history = []
869 history = []
851 commits_group = ([], _("Changesets"))
870 commits_group = ([], _("Changesets"))
852 for commit in commits:
871 for commit in commits:
853 branch = ' (%s)' % commit.branch if commit.branch else ''
872 branch = ' (%s)' % commit.branch if commit.branch else ''
854 n_desc = 'r%s:%s%s' % (commit.idx, commit.short_id, branch)
873 n_desc = 'r%s:%s%s' % (commit.idx, commit.short_id, branch)
855 commits_group[0].append((commit.raw_id, n_desc,))
874 commits_group[0].append((commit.raw_id, n_desc,))
856 history.append(commits_group)
875 history.append(commits_group)
857
876
858 symbolic_reference = self._symbolic_reference
877 symbolic_reference = self._symbolic_reference
859
878
860 if self.rhodecode_vcs_repo.alias == 'svn':
879 if self.rhodecode_vcs_repo.alias == 'svn':
861 adjusted_f_path = RepoFilesView.adjust_file_path_for_svn(
880 adjusted_f_path = RepoFilesView.adjust_file_path_for_svn(
862 f_path, self.rhodecode_vcs_repo)
881 f_path, self.rhodecode_vcs_repo)
863 if adjusted_f_path != f_path:
882 if adjusted_f_path != f_path:
864 log.debug(
883 log.debug(
865 'Recognized svn tag or branch in file "%s", using svn '
884 'Recognized svn tag or branch in file "%s", using svn '
866 'specific symbolic references', f_path)
885 'specific symbolic references', f_path)
867 f_path = adjusted_f_path
886 f_path = adjusted_f_path
868 symbolic_reference = self._symbolic_reference_svn
887 symbolic_reference = self._symbolic_reference_svn
869
888
870 branches = self._create_references(
889 branches = self._create_references(
871 self.rhodecode_vcs_repo.branches, symbolic_reference, f_path)
890 self.rhodecode_vcs_repo.branches, symbolic_reference, f_path)
872 branches_group = (branches, _("Branches"))
891 branches_group = (branches, _("Branches"))
873
892
874 tags = self._create_references(
893 tags = self._create_references(
875 self.rhodecode_vcs_repo.tags, symbolic_reference, f_path)
894 self.rhodecode_vcs_repo.tags, symbolic_reference, f_path)
876 tags_group = (tags, _("Tags"))
895 tags_group = (tags, _("Tags"))
877
896
878 history.append(branches_group)
897 history.append(branches_group)
879 history.append(tags_group)
898 history.append(tags_group)
880
899
881 return history, commits
900 return history, commits
882
901
883 @LoginRequired()
902 @LoginRequired()
884 @HasRepoPermissionAnyDecorator(
903 @HasRepoPermissionAnyDecorator(
885 'repository.read', 'repository.write', 'repository.admin')
904 'repository.read', 'repository.write', 'repository.admin')
886 @view_config(
905 @view_config(
887 route_name='repo_file_history', request_method='GET',
906 route_name='repo_file_history', request_method='GET',
888 renderer='json_ext')
907 renderer='json_ext')
889 def repo_file_history(self):
908 def repo_file_history(self):
890 self.load_default_context()
909 self.load_default_context()
891
910
892 commit_id, f_path = self._get_commit_and_path()
911 commit_id, f_path = self._get_commit_and_path()
893 commit = self._get_commit_or_redirect(commit_id)
912 commit = self._get_commit_or_redirect(commit_id)
894 file_node = self._get_filenode_or_redirect(commit, f_path)
913 file_node = self._get_filenode_or_redirect(commit, f_path)
895
914
896 if file_node.is_file():
915 if file_node.is_file():
897 file_history, _hist = self._get_node_history(commit, f_path)
916 file_history, _hist = self._get_node_history(commit, f_path)
898
917
899 res = []
918 res = []
900 for obj in file_history:
919 for obj in file_history:
901 res.append({
920 res.append({
902 'text': obj[1],
921 'text': obj[1],
903 'children': [{'id': o[0], 'text': o[1]} for o in obj[0]]
922 'children': [{'id': o[0], 'text': o[1]} for o in obj[0]]
904 })
923 })
905
924
906 data = {
925 data = {
907 'more': False,
926 'more': False,
908 'results': res
927 'results': res
909 }
928 }
910 return data
929 return data
911
930
912 log.warning('Cannot fetch history for directory')
931 log.warning('Cannot fetch history for directory')
913 raise HTTPBadRequest()
932 raise HTTPBadRequest()
914
933
915 @LoginRequired()
934 @LoginRequired()
916 @HasRepoPermissionAnyDecorator(
935 @HasRepoPermissionAnyDecorator(
917 'repository.read', 'repository.write', 'repository.admin')
936 'repository.read', 'repository.write', 'repository.admin')
918 @view_config(
937 @view_config(
919 route_name='repo_file_authors', request_method='GET',
938 route_name='repo_file_authors', request_method='GET',
920 renderer='rhodecode:templates/files/file_authors_box.mako')
939 renderer='rhodecode:templates/files/file_authors_box.mako')
921 def repo_file_authors(self):
940 def repo_file_authors(self):
922 c = self.load_default_context()
941 c = self.load_default_context()
923
942
924 commit_id, f_path = self._get_commit_and_path()
943 commit_id, f_path = self._get_commit_and_path()
925 commit = self._get_commit_or_redirect(commit_id)
944 commit = self._get_commit_or_redirect(commit_id)
926 file_node = self._get_filenode_or_redirect(commit, f_path)
945 file_node = self._get_filenode_or_redirect(commit, f_path)
927
946
928 if not file_node.is_file():
947 if not file_node.is_file():
929 raise HTTPBadRequest()
948 raise HTTPBadRequest()
930
949
931 c.file_last_commit = file_node.last_commit
950 c.file_last_commit = file_node.last_commit
932 if self.request.GET.get('annotate') == '1':
951 if self.request.GET.get('annotate') == '1':
933 # use _hist from annotation if annotation mode is on
952 # use _hist from annotation if annotation mode is on
934 commit_ids = set(x[1] for x in file_node.annotate)
953 commit_ids = set(x[1] for x in file_node.annotate)
935 _hist = (
954 _hist = (
936 self.rhodecode_vcs_repo.get_commit(commit_id)
955 self.rhodecode_vcs_repo.get_commit(commit_id)
937 for commit_id in commit_ids)
956 for commit_id in commit_ids)
938 else:
957 else:
939 _f_history, _hist = self._get_node_history(commit, f_path)
958 _f_history, _hist = self._get_node_history(commit, f_path)
940 c.file_author = False
959 c.file_author = False
941
960
942 unique = collections.OrderedDict()
961 unique = collections.OrderedDict()
943 for commit in _hist:
962 for commit in _hist:
944 author = commit.author
963 author = commit.author
945 if author not in unique:
964 if author not in unique:
946 unique[commit.author] = [
965 unique[commit.author] = [
947 h.email(author),
966 h.email(author),
948 h.person(author, 'username_or_name_or_email'),
967 h.person(author, 'username_or_name_or_email'),
949 1 # counter
968 1 # counter
950 ]
969 ]
951
970
952 else:
971 else:
953 # increase counter
972 # increase counter
954 unique[commit.author][2] += 1
973 unique[commit.author][2] += 1
955
974
956 c.authors = [val for val in unique.values()]
975 c.authors = [val for val in unique.values()]
957
976
958 return self._get_template_context(c)
977 return self._get_template_context(c)
959
978
960 @LoginRequired()
979 @LoginRequired()
961 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
980 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
962 @view_config(
981 @view_config(
963 route_name='repo_files_remove_file', request_method='GET',
982 route_name='repo_files_remove_file', request_method='GET',
964 renderer='rhodecode:templates/files/files_delete.mako')
983 renderer='rhodecode:templates/files/files_delete.mako')
965 def repo_files_remove_file(self):
984 def repo_files_remove_file(self):
966 _ = self.request.translate
985 _ = self.request.translate
967 c = self.load_default_context()
986 c = self.load_default_context()
968 commit_id, f_path = self._get_commit_and_path()
987 commit_id, f_path = self._get_commit_and_path()
969
988
970 self._ensure_not_locked()
989 self._ensure_not_locked()
971
990
972 if not self._is_valid_head(commit_id, self.rhodecode_vcs_repo):
991 if not self._is_valid_head(commit_id, self.rhodecode_vcs_repo):
973 h.flash(_('You can only delete files with commit '
992 h.flash(_('You can only delete files with commit '
974 'being a valid branch '), category='warning')
993 'being a valid branch '), category='warning')
975 raise HTTPFound(
994 raise HTTPFound(
976 h.route_path('repo_files',
995 h.route_path('repo_files',
977 repo_name=self.db_repo_name, commit_id='tip',
996 repo_name=self.db_repo_name, commit_id='tip',
978 f_path=f_path))
997 f_path=f_path))
979
998
980 c.commit = self._get_commit_or_redirect(commit_id)
999 c.commit = self._get_commit_or_redirect(commit_id)
981 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1000 c.file = self._get_filenode_or_redirect(c.commit, f_path)
982
1001
983 c.default_message = _(
1002 c.default_message = _(
984 'Deleted file {} via RhodeCode Enterprise').format(f_path)
1003 'Deleted file {} via RhodeCode Enterprise').format(f_path)
985 c.f_path = f_path
1004 c.f_path = f_path
986
1005
987 return self._get_template_context(c)
1006 return self._get_template_context(c)
988
1007
989 @LoginRequired()
1008 @LoginRequired()
990 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1009 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
991 @CSRFRequired()
1010 @CSRFRequired()
992 @view_config(
1011 @view_config(
993 route_name='repo_files_delete_file', request_method='POST',
1012 route_name='repo_files_delete_file', request_method='POST',
994 renderer=None)
1013 renderer=None)
995 def repo_files_delete_file(self):
1014 def repo_files_delete_file(self):
996 _ = self.request.translate
1015 _ = self.request.translate
997
1016
998 c = self.load_default_context()
1017 c = self.load_default_context()
999 commit_id, f_path = self._get_commit_and_path()
1018 commit_id, f_path = self._get_commit_and_path()
1000
1019
1001 self._ensure_not_locked()
1020 self._ensure_not_locked()
1002
1021
1003 if not self._is_valid_head(commit_id, self.rhodecode_vcs_repo):
1022 if not self._is_valid_head(commit_id, self.rhodecode_vcs_repo):
1004 h.flash(_('You can only delete files with commit '
1023 h.flash(_('You can only delete files with commit '
1005 'being a valid branch '), category='warning')
1024 'being a valid branch '), category='warning')
1006 raise HTTPFound(
1025 raise HTTPFound(
1007 h.route_path('repo_files',
1026 h.route_path('repo_files',
1008 repo_name=self.db_repo_name, commit_id='tip',
1027 repo_name=self.db_repo_name, commit_id='tip',
1009 f_path=f_path))
1028 f_path=f_path))
1010
1029
1011 c.commit = self._get_commit_or_redirect(commit_id)
1030 c.commit = self._get_commit_or_redirect(commit_id)
1012 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1031 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1013
1032
1014 c.default_message = _(
1033 c.default_message = _(
1015 'Deleted file {} via RhodeCode Enterprise').format(f_path)
1034 'Deleted file {} via RhodeCode Enterprise').format(f_path)
1016 c.f_path = f_path
1035 c.f_path = f_path
1017 node_path = f_path
1036 node_path = f_path
1018 author = self._rhodecode_db_user.full_contact
1037 author = self._rhodecode_db_user.full_contact
1019 message = self.request.POST.get('message') or c.default_message
1038 message = self.request.POST.get('message') or c.default_message
1020 try:
1039 try:
1021 nodes = {
1040 nodes = {
1022 node_path: {
1041 node_path: {
1023 'content': ''
1042 'content': ''
1024 }
1043 }
1025 }
1044 }
1026 ScmModel().delete_nodes(
1045 ScmModel().delete_nodes(
1027 user=self._rhodecode_db_user.user_id, repo=self.db_repo,
1046 user=self._rhodecode_db_user.user_id, repo=self.db_repo,
1028 message=message,
1047 message=message,
1029 nodes=nodes,
1048 nodes=nodes,
1030 parent_commit=c.commit,
1049 parent_commit=c.commit,
1031 author=author,
1050 author=author,
1032 )
1051 )
1033
1052
1034 h.flash(
1053 h.flash(
1035 _('Successfully deleted file `{}`').format(
1054 _('Successfully deleted file `{}`').format(
1036 h.escape(f_path)), category='success')
1055 h.escape(f_path)), category='success')
1037 except Exception:
1056 except Exception:
1038 log.exception('Error during commit operation')
1057 log.exception('Error during commit operation')
1039 h.flash(_('Error occurred during commit'), category='error')
1058 h.flash(_('Error occurred during commit'), category='error')
1040 raise HTTPFound(
1059 raise HTTPFound(
1041 h.route_path('repo_commit', repo_name=self.db_repo_name,
1060 h.route_path('repo_commit', repo_name=self.db_repo_name,
1042 commit_id='tip'))
1061 commit_id='tip'))
1043
1062
1044 @LoginRequired()
1063 @LoginRequired()
1045 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1064 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1046 @view_config(
1065 @view_config(
1047 route_name='repo_files_edit_file', request_method='GET',
1066 route_name='repo_files_edit_file', request_method='GET',
1048 renderer='rhodecode:templates/files/files_edit.mako')
1067 renderer='rhodecode:templates/files/files_edit.mako')
1049 def repo_files_edit_file(self):
1068 def repo_files_edit_file(self):
1050 _ = self.request.translate
1069 _ = self.request.translate
1051 c = self.load_default_context()
1070 c = self.load_default_context()
1052 commit_id, f_path = self._get_commit_and_path()
1071 commit_id, f_path = self._get_commit_and_path()
1053
1072
1054 self._ensure_not_locked()
1073 self._ensure_not_locked()
1055
1074
1056 if not self._is_valid_head(commit_id, self.rhodecode_vcs_repo):
1075 if not self._is_valid_head(commit_id, self.rhodecode_vcs_repo):
1057 h.flash(_('You can only edit files with commit '
1076 h.flash(_('You can only edit files with commit '
1058 'being a valid branch '), category='warning')
1077 'being a valid branch '), category='warning')
1059 raise HTTPFound(
1078 raise HTTPFound(
1060 h.route_path('repo_files',
1079 h.route_path('repo_files',
1061 repo_name=self.db_repo_name, commit_id='tip',
1080 repo_name=self.db_repo_name, commit_id='tip',
1062 f_path=f_path))
1081 f_path=f_path))
1063
1082
1064 c.commit = self._get_commit_or_redirect(commit_id)
1083 c.commit = self._get_commit_or_redirect(commit_id)
1065 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1084 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1066
1085
1067 if c.file.is_binary:
1086 if c.file.is_binary:
1068 files_url = h.route_path(
1087 files_url = h.route_path(
1069 'repo_files',
1088 'repo_files',
1070 repo_name=self.db_repo_name,
1089 repo_name=self.db_repo_name,
1071 commit_id=c.commit.raw_id, f_path=f_path)
1090 commit_id=c.commit.raw_id, f_path=f_path)
1072 raise HTTPFound(files_url)
1091 raise HTTPFound(files_url)
1073
1092
1074 c.default_message = _(
1093 c.default_message = _(
1075 'Edited file {} via RhodeCode Enterprise').format(f_path)
1094 'Edited file {} via RhodeCode Enterprise').format(f_path)
1076 c.f_path = f_path
1095 c.f_path = f_path
1077
1096
1078 return self._get_template_context(c)
1097 return self._get_template_context(c)
1079
1098
1080 @LoginRequired()
1099 @LoginRequired()
1081 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1100 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1082 @CSRFRequired()
1101 @CSRFRequired()
1083 @view_config(
1102 @view_config(
1084 route_name='repo_files_update_file', request_method='POST',
1103 route_name='repo_files_update_file', request_method='POST',
1085 renderer=None)
1104 renderer=None)
1086 def repo_files_update_file(self):
1105 def repo_files_update_file(self):
1087 _ = self.request.translate
1106 _ = self.request.translate
1088 c = self.load_default_context()
1107 c = self.load_default_context()
1089 commit_id, f_path = self._get_commit_and_path()
1108 commit_id, f_path = self._get_commit_and_path()
1090
1109
1091 self._ensure_not_locked()
1110 self._ensure_not_locked()
1092
1111
1093 if not self._is_valid_head(commit_id, self.rhodecode_vcs_repo):
1112 if not self._is_valid_head(commit_id, self.rhodecode_vcs_repo):
1094 h.flash(_('You can only edit files with commit '
1113 h.flash(_('You can only edit files with commit '
1095 'being a valid branch '), category='warning')
1114 'being a valid branch '), category='warning')
1096 raise HTTPFound(
1115 raise HTTPFound(
1097 h.route_path('repo_files',
1116 h.route_path('repo_files',
1098 repo_name=self.db_repo_name, commit_id='tip',
1117 repo_name=self.db_repo_name, commit_id='tip',
1099 f_path=f_path))
1118 f_path=f_path))
1100
1119
1101 c.commit = self._get_commit_or_redirect(commit_id)
1120 c.commit = self._get_commit_or_redirect(commit_id)
1102 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1121 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1103
1122
1104 if c.file.is_binary:
1123 if c.file.is_binary:
1105 raise HTTPFound(
1124 raise HTTPFound(
1106 h.route_path('repo_files',
1125 h.route_path('repo_files',
1107 repo_name=self.db_repo_name,
1126 repo_name=self.db_repo_name,
1108 commit_id=c.commit.raw_id,
1127 commit_id=c.commit.raw_id,
1109 f_path=f_path))
1128 f_path=f_path))
1110
1129
1111 c.default_message = _(
1130 c.default_message = _(
1112 'Edited file {} via RhodeCode Enterprise').format(f_path)
1131 'Edited file {} via RhodeCode Enterprise').format(f_path)
1113 c.f_path = f_path
1132 c.f_path = f_path
1114 old_content = c.file.content
1133 old_content = c.file.content
1115 sl = old_content.splitlines(1)
1134 sl = old_content.splitlines(1)
1116 first_line = sl[0] if sl else ''
1135 first_line = sl[0] if sl else ''
1117
1136
1118 r_post = self.request.POST
1137 r_post = self.request.POST
1119 # modes: 0 - Unix, 1 - Mac, 2 - DOS
1138 # modes: 0 - Unix, 1 - Mac, 2 - DOS
1120 mode = detect_mode(first_line, 0)
1139 mode = detect_mode(first_line, 0)
1121 content = convert_line_endings(r_post.get('content', ''), mode)
1140 content = convert_line_endings(r_post.get('content', ''), mode)
1122
1141
1123 message = r_post.get('message') or c.default_message
1142 message = r_post.get('message') or c.default_message
1124 org_f_path = c.file.unicode_path
1143 org_f_path = c.file.unicode_path
1125 filename = r_post['filename']
1144 filename = r_post['filename']
1126 org_filename = c.file.name
1145 org_filename = c.file.name
1127
1146
1128 if content == old_content and filename == org_filename:
1147 if content == old_content and filename == org_filename:
1129 h.flash(_('No changes'), category='warning')
1148 h.flash(_('No changes'), category='warning')
1130 raise HTTPFound(
1149 raise HTTPFound(
1131 h.route_path('repo_commit', repo_name=self.db_repo_name,
1150 h.route_path('repo_commit', repo_name=self.db_repo_name,
1132 commit_id='tip'))
1151 commit_id='tip'))
1133 try:
1152 try:
1134 mapping = {
1153 mapping = {
1135 org_f_path: {
1154 org_f_path: {
1136 'org_filename': org_f_path,
1155 'org_filename': org_f_path,
1137 'filename': os.path.join(c.file.dir_path, filename),
1156 'filename': os.path.join(c.file.dir_path, filename),
1138 'content': content,
1157 'content': content,
1139 'lexer': '',
1158 'lexer': '',
1140 'op': 'mod',
1159 'op': 'mod',
1141 }
1160 }
1142 }
1161 }
1143
1162
1144 ScmModel().update_nodes(
1163 ScmModel().update_nodes(
1145 user=self._rhodecode_db_user.user_id,
1164 user=self._rhodecode_db_user.user_id,
1146 repo=self.db_repo,
1165 repo=self.db_repo,
1147 message=message,
1166 message=message,
1148 nodes=mapping,
1167 nodes=mapping,
1149 parent_commit=c.commit,
1168 parent_commit=c.commit,
1150 )
1169 )
1151
1170
1152 h.flash(
1171 h.flash(
1153 _('Successfully committed changes to file `{}`').format(
1172 _('Successfully committed changes to file `{}`').format(
1154 h.escape(f_path)), category='success')
1173 h.escape(f_path)), category='success')
1155 except Exception:
1174 except Exception:
1156 log.exception('Error occurred during commit')
1175 log.exception('Error occurred during commit')
1157 h.flash(_('Error occurred during commit'), category='error')
1176 h.flash(_('Error occurred during commit'), category='error')
1158 raise HTTPFound(
1177 raise HTTPFound(
1159 h.route_path('repo_commit', repo_name=self.db_repo_name,
1178 h.route_path('repo_commit', repo_name=self.db_repo_name,
1160 commit_id='tip'))
1179 commit_id='tip'))
1161
1180
1162 @LoginRequired()
1181 @LoginRequired()
1163 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1182 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1164 @view_config(
1183 @view_config(
1165 route_name='repo_files_add_file', request_method='GET',
1184 route_name='repo_files_add_file', request_method='GET',
1166 renderer='rhodecode:templates/files/files_add.mako')
1185 renderer='rhodecode:templates/files/files_add.mako')
1167 def repo_files_add_file(self):
1186 def repo_files_add_file(self):
1168 _ = self.request.translate
1187 _ = self.request.translate
1169 c = self.load_default_context()
1188 c = self.load_default_context()
1170 commit_id, f_path = self._get_commit_and_path()
1189 commit_id, f_path = self._get_commit_and_path()
1171
1190
1172 self._ensure_not_locked()
1191 self._ensure_not_locked()
1173
1192
1174 c.commit = self._get_commit_or_redirect(commit_id, redirect_after=False)
1193 c.commit = self._get_commit_or_redirect(commit_id, redirect_after=False)
1175 if c.commit is None:
1194 if c.commit is None:
1176 c.commit = EmptyCommit(alias=self.rhodecode_vcs_repo.alias)
1195 c.commit = EmptyCommit(alias=self.rhodecode_vcs_repo.alias)
1177 c.default_message = (_('Added file via RhodeCode Enterprise'))
1196 c.default_message = (_('Added file via RhodeCode Enterprise'))
1178 c.f_path = f_path.lstrip('/') # ensure not relative path
1197 c.f_path = f_path.lstrip('/') # ensure not relative path
1179
1198
1180 return self._get_template_context(c)
1199 return self._get_template_context(c)
1181
1200
1182 @LoginRequired()
1201 @LoginRequired()
1183 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1202 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1184 @CSRFRequired()
1203 @CSRFRequired()
1185 @view_config(
1204 @view_config(
1186 route_name='repo_files_create_file', request_method='POST',
1205 route_name='repo_files_create_file', request_method='POST',
1187 renderer=None)
1206 renderer=None)
1188 def repo_files_create_file(self):
1207 def repo_files_create_file(self):
1189 _ = self.request.translate
1208 _ = self.request.translate
1190 c = self.load_default_context()
1209 c = self.load_default_context()
1191 commit_id, f_path = self._get_commit_and_path()
1210 commit_id, f_path = self._get_commit_and_path()
1192
1211
1193 self._ensure_not_locked()
1212 self._ensure_not_locked()
1194
1213
1195 r_post = self.request.POST
1214 r_post = self.request.POST
1196
1215
1197 c.commit = self._get_commit_or_redirect(
1216 c.commit = self._get_commit_or_redirect(
1198 commit_id, redirect_after=False)
1217 commit_id, redirect_after=False)
1199 if c.commit is None:
1218 if c.commit is None:
1200 c.commit = EmptyCommit(alias=self.rhodecode_vcs_repo.alias)
1219 c.commit = EmptyCommit(alias=self.rhodecode_vcs_repo.alias)
1201 c.default_message = (_('Added file via RhodeCode Enterprise'))
1220 c.default_message = (_('Added file via RhodeCode Enterprise'))
1202 c.f_path = f_path
1221 c.f_path = f_path
1203 unix_mode = 0
1222 unix_mode = 0
1204 content = convert_line_endings(r_post.get('content', ''), unix_mode)
1223 content = convert_line_endings(r_post.get('content', ''), unix_mode)
1205
1224
1206 message = r_post.get('message') or c.default_message
1225 message = r_post.get('message') or c.default_message
1207 filename = r_post.get('filename')
1226 filename = r_post.get('filename')
1208 location = r_post.get('location', '') # dir location
1227 location = r_post.get('location', '') # dir location
1209 file_obj = r_post.get('upload_file', None)
1228 file_obj = r_post.get('upload_file', None)
1210
1229
1211 if file_obj is not None and hasattr(file_obj, 'filename'):
1230 if file_obj is not None and hasattr(file_obj, 'filename'):
1212 filename = r_post.get('filename_upload')
1231 filename = r_post.get('filename_upload')
1213 content = file_obj.file
1232 content = file_obj.file
1214
1233
1215 if hasattr(content, 'file'):
1234 if hasattr(content, 'file'):
1216 # non posix systems store real file under file attr
1235 # non posix systems store real file under file attr
1217 content = content.file
1236 content = content.file
1218
1237
1219 if self.rhodecode_vcs_repo.is_empty:
1238 if self.rhodecode_vcs_repo.is_empty:
1220 default_redirect_url = h.route_path(
1239 default_redirect_url = h.route_path(
1221 'repo_summary', repo_name=self.db_repo_name)
1240 'repo_summary', repo_name=self.db_repo_name)
1222 else:
1241 else:
1223 default_redirect_url = h.route_path(
1242 default_redirect_url = h.route_path(
1224 'repo_commit', repo_name=self.db_repo_name, commit_id='tip')
1243 'repo_commit', repo_name=self.db_repo_name, commit_id='tip')
1225
1244
1226 # If there's no commit, redirect to repo summary
1245 # If there's no commit, redirect to repo summary
1227 if type(c.commit) is EmptyCommit:
1246 if type(c.commit) is EmptyCommit:
1228 redirect_url = h.route_path(
1247 redirect_url = h.route_path(
1229 'repo_summary', repo_name=self.db_repo_name)
1248 'repo_summary', repo_name=self.db_repo_name)
1230 else:
1249 else:
1231 redirect_url = default_redirect_url
1250 redirect_url = default_redirect_url
1232
1251
1233 if not filename:
1252 if not filename:
1234 h.flash(_('No filename'), category='warning')
1253 h.flash(_('No filename'), category='warning')
1235 raise HTTPFound(redirect_url)
1254 raise HTTPFound(redirect_url)
1236
1255
1237 # extract the location from filename,
1256 # extract the location from filename,
1238 # allows using foo/bar.txt syntax to create subdirectories
1257 # allows using foo/bar.txt syntax to create subdirectories
1239 subdir_loc = filename.rsplit('/', 1)
1258 subdir_loc = filename.rsplit('/', 1)
1240 if len(subdir_loc) == 2:
1259 if len(subdir_loc) == 2:
1241 location = os.path.join(location, subdir_loc[0])
1260 location = os.path.join(location, subdir_loc[0])
1242
1261
1243 # strip all crap out of file, just leave the basename
1262 # strip all crap out of file, just leave the basename
1244 filename = os.path.basename(filename)
1263 filename = os.path.basename(filename)
1245 node_path = os.path.join(location, filename)
1264 node_path = os.path.join(location, filename)
1246 author = self._rhodecode_db_user.full_contact
1265 author = self._rhodecode_db_user.full_contact
1247
1266
1248 try:
1267 try:
1249 nodes = {
1268 nodes = {
1250 node_path: {
1269 node_path: {
1251 'content': content
1270 'content': content
1252 }
1271 }
1253 }
1272 }
1254 ScmModel().create_nodes(
1273 ScmModel().create_nodes(
1255 user=self._rhodecode_db_user.user_id,
1274 user=self._rhodecode_db_user.user_id,
1256 repo=self.db_repo,
1275 repo=self.db_repo,
1257 message=message,
1276 message=message,
1258 nodes=nodes,
1277 nodes=nodes,
1259 parent_commit=c.commit,
1278 parent_commit=c.commit,
1260 author=author,
1279 author=author,
1261 )
1280 )
1262
1281
1263 h.flash(
1282 h.flash(
1264 _('Successfully committed new file `{}`').format(
1283 _('Successfully committed new file `{}`').format(
1265 h.escape(node_path)), category='success')
1284 h.escape(node_path)), category='success')
1266 except NonRelativePathError:
1285 except NonRelativePathError:
1267 log.exception('Non Relative path found')
1286 log.exception('Non Relative path found')
1268 h.flash(_(
1287 h.flash(_(
1269 'The location specified must be a relative path and must not '
1288 'The location specified must be a relative path and must not '
1270 'contain .. in the path'), category='warning')
1289 'contain .. in the path'), category='warning')
1271 raise HTTPFound(default_redirect_url)
1290 raise HTTPFound(default_redirect_url)
1272 except (NodeError, NodeAlreadyExistsError) as e:
1291 except (NodeError, NodeAlreadyExistsError) as e:
1273 h.flash(_(h.escape(e)), category='error')
1292 h.flash(_(h.escape(e)), category='error')
1274 except Exception:
1293 except Exception:
1275 log.exception('Error occurred during commit')
1294 log.exception('Error occurred during commit')
1276 h.flash(_('Error occurred during commit'), category='error')
1295 h.flash(_('Error occurred during commit'), category='error')
1277
1296
1278 raise HTTPFound(default_redirect_url)
1297 raise HTTPFound(default_redirect_url)
@@ -1,369 +1,379 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2018 RhodeCode GmbH
3 # Copyright (C) 2011-2018 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 logging
21 import logging
22 import string
22 import string
23 import rhodecode
23
24
24 from pyramid.view import view_config
25 from pyramid.view import view_config
25 from beaker.cache import cache_region
26 from beaker.cache import cache_region
26
27
27 from rhodecode.controllers import utils
28 from rhodecode.controllers import utils
28 from rhodecode.apps._base import RepoAppView
29 from rhodecode.apps._base import RepoAppView
29 from rhodecode.config.conf import (LANGUAGES_EXTENSIONS_MAP)
30 from rhodecode.config.conf import (LANGUAGES_EXTENSIONS_MAP)
30 from rhodecode.lib import helpers as h, rc_cache
31 from rhodecode.lib import helpers as h, rc_cache
31 from rhodecode.lib.utils2 import safe_str, safe_int
32 from rhodecode.lib.utils2 import safe_str, safe_int
32 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
33 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
33 from rhodecode.lib.markup_renderer import MarkupRenderer, relative_links
34 from rhodecode.lib.markup_renderer import MarkupRenderer, relative_links
34 from rhodecode.lib.ext_json import json
35 from rhodecode.lib.ext_json import json
35 from rhodecode.lib.vcs.backends.base import EmptyCommit
36 from rhodecode.lib.vcs.backends.base import EmptyCommit
36 from rhodecode.lib.vcs.exceptions import (
37 from rhodecode.lib.vcs.exceptions import (
37 CommitError, EmptyRepositoryError, CommitDoesNotExistError)
38 CommitError, EmptyRepositoryError, CommitDoesNotExistError)
38 from rhodecode.model.db import Statistics, CacheKey, User
39 from rhodecode.model.db import Statistics, CacheKey, User
39 from rhodecode.model.meta import Session
40 from rhodecode.model.meta import Session
40 from rhodecode.model.repo import ReadmeFinder
41 from rhodecode.model.repo import ReadmeFinder
41 from rhodecode.model.scm import ScmModel
42 from rhodecode.model.scm import ScmModel
42
43
43 log = logging.getLogger(__name__)
44 log = logging.getLogger(__name__)
44
45
45
46
46 class RepoSummaryView(RepoAppView):
47 class RepoSummaryView(RepoAppView):
47
48
48 def load_default_context(self):
49 def load_default_context(self):
49 c = self._get_local_tmpl_context(include_app_defaults=True)
50 c = self._get_local_tmpl_context(include_app_defaults=True)
50 c.rhodecode_repo = None
51 c.rhodecode_repo = None
51 if not c.repository_requirements_missing:
52 if not c.repository_requirements_missing:
52 c.rhodecode_repo = self.rhodecode_vcs_repo
53 c.rhodecode_repo = self.rhodecode_vcs_repo
53 return c
54 return c
54
55
55 def _get_readme_data(self, db_repo, default_renderer):
56 def _get_readme_data(self, db_repo, default_renderer):
56 repo_name = db_repo.repo_name
57 repo_name = db_repo.repo_name
57 log.debug('Looking for README file')
58 log.debug('Looking for README file')
58
59
59 @cache_region('long_term')
60 @cache_region('long_term')
60 def _generate_readme(cache_key):
61 def _generate_readme(cache_key):
61 readme_data = None
62 readme_data = None
62 readme_node = None
63 readme_node = None
63 readme_filename = None
64 readme_filename = None
64 commit = self._get_landing_commit_or_none(db_repo)
65 commit = self._get_landing_commit_or_none(db_repo)
65 if commit:
66 if commit:
66 log.debug("Searching for a README file.")
67 log.debug("Searching for a README file.")
67 readme_node = ReadmeFinder(default_renderer).search(commit)
68 readme_node = ReadmeFinder(default_renderer).search(commit)
68 if readme_node:
69 if readme_node:
69 relative_urls = {
70 relative_urls = {
70 'raw': h.route_path(
71 'raw': h.route_path(
71 'repo_file_raw', repo_name=repo_name,
72 'repo_file_raw', repo_name=repo_name,
72 commit_id=commit.raw_id, f_path=readme_node.path),
73 commit_id=commit.raw_id, f_path=readme_node.path),
73 'standard': h.route_path(
74 'standard': h.route_path(
74 'repo_files', repo_name=repo_name,
75 'repo_files', repo_name=repo_name,
75 commit_id=commit.raw_id, f_path=readme_node.path),
76 commit_id=commit.raw_id, f_path=readme_node.path),
76 }
77 }
77 readme_data = self._render_readme_or_none(
78 readme_data = self._render_readme_or_none(
78 commit, readme_node, relative_urls)
79 commit, readme_node, relative_urls)
79 readme_filename = readme_node.path
80 readme_filename = readme_node.path
80 return readme_data, readme_filename
81 return readme_data, readme_filename
81
82
82 invalidator_context = CacheKey.repo_context_cache(
83 invalidator_context = CacheKey.repo_context_cache(
83 _generate_readme, repo_name, CacheKey.CACHE_TYPE_README)
84 _generate_readme, repo_name, CacheKey.CACHE_TYPE_README)
84
85
85 with invalidator_context as context:
86 with invalidator_context as context:
86 context.invalidate()
87 context.invalidate()
87 computed = context.compute()
88 computed = context.compute()
88
89
89 return computed
90 return computed
90
91
91 def _get_landing_commit_or_none(self, db_repo):
92 def _get_landing_commit_or_none(self, db_repo):
92 log.debug("Getting the landing commit.")
93 log.debug("Getting the landing commit.")
93 try:
94 try:
94 commit = db_repo.get_landing_commit()
95 commit = db_repo.get_landing_commit()
95 if not isinstance(commit, EmptyCommit):
96 if not isinstance(commit, EmptyCommit):
96 return commit
97 return commit
97 else:
98 else:
98 log.debug("Repository is empty, no README to render.")
99 log.debug("Repository is empty, no README to render.")
99 except CommitError:
100 except CommitError:
100 log.exception(
101 log.exception(
101 "Problem getting commit when trying to render the README.")
102 "Problem getting commit when trying to render the README.")
102
103
103 def _render_readme_or_none(self, commit, readme_node, relative_urls):
104 def _render_readme_or_none(self, commit, readme_node, relative_urls):
104 log.debug(
105 log.debug(
105 'Found README file `%s` rendering...', readme_node.path)
106 'Found README file `%s` rendering...', readme_node.path)
106 renderer = MarkupRenderer()
107 renderer = MarkupRenderer()
107 try:
108 try:
108 html_source = renderer.render(
109 html_source = renderer.render(
109 readme_node.content, filename=readme_node.path)
110 readme_node.content, filename=readme_node.path)
110 if relative_urls:
111 if relative_urls:
111 return relative_links(html_source, relative_urls)
112 return relative_links(html_source, relative_urls)
112 return html_source
113 return html_source
113 except Exception:
114 except Exception:
114 log.exception(
115 log.exception(
115 "Exception while trying to render the README")
116 "Exception while trying to render the README")
116
117
117 def _load_commits_context(self, c):
118 def _load_commits_context(self, c):
118 p = safe_int(self.request.GET.get('page'), 1)
119 p = safe_int(self.request.GET.get('page'), 1)
119 size = safe_int(self.request.GET.get('size'), 10)
120 size = safe_int(self.request.GET.get('size'), 10)
120
121
121 def url_generator(**kw):
122 def url_generator(**kw):
122 query_params = {
123 query_params = {
123 'size': size
124 'size': size
124 }
125 }
125 query_params.update(kw)
126 query_params.update(kw)
126 return h.route_path(
127 return h.route_path(
127 'repo_summary_commits',
128 'repo_summary_commits',
128 repo_name=c.rhodecode_db_repo.repo_name, _query=query_params)
129 repo_name=c.rhodecode_db_repo.repo_name, _query=query_params)
129
130
130 pre_load = ['author', 'branch', 'date', 'message']
131 pre_load = ['author', 'branch', 'date', 'message']
131 try:
132 try:
132 collection = self.rhodecode_vcs_repo.get_commits(pre_load=pre_load)
133 collection = self.rhodecode_vcs_repo.get_commits(pre_load=pre_load)
133 except EmptyRepositoryError:
134 except EmptyRepositoryError:
134 collection = self.rhodecode_vcs_repo
135 collection = self.rhodecode_vcs_repo
135
136
136 c.repo_commits = h.RepoPage(
137 c.repo_commits = h.RepoPage(
137 collection, page=p, items_per_page=size, url=url_generator)
138 collection, page=p, items_per_page=size, url=url_generator)
138 page_ids = [x.raw_id for x in c.repo_commits]
139 page_ids = [x.raw_id for x in c.repo_commits]
139 c.comments = self.db_repo.get_comments(page_ids)
140 c.comments = self.db_repo.get_comments(page_ids)
140 c.statuses = self.db_repo.statuses(page_ids)
141 c.statuses = self.db_repo.statuses(page_ids)
141
142
142 @LoginRequired()
143 @LoginRequired()
143 @HasRepoPermissionAnyDecorator(
144 @HasRepoPermissionAnyDecorator(
144 'repository.read', 'repository.write', 'repository.admin')
145 'repository.read', 'repository.write', 'repository.admin')
145 @view_config(
146 @view_config(
146 route_name='repo_summary_commits', request_method='GET',
147 route_name='repo_summary_commits', request_method='GET',
147 renderer='rhodecode:templates/summary/summary_commits.mako')
148 renderer='rhodecode:templates/summary/summary_commits.mako')
148 def summary_commits(self):
149 def summary_commits(self):
149 c = self.load_default_context()
150 c = self.load_default_context()
150 self._load_commits_context(c)
151 self._load_commits_context(c)
151 return self._get_template_context(c)
152 return self._get_template_context(c)
152
153
153 @LoginRequired()
154 @LoginRequired()
154 @HasRepoPermissionAnyDecorator(
155 @HasRepoPermissionAnyDecorator(
155 'repository.read', 'repository.write', 'repository.admin')
156 'repository.read', 'repository.write', 'repository.admin')
156 @view_config(
157 @view_config(
157 route_name='repo_summary', request_method='GET',
158 route_name='repo_summary', request_method='GET',
158 renderer='rhodecode:templates/summary/summary.mako')
159 renderer='rhodecode:templates/summary/summary.mako')
159 @view_config(
160 @view_config(
160 route_name='repo_summary_slash', request_method='GET',
161 route_name='repo_summary_slash', request_method='GET',
161 renderer='rhodecode:templates/summary/summary.mako')
162 renderer='rhodecode:templates/summary/summary.mako')
162 @view_config(
163 @view_config(
163 route_name='repo_summary_explicit', request_method='GET',
164 route_name='repo_summary_explicit', request_method='GET',
164 renderer='rhodecode:templates/summary/summary.mako')
165 renderer='rhodecode:templates/summary/summary.mako')
165 def summary(self):
166 def summary(self):
166 c = self.load_default_context()
167 c = self.load_default_context()
167
168
168 # Prepare the clone URL
169 # Prepare the clone URL
169 username = ''
170 username = ''
170 if self._rhodecode_user.username != User.DEFAULT_USER:
171 if self._rhodecode_user.username != User.DEFAULT_USER:
171 username = safe_str(self._rhodecode_user.username)
172 username = safe_str(self._rhodecode_user.username)
172
173
173 _def_clone_uri = _def_clone_uri_id = c.clone_uri_tmpl
174 _def_clone_uri = _def_clone_uri_id = c.clone_uri_tmpl
174 _def_clone_uri_ssh = c.clone_uri_ssh_tmpl
175 _def_clone_uri_ssh = c.clone_uri_ssh_tmpl
175
176
176 if '{repo}' in _def_clone_uri:
177 if '{repo}' in _def_clone_uri:
177 _def_clone_uri_id = _def_clone_uri.replace(
178 _def_clone_uri_id = _def_clone_uri.replace(
178 '{repo}', '_{repoid}')
179 '{repo}', '_{repoid}')
179 elif '{repoid}' in _def_clone_uri:
180 elif '{repoid}' in _def_clone_uri:
180 _def_clone_uri_id = _def_clone_uri.replace(
181 _def_clone_uri_id = _def_clone_uri.replace(
181 '_{repoid}', '{repo}')
182 '_{repoid}', '{repo}')
182
183
183 c.clone_repo_url = self.db_repo.clone_url(
184 c.clone_repo_url = self.db_repo.clone_url(
184 user=username, uri_tmpl=_def_clone_uri)
185 user=username, uri_tmpl=_def_clone_uri)
185 c.clone_repo_url_id = self.db_repo.clone_url(
186 c.clone_repo_url_id = self.db_repo.clone_url(
186 user=username, uri_tmpl=_def_clone_uri_id)
187 user=username, uri_tmpl=_def_clone_uri_id)
187 c.clone_repo_url_ssh = self.db_repo.clone_url(
188 c.clone_repo_url_ssh = self.db_repo.clone_url(
188 uri_tmpl=_def_clone_uri_ssh, ssh=True)
189 uri_tmpl=_def_clone_uri_ssh, ssh=True)
189
190
190 # If enabled, get statistics data
191 # If enabled, get statistics data
191
192
192 c.show_stats = bool(self.db_repo.enable_statistics)
193 c.show_stats = bool(self.db_repo.enable_statistics)
193
194
194 stats = Session().query(Statistics) \
195 stats = Session().query(Statistics) \
195 .filter(Statistics.repository == self.db_repo) \
196 .filter(Statistics.repository == self.db_repo) \
196 .scalar()
197 .scalar()
197
198
198 c.stats_percentage = 0
199 c.stats_percentage = 0
199
200
200 if stats and stats.languages:
201 if stats and stats.languages:
201 c.no_data = False is self.db_repo.enable_statistics
202 c.no_data = False is self.db_repo.enable_statistics
202 lang_stats_d = json.loads(stats.languages)
203 lang_stats_d = json.loads(stats.languages)
203
204
204 # Sort first by decreasing count and second by the file extension,
205 # Sort first by decreasing count and second by the file extension,
205 # so we have a consistent output.
206 # so we have a consistent output.
206 lang_stats_items = sorted(lang_stats_d.iteritems(),
207 lang_stats_items = sorted(lang_stats_d.iteritems(),
207 key=lambda k: (-k[1], k[0]))[:10]
208 key=lambda k: (-k[1], k[0]))[:10]
208 lang_stats = [(x, {"count": y,
209 lang_stats = [(x, {"count": y,
209 "desc": LANGUAGES_EXTENSIONS_MAP.get(x)})
210 "desc": LANGUAGES_EXTENSIONS_MAP.get(x)})
210 for x, y in lang_stats_items]
211 for x, y in lang_stats_items]
211
212
212 c.trending_languages = json.dumps(lang_stats)
213 c.trending_languages = json.dumps(lang_stats)
213 else:
214 else:
214 c.no_data = True
215 c.no_data = True
215 c.trending_languages = json.dumps({})
216 c.trending_languages = json.dumps({})
216
217
217 scm_model = ScmModel()
218 scm_model = ScmModel()
218 c.enable_downloads = self.db_repo.enable_downloads
219 c.enable_downloads = self.db_repo.enable_downloads
219 c.repository_followers = scm_model.get_followers(self.db_repo)
220 c.repository_followers = scm_model.get_followers(self.db_repo)
220 c.repository_forks = scm_model.get_forks(self.db_repo)
221 c.repository_forks = scm_model.get_forks(self.db_repo)
221 c.repository_is_user_following = scm_model.is_following_repo(
222 c.repository_is_user_following = scm_model.is_following_repo(
222 self.db_repo_name, self._rhodecode_user.user_id)
223 self.db_repo_name, self._rhodecode_user.user_id)
223
224
224 # first interaction with the VCS instance after here...
225 # first interaction with the VCS instance after here...
225 if c.repository_requirements_missing:
226 if c.repository_requirements_missing:
226 self.request.override_renderer = \
227 self.request.override_renderer = \
227 'rhodecode:templates/summary/missing_requirements.mako'
228 'rhodecode:templates/summary/missing_requirements.mako'
228 return self._get_template_context(c)
229 return self._get_template_context(c)
229
230
230 c.readme_data, c.readme_file = \
231 c.readme_data, c.readme_file = \
231 self._get_readme_data(self.db_repo, c.visual.default_renderer)
232 self._get_readme_data(self.db_repo, c.visual.default_renderer)
232
233
233 # loads the summary commits template context
234 # loads the summary commits template context
234 self._load_commits_context(c)
235 self._load_commits_context(c)
235
236
236 return self._get_template_context(c)
237 return self._get_template_context(c)
237
238
238 def get_request_commit_id(self):
239 def get_request_commit_id(self):
239 return self.request.matchdict['commit_id']
240 return self.request.matchdict['commit_id']
240
241
241 @LoginRequired()
242 @LoginRequired()
242 @HasRepoPermissionAnyDecorator(
243 @HasRepoPermissionAnyDecorator(
243 'repository.read', 'repository.write', 'repository.admin')
244 'repository.read', 'repository.write', 'repository.admin')
244 @view_config(
245 @view_config(
245 route_name='repo_stats', request_method='GET',
246 route_name='repo_stats', request_method='GET',
246 renderer='json_ext')
247 renderer='json_ext')
247 def repo_stats(self):
248 def repo_stats(self):
248 commit_id = self.get_request_commit_id()
249 commit_id = self.get_request_commit_id()
249 show_stats = bool(self.db_repo.enable_statistics)
250 show_stats = bool(self.db_repo.enable_statistics)
250 repo_id = self.db_repo.repo_id
251 repo_id = self.db_repo.repo_id
251
252
253 cache_seconds = safe_int(
254 rhodecode.CONFIG.get('rc_cache.cache_repo.expiration_time'))
255 cache_on = cache_seconds > 0
256 log.debug(
257 'Computing REPO TREE for repo_id %s commit_id `%s` '
258 'with caching: %s[TTL: %ss]' % (
259 repo_id, commit_id, cache_on, cache_seconds or 0))
260
252 cache_namespace_uid = 'cache_repo.{}'.format(repo_id)
261 cache_namespace_uid = 'cache_repo.{}'.format(repo_id)
253 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)
254
263
255 @region.cache_on_arguments(namespace=cache_namespace_uid)
264 @region.cache_on_arguments(namespace=cache_namespace_uid,
265 should_cache_fn=lambda v: cache_on)
256 def compute_stats(repo_id, commit_id, show_stats):
266 def compute_stats(repo_id, commit_id, show_stats):
257 code_stats = {}
267 code_stats = {}
258 size = 0
268 size = 0
259 try:
269 try:
260 scm_instance = self.db_repo.scm_instance()
270 scm_instance = self.db_repo.scm_instance()
261 commit = scm_instance.get_commit(commit_id)
271 commit = scm_instance.get_commit(commit_id)
262
272
263 for node in commit.get_filenodes_generator():
273 for node in commit.get_filenodes_generator():
264 size += node.size
274 size += node.size
265 if not show_stats:
275 if not show_stats:
266 continue
276 continue
267 ext = string.lower(node.extension)
277 ext = string.lower(node.extension)
268 ext_info = LANGUAGES_EXTENSIONS_MAP.get(ext)
278 ext_info = LANGUAGES_EXTENSIONS_MAP.get(ext)
269 if ext_info:
279 if ext_info:
270 if ext in code_stats:
280 if ext in code_stats:
271 code_stats[ext]['count'] += 1
281 code_stats[ext]['count'] += 1
272 else:
282 else:
273 code_stats[ext] = {"count": 1, "desc": ext_info}
283 code_stats[ext] = {"count": 1, "desc": ext_info}
274 except (EmptyRepositoryError, CommitDoesNotExistError):
284 except (EmptyRepositoryError, CommitDoesNotExistError):
275 pass
285 pass
276 return {'size': h.format_byte_size_binary(size),
286 return {'size': h.format_byte_size_binary(size),
277 'code_stats': code_stats}
287 'code_stats': code_stats}
278
288
279 stats = compute_stats(self.db_repo.repo_id, commit_id, show_stats)
289 stats = compute_stats(self.db_repo.repo_id, commit_id, show_stats)
280 return stats
290 return stats
281
291
282 @LoginRequired()
292 @LoginRequired()
283 @HasRepoPermissionAnyDecorator(
293 @HasRepoPermissionAnyDecorator(
284 'repository.read', 'repository.write', 'repository.admin')
294 'repository.read', 'repository.write', 'repository.admin')
285 @view_config(
295 @view_config(
286 route_name='repo_refs_data', request_method='GET',
296 route_name='repo_refs_data', request_method='GET',
287 renderer='json_ext')
297 renderer='json_ext')
288 def repo_refs_data(self):
298 def repo_refs_data(self):
289 _ = self.request.translate
299 _ = self.request.translate
290 self.load_default_context()
300 self.load_default_context()
291
301
292 repo = self.rhodecode_vcs_repo
302 repo = self.rhodecode_vcs_repo
293 refs_to_create = [
303 refs_to_create = [
294 (_("Branch"), repo.branches, 'branch'),
304 (_("Branch"), repo.branches, 'branch'),
295 (_("Tag"), repo.tags, 'tag'),
305 (_("Tag"), repo.tags, 'tag'),
296 (_("Bookmark"), repo.bookmarks, 'book'),
306 (_("Bookmark"), repo.bookmarks, 'book'),
297 ]
307 ]
298 res = self._create_reference_data(
308 res = self._create_reference_data(
299 repo, self.db_repo_name, refs_to_create)
309 repo, self.db_repo_name, refs_to_create)
300 data = {
310 data = {
301 'more': False,
311 'more': False,
302 'results': res
312 'results': res
303 }
313 }
304 return data
314 return data
305
315
306 @LoginRequired()
316 @LoginRequired()
307 @HasRepoPermissionAnyDecorator(
317 @HasRepoPermissionAnyDecorator(
308 'repository.read', 'repository.write', 'repository.admin')
318 'repository.read', 'repository.write', 'repository.admin')
309 @view_config(
319 @view_config(
310 route_name='repo_refs_changelog_data', request_method='GET',
320 route_name='repo_refs_changelog_data', request_method='GET',
311 renderer='json_ext')
321 renderer='json_ext')
312 def repo_refs_changelog_data(self):
322 def repo_refs_changelog_data(self):
313 _ = self.request.translate
323 _ = self.request.translate
314 self.load_default_context()
324 self.load_default_context()
315
325
316 repo = self.rhodecode_vcs_repo
326 repo = self.rhodecode_vcs_repo
317
327
318 refs_to_create = [
328 refs_to_create = [
319 (_("Branches"), repo.branches, 'branch'),
329 (_("Branches"), repo.branches, 'branch'),
320 (_("Closed branches"), repo.branches_closed, 'branch_closed'),
330 (_("Closed branches"), repo.branches_closed, 'branch_closed'),
321 # TODO: enable when vcs can handle bookmarks filters
331 # TODO: enable when vcs can handle bookmarks filters
322 # (_("Bookmarks"), repo.bookmarks, "book"),
332 # (_("Bookmarks"), repo.bookmarks, "book"),
323 ]
333 ]
324 res = self._create_reference_data(
334 res = self._create_reference_data(
325 repo, self.db_repo_name, refs_to_create)
335 repo, self.db_repo_name, refs_to_create)
326 data = {
336 data = {
327 'more': False,
337 'more': False,
328 'results': res
338 'results': res
329 }
339 }
330 return data
340 return data
331
341
332 def _create_reference_data(self, repo, full_repo_name, refs_to_create):
342 def _create_reference_data(self, repo, full_repo_name, refs_to_create):
333 format_ref_id = utils.get_format_ref_id(repo)
343 format_ref_id = utils.get_format_ref_id(repo)
334
344
335 result = []
345 result = []
336 for title, refs, ref_type in refs_to_create:
346 for title, refs, ref_type in refs_to_create:
337 if refs:
347 if refs:
338 result.append({
348 result.append({
339 'text': title,
349 'text': title,
340 'children': self._create_reference_items(
350 'children': self._create_reference_items(
341 repo, full_repo_name, refs, ref_type,
351 repo, full_repo_name, refs, ref_type,
342 format_ref_id),
352 format_ref_id),
343 })
353 })
344 return result
354 return result
345
355
346 def _create_reference_items(self, repo, full_repo_name, refs, ref_type,
356 def _create_reference_items(self, repo, full_repo_name, refs, ref_type,
347 format_ref_id):
357 format_ref_id):
348 result = []
358 result = []
349 is_svn = h.is_svn(repo)
359 is_svn = h.is_svn(repo)
350 for ref_name, raw_id in refs.iteritems():
360 for ref_name, raw_id in refs.iteritems():
351 files_url = self._create_files_url(
361 files_url = self._create_files_url(
352 repo, full_repo_name, ref_name, raw_id, is_svn)
362 repo, full_repo_name, ref_name, raw_id, is_svn)
353 result.append({
363 result.append({
354 'text': ref_name,
364 'text': ref_name,
355 'id': format_ref_id(ref_name, raw_id),
365 'id': format_ref_id(ref_name, raw_id),
356 'raw_id': raw_id,
366 'raw_id': raw_id,
357 'type': ref_type,
367 'type': ref_type,
358 'files_url': files_url,
368 'files_url': files_url,
359 })
369 })
360 return result
370 return result
361
371
362 def _create_files_url(self, repo, full_repo_name, ref_name, raw_id, is_svn):
372 def _create_files_url(self, repo, full_repo_name, ref_name, raw_id, is_svn):
363 use_commit_id = '/' in ref_name or is_svn
373 use_commit_id = '/' in ref_name or is_svn
364 return h.route_path(
374 return h.route_path(
365 'repo_files',
375 'repo_files',
366 repo_name=full_repo_name,
376 repo_name=full_repo_name,
367 f_path=ref_name if is_svn else '',
377 f_path=ref_name if is_svn else '',
368 commit_id=raw_id if use_commit_id else ref_name,
378 commit_id=raw_id if use_commit_id else ref_name,
369 _query=dict(at=ref_name))
379 _query=dict(at=ref_name))
General Comments 0
You need to be logged in to leave comments. Login now