##// END OF EJS Templates
files: don't load the file if it is over the size limit since it...
dan -
r1029:518666ba default
parent child Browse files
Show More
@@ -1,1122 +1,1125 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
3 # Copyright (C) 2010-2016 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 """
21 """
22 Files controller for RhodeCode Enterprise
22 Files controller for RhodeCode Enterprise
23 """
23 """
24
24
25 import itertools
25 import itertools
26 import logging
26 import logging
27 import os
27 import os
28 import shutil
28 import shutil
29 import tempfile
29 import tempfile
30
30
31 from pylons import request, response, tmpl_context as c, url
31 from pylons import request, response, tmpl_context as c, url
32 from pylons.i18n.translation import _
32 from pylons.i18n.translation import _
33 from pylons.controllers.util import redirect
33 from pylons.controllers.util import redirect
34 from webob.exc import HTTPNotFound, HTTPBadRequest
34 from webob.exc import HTTPNotFound, HTTPBadRequest
35
35
36 from rhodecode.controllers.utils import parse_path_ref
36 from rhodecode.controllers.utils import parse_path_ref
37 from rhodecode.lib import diffs, helpers as h, caches
37 from rhodecode.lib import diffs, helpers as h, caches
38 from rhodecode.lib.compat import OrderedDict
38 from rhodecode.lib.compat import OrderedDict
39 from rhodecode.lib.codeblocks import (
39 from rhodecode.lib.codeblocks import (
40 filenode_as_lines_tokens, filenode_as_annotated_lines_tokens)
40 filenode_as_lines_tokens, filenode_as_annotated_lines_tokens)
41 from rhodecode.lib.utils import jsonify, action_logger
41 from rhodecode.lib.utils import jsonify, action_logger
42 from rhodecode.lib.utils2 import (
42 from rhodecode.lib.utils2 import (
43 convert_line_endings, detect_mode, safe_str, str2bool)
43 convert_line_endings, detect_mode, safe_str, str2bool)
44 from rhodecode.lib.auth import (
44 from rhodecode.lib.auth import (
45 LoginRequired, HasRepoPermissionAnyDecorator, CSRFRequired, XHRRequired)
45 LoginRequired, HasRepoPermissionAnyDecorator, CSRFRequired, XHRRequired)
46 from rhodecode.lib.base import BaseRepoController, render
46 from rhodecode.lib.base import BaseRepoController, render
47 from rhodecode.lib.vcs import path as vcspath
47 from rhodecode.lib.vcs import path as vcspath
48 from rhodecode.lib.vcs.backends.base import EmptyCommit
48 from rhodecode.lib.vcs.backends.base import EmptyCommit
49 from rhodecode.lib.vcs.conf import settings
49 from rhodecode.lib.vcs.conf import settings
50 from rhodecode.lib.vcs.exceptions import (
50 from rhodecode.lib.vcs.exceptions import (
51 RepositoryError, CommitDoesNotExistError, EmptyRepositoryError,
51 RepositoryError, CommitDoesNotExistError, EmptyRepositoryError,
52 ImproperArchiveTypeError, VCSError, NodeAlreadyExistsError,
52 ImproperArchiveTypeError, VCSError, NodeAlreadyExistsError,
53 NodeDoesNotExistError, CommitError, NodeError)
53 NodeDoesNotExistError, CommitError, NodeError)
54 from rhodecode.lib.vcs.nodes import FileNode
54 from rhodecode.lib.vcs.nodes import FileNode
55
55
56 from rhodecode.model.repo import RepoModel
56 from rhodecode.model.repo import RepoModel
57 from rhodecode.model.scm import ScmModel
57 from rhodecode.model.scm import ScmModel
58 from rhodecode.model.db import Repository
58 from rhodecode.model.db import Repository
59
59
60 from rhodecode.controllers.changeset import (
60 from rhodecode.controllers.changeset import (
61 _ignorews_url, _context_url, get_line_ctx, get_ignore_ws)
61 _ignorews_url, _context_url, get_line_ctx, get_ignore_ws)
62 from rhodecode.lib.exceptions import NonRelativePathError
62 from rhodecode.lib.exceptions import NonRelativePathError
63
63
64 log = logging.getLogger(__name__)
64 log = logging.getLogger(__name__)
65
65
66
66
67 class FilesController(BaseRepoController):
67 class FilesController(BaseRepoController):
68
68
69 def __before__(self):
69 def __before__(self):
70 super(FilesController, self).__before__()
70 super(FilesController, self).__before__()
71 c.cut_off_limit = self.cut_off_limit_file
71 c.cut_off_limit = self.cut_off_limit_file
72
72
73 def _get_default_encoding(self):
73 def _get_default_encoding(self):
74 enc_list = getattr(c, 'default_encodings', [])
74 enc_list = getattr(c, 'default_encodings', [])
75 return enc_list[0] if enc_list else 'UTF-8'
75 return enc_list[0] if enc_list else 'UTF-8'
76
76
77 def __get_commit_or_redirect(self, commit_id, repo_name,
77 def __get_commit_or_redirect(self, commit_id, repo_name,
78 redirect_after=True):
78 redirect_after=True):
79 """
79 """
80 This is a safe way to get commit. If an error occurs it redirects to
80 This is a safe way to get commit. If an error occurs it redirects to
81 tip with proper message
81 tip with proper message
82
82
83 :param commit_id: id of commit to fetch
83 :param commit_id: id of commit to fetch
84 :param repo_name: repo name to redirect after
84 :param repo_name: repo name to redirect after
85 :param redirect_after: toggle redirection
85 :param redirect_after: toggle redirection
86 """
86 """
87 try:
87 try:
88 return c.rhodecode_repo.get_commit(commit_id)
88 return c.rhodecode_repo.get_commit(commit_id)
89 except EmptyRepositoryError:
89 except EmptyRepositoryError:
90 if not redirect_after:
90 if not redirect_after:
91 return None
91 return None
92 url_ = url('files_add_home',
92 url_ = url('files_add_home',
93 repo_name=c.repo_name,
93 repo_name=c.repo_name,
94 revision=0, f_path='', anchor='edit')
94 revision=0, f_path='', anchor='edit')
95 if h.HasRepoPermissionAny(
95 if h.HasRepoPermissionAny(
96 'repository.write', 'repository.admin')(c.repo_name):
96 'repository.write', 'repository.admin')(c.repo_name):
97 add_new = h.link_to(
97 add_new = h.link_to(
98 _('Click here to add a new file.'),
98 _('Click here to add a new file.'),
99 url_, class_="alert-link")
99 url_, class_="alert-link")
100 else:
100 else:
101 add_new = ""
101 add_new = ""
102 h.flash(h.literal(
102 h.flash(h.literal(
103 _('There are no files yet. %s') % add_new), category='warning')
103 _('There are no files yet. %s') % add_new), category='warning')
104 redirect(h.url('summary_home', repo_name=repo_name))
104 redirect(h.url('summary_home', repo_name=repo_name))
105 except (CommitDoesNotExistError, LookupError):
105 except (CommitDoesNotExistError, LookupError):
106 msg = _('No such commit exists for this repository')
106 msg = _('No such commit exists for this repository')
107 h.flash(msg, category='error')
107 h.flash(msg, category='error')
108 raise HTTPNotFound()
108 raise HTTPNotFound()
109 except RepositoryError as e:
109 except RepositoryError as e:
110 h.flash(safe_str(e), category='error')
110 h.flash(safe_str(e), category='error')
111 raise HTTPNotFound()
111 raise HTTPNotFound()
112
112
113 def __get_filenode_or_redirect(self, repo_name, commit, path):
113 def __get_filenode_or_redirect(self, repo_name, commit, path):
114 """
114 """
115 Returns file_node, if error occurs or given path is directory,
115 Returns file_node, if error occurs or given path is directory,
116 it'll redirect to top level path
116 it'll redirect to top level path
117
117
118 :param repo_name: repo_name
118 :param repo_name: repo_name
119 :param commit: given commit
119 :param commit: given commit
120 :param path: path to lookup
120 :param path: path to lookup
121 """
121 """
122 try:
122 try:
123 file_node = commit.get_node(path)
123 file_node = commit.get_node(path)
124 if file_node.is_dir():
124 if file_node.is_dir():
125 raise RepositoryError('The given path is a directory')
125 raise RepositoryError('The given path is a directory')
126 except CommitDoesNotExistError:
126 except CommitDoesNotExistError:
127 msg = _('No such commit exists for this repository')
127 msg = _('No such commit exists for this repository')
128 log.exception(msg)
128 log.exception(msg)
129 h.flash(msg, category='error')
129 h.flash(msg, category='error')
130 raise HTTPNotFound()
130 raise HTTPNotFound()
131 except RepositoryError as e:
131 except RepositoryError as e:
132 h.flash(safe_str(e), category='error')
132 h.flash(safe_str(e), category='error')
133 raise HTTPNotFound()
133 raise HTTPNotFound()
134
134
135 return file_node
135 return file_node
136
136
137 def __get_tree_cache_manager(self, repo_name, namespace_type):
137 def __get_tree_cache_manager(self, repo_name, namespace_type):
138 _namespace = caches.get_repo_namespace_key(namespace_type, repo_name)
138 _namespace = caches.get_repo_namespace_key(namespace_type, repo_name)
139 return caches.get_cache_manager('repo_cache_long', _namespace)
139 return caches.get_cache_manager('repo_cache_long', _namespace)
140
140
141 def _get_tree_at_commit(self, repo_name, commit_id, f_path,
141 def _get_tree_at_commit(self, repo_name, commit_id, f_path,
142 full_load=False, force=False):
142 full_load=False, force=False):
143 def _cached_tree():
143 def _cached_tree():
144 log.debug('Generating cached file tree for %s, %s, %s',
144 log.debug('Generating cached file tree for %s, %s, %s',
145 repo_name, commit_id, f_path)
145 repo_name, commit_id, f_path)
146 c.full_load = full_load
146 c.full_load = full_load
147 return render('files/files_browser_tree.html')
147 return render('files/files_browser_tree.html')
148
148
149 cache_manager = self.__get_tree_cache_manager(
149 cache_manager = self.__get_tree_cache_manager(
150 repo_name, caches.FILE_TREE)
150 repo_name, caches.FILE_TREE)
151
151
152 cache_key = caches.compute_key_from_params(
152 cache_key = caches.compute_key_from_params(
153 repo_name, commit_id, f_path)
153 repo_name, commit_id, f_path)
154
154
155 if force:
155 if force:
156 # we want to force recompute of caches
156 # we want to force recompute of caches
157 cache_manager.remove_value(cache_key)
157 cache_manager.remove_value(cache_key)
158
158
159 return cache_manager.get(cache_key, createfunc=_cached_tree)
159 return cache_manager.get(cache_key, createfunc=_cached_tree)
160
160
161 def _get_nodelist_at_commit(self, repo_name, commit_id, f_path):
161 def _get_nodelist_at_commit(self, repo_name, commit_id, f_path):
162 def _cached_nodes():
162 def _cached_nodes():
163 log.debug('Generating cached nodelist for %s, %s, %s',
163 log.debug('Generating cached nodelist for %s, %s, %s',
164 repo_name, commit_id, f_path)
164 repo_name, commit_id, f_path)
165 _d, _f = ScmModel().get_nodes(
165 _d, _f = ScmModel().get_nodes(
166 repo_name, commit_id, f_path, flat=False)
166 repo_name, commit_id, f_path, flat=False)
167 return _d + _f
167 return _d + _f
168
168
169 cache_manager = self.__get_tree_cache_manager(
169 cache_manager = self.__get_tree_cache_manager(
170 repo_name, caches.FILE_SEARCH_TREE_META)
170 repo_name, caches.FILE_SEARCH_TREE_META)
171
171
172 cache_key = caches.compute_key_from_params(
172 cache_key = caches.compute_key_from_params(
173 repo_name, commit_id, f_path)
173 repo_name, commit_id, f_path)
174 return cache_manager.get(cache_key, createfunc=_cached_nodes)
174 return cache_manager.get(cache_key, createfunc=_cached_nodes)
175
175
176 @LoginRequired()
176 @LoginRequired()
177 @HasRepoPermissionAnyDecorator(
177 @HasRepoPermissionAnyDecorator(
178 'repository.read', 'repository.write', 'repository.admin')
178 'repository.read', 'repository.write', 'repository.admin')
179 def index(
179 def index(
180 self, repo_name, revision, f_path, annotate=False, rendered=False):
180 self, repo_name, revision, f_path, annotate=False, rendered=False):
181 commit_id = revision
181 commit_id = revision
182
182
183 # redirect to given commit_id from form if given
183 # redirect to given commit_id from form if given
184 get_commit_id = request.GET.get('at_rev', None)
184 get_commit_id = request.GET.get('at_rev', None)
185 if get_commit_id:
185 if get_commit_id:
186 self.__get_commit_or_redirect(get_commit_id, repo_name)
186 self.__get_commit_or_redirect(get_commit_id, repo_name)
187
187
188 c.commit = self.__get_commit_or_redirect(commit_id, repo_name)
188 c.commit = self.__get_commit_or_redirect(commit_id, repo_name)
189 c.branch = request.GET.get('branch', None)
189 c.branch = request.GET.get('branch', None)
190 c.f_path = f_path
190 c.f_path = f_path
191 c.annotate = annotate
191 c.annotate = annotate
192 # default is false, but .rst/.md files later are autorendered, we can
192 # default is false, but .rst/.md files later are autorendered, we can
193 # overwrite autorendering by setting this GET flag
193 # overwrite autorendering by setting this GET flag
194 c.renderer = rendered or not request.GET.get('no-render', False)
194 c.renderer = rendered or not request.GET.get('no-render', False)
195
195
196 # prev link
196 # prev link
197 try:
197 try:
198 prev_commit = c.commit.prev(c.branch)
198 prev_commit = c.commit.prev(c.branch)
199 c.prev_commit = prev_commit
199 c.prev_commit = prev_commit
200 c.url_prev = url('files_home', repo_name=c.repo_name,
200 c.url_prev = url('files_home', repo_name=c.repo_name,
201 revision=prev_commit.raw_id, f_path=f_path)
201 revision=prev_commit.raw_id, f_path=f_path)
202 if c.branch:
202 if c.branch:
203 c.url_prev += '?branch=%s' % c.branch
203 c.url_prev += '?branch=%s' % c.branch
204 except (CommitDoesNotExistError, VCSError):
204 except (CommitDoesNotExistError, VCSError):
205 c.url_prev = '#'
205 c.url_prev = '#'
206 c.prev_commit = EmptyCommit()
206 c.prev_commit = EmptyCommit()
207
207
208 # next link
208 # next link
209 try:
209 try:
210 next_commit = c.commit.next(c.branch)
210 next_commit = c.commit.next(c.branch)
211 c.next_commit = next_commit
211 c.next_commit = next_commit
212 c.url_next = url('files_home', repo_name=c.repo_name,
212 c.url_next = url('files_home', repo_name=c.repo_name,
213 revision=next_commit.raw_id, f_path=f_path)
213 revision=next_commit.raw_id, f_path=f_path)
214 if c.branch:
214 if c.branch:
215 c.url_next += '?branch=%s' % c.branch
215 c.url_next += '?branch=%s' % c.branch
216 except (CommitDoesNotExistError, VCSError):
216 except (CommitDoesNotExistError, VCSError):
217 c.url_next = '#'
217 c.url_next = '#'
218 c.next_commit = EmptyCommit()
218 c.next_commit = EmptyCommit()
219
219
220 # files or dirs
220 # files or dirs
221 try:
221 try:
222 c.file = c.commit.get_node(f_path)
222 c.file = c.commit.get_node(f_path)
223 c.file_author = True
223 c.file_author = True
224 c.file_tree = ''
224 c.file_tree = ''
225 if c.file.is_file():
225 if c.file.is_file():
226 c.file_last_commit = c.file.last_commit
226 c.file_last_commit = c.file.last_commit
227 if c.annotate: # annotation has precedence over renderer
227 if c.file.size < self.cut_off_limit_file:
228 c.annotated_lines = filenode_as_annotated_lines_tokens(
228 if c.annotate: # annotation has precedence over renderer
229 c.file)
229 c.annotated_lines = filenode_as_annotated_lines_tokens(
230 else:
230 c.file
231 c.renderer = (
231 )
232 c.renderer and h.renderer_from_filename(c.file.path))
232 else:
233 if not c.renderer:
233 c.renderer = (
234 c.lines = filenode_as_lines_tokens(c.file)
234 c.renderer and h.renderer_from_filename(c.file.path)
235 )
236 if not c.renderer:
237 c.lines = filenode_as_lines_tokens(c.file)
235
238
236 c.on_branch_head = self._is_valid_head(
239 c.on_branch_head = self._is_valid_head(
237 commit_id, c.rhodecode_repo)
240 commit_id, c.rhodecode_repo)
238 c.branch_or_raw_id = c.commit.branch or c.commit.raw_id
241 c.branch_or_raw_id = c.commit.branch or c.commit.raw_id
239
242
240 author = c.file_last_commit.author
243 author = c.file_last_commit.author
241 c.authors = [(h.email(author),
244 c.authors = [(h.email(author),
242 h.person(author, 'username_or_name_or_email'))]
245 h.person(author, 'username_or_name_or_email'))]
243 else:
246 else:
244 c.authors = []
247 c.authors = []
245 c.file_tree = self._get_tree_at_commit(
248 c.file_tree = self._get_tree_at_commit(
246 repo_name, c.commit.raw_id, f_path)
249 repo_name, c.commit.raw_id, f_path)
247
250
248 except RepositoryError as e:
251 except RepositoryError as e:
249 h.flash(safe_str(e), category='error')
252 h.flash(safe_str(e), category='error')
250 raise HTTPNotFound()
253 raise HTTPNotFound()
251
254
252 if request.environ.get('HTTP_X_PJAX'):
255 if request.environ.get('HTTP_X_PJAX'):
253 return render('files/files_pjax.html')
256 return render('files/files_pjax.html')
254
257
255 return render('files/files.html')
258 return render('files/files.html')
256
259
257 @LoginRequired()
260 @LoginRequired()
258 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
261 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
259 'repository.admin')
262 'repository.admin')
260 @jsonify
263 @jsonify
261 def history(self, repo_name, revision, f_path):
264 def history(self, repo_name, revision, f_path):
262 commit = self.__get_commit_or_redirect(revision, repo_name)
265 commit = self.__get_commit_or_redirect(revision, repo_name)
263 f_path = f_path
266 f_path = f_path
264 _file = commit.get_node(f_path)
267 _file = commit.get_node(f_path)
265 if _file.is_file():
268 if _file.is_file():
266 file_history, _hist = self._get_node_history(commit, f_path)
269 file_history, _hist = self._get_node_history(commit, f_path)
267
270
268 res = []
271 res = []
269 for obj in file_history:
272 for obj in file_history:
270 res.append({
273 res.append({
271 'text': obj[1],
274 'text': obj[1],
272 'children': [{'id': o[0], 'text': o[1]} for o in obj[0]]
275 'children': [{'id': o[0], 'text': o[1]} for o in obj[0]]
273 })
276 })
274
277
275 data = {
278 data = {
276 'more': False,
279 'more': False,
277 'results': res
280 'results': res
278 }
281 }
279 return data
282 return data
280
283
281 @LoginRequired()
284 @LoginRequired()
282 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
285 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
283 'repository.admin')
286 'repository.admin')
284 def authors(self, repo_name, revision, f_path):
287 def authors(self, repo_name, revision, f_path):
285 commit = self.__get_commit_or_redirect(revision, repo_name)
288 commit = self.__get_commit_or_redirect(revision, repo_name)
286 file_node = commit.get_node(f_path)
289 file_node = commit.get_node(f_path)
287 if file_node.is_file():
290 if file_node.is_file():
288 c.file_last_commit = file_node.last_commit
291 c.file_last_commit = file_node.last_commit
289 if request.GET.get('annotate') == '1':
292 if request.GET.get('annotate') == '1':
290 # use _hist from annotation if annotation mode is on
293 # use _hist from annotation if annotation mode is on
291 commit_ids = set(x[1] for x in file_node.annotate)
294 commit_ids = set(x[1] for x in file_node.annotate)
292 _hist = (
295 _hist = (
293 c.rhodecode_repo.get_commit(commit_id)
296 c.rhodecode_repo.get_commit(commit_id)
294 for commit_id in commit_ids)
297 for commit_id in commit_ids)
295 else:
298 else:
296 _f_history, _hist = self._get_node_history(commit, f_path)
299 _f_history, _hist = self._get_node_history(commit, f_path)
297 c.file_author = False
300 c.file_author = False
298 c.authors = []
301 c.authors = []
299 for author in set(commit.author for commit in _hist):
302 for author in set(commit.author for commit in _hist):
300 c.authors.append((
303 c.authors.append((
301 h.email(author),
304 h.email(author),
302 h.person(author, 'username_or_name_or_email')))
305 h.person(author, 'username_or_name_or_email')))
303 return render('files/file_authors_box.html')
306 return render('files/file_authors_box.html')
304
307
305 @LoginRequired()
308 @LoginRequired()
306 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
309 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
307 'repository.admin')
310 'repository.admin')
308 def rawfile(self, repo_name, revision, f_path):
311 def rawfile(self, repo_name, revision, f_path):
309 """
312 """
310 Action for download as raw
313 Action for download as raw
311 """
314 """
312 commit = self.__get_commit_or_redirect(revision, repo_name)
315 commit = self.__get_commit_or_redirect(revision, repo_name)
313 file_node = self.__get_filenode_or_redirect(repo_name, commit, f_path)
316 file_node = self.__get_filenode_or_redirect(repo_name, commit, f_path)
314
317
315 response.content_disposition = 'attachment; filename=%s' % \
318 response.content_disposition = 'attachment; filename=%s' % \
316 safe_str(f_path.split(Repository.NAME_SEP)[-1])
319 safe_str(f_path.split(Repository.NAME_SEP)[-1])
317
320
318 response.content_type = file_node.mimetype
321 response.content_type = file_node.mimetype
319 charset = self._get_default_encoding()
322 charset = self._get_default_encoding()
320 if charset:
323 if charset:
321 response.charset = charset
324 response.charset = charset
322
325
323 return file_node.content
326 return file_node.content
324
327
325 @LoginRequired()
328 @LoginRequired()
326 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
329 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
327 'repository.admin')
330 'repository.admin')
328 def raw(self, repo_name, revision, f_path):
331 def raw(self, repo_name, revision, f_path):
329 """
332 """
330 Action for show as raw, some mimetypes are "rendered",
333 Action for show as raw, some mimetypes are "rendered",
331 those include images, icons.
334 those include images, icons.
332 """
335 """
333 commit = self.__get_commit_or_redirect(revision, repo_name)
336 commit = self.__get_commit_or_redirect(revision, repo_name)
334 file_node = self.__get_filenode_or_redirect(repo_name, commit, f_path)
337 file_node = self.__get_filenode_or_redirect(repo_name, commit, f_path)
335
338
336 raw_mimetype_mapping = {
339 raw_mimetype_mapping = {
337 # map original mimetype to a mimetype used for "show as raw"
340 # map original mimetype to a mimetype used for "show as raw"
338 # you can also provide a content-disposition to override the
341 # you can also provide a content-disposition to override the
339 # default "attachment" disposition.
342 # default "attachment" disposition.
340 # orig_type: (new_type, new_dispo)
343 # orig_type: (new_type, new_dispo)
341
344
342 # show images inline:
345 # show images inline:
343 # Do not re-add SVG: it is unsafe and permits XSS attacks. One can
346 # Do not re-add SVG: it is unsafe and permits XSS attacks. One can
344 # for example render an SVG with javascript inside or even render
347 # for example render an SVG with javascript inside or even render
345 # HTML.
348 # HTML.
346 'image/x-icon': ('image/x-icon', 'inline'),
349 'image/x-icon': ('image/x-icon', 'inline'),
347 'image/png': ('image/png', 'inline'),
350 'image/png': ('image/png', 'inline'),
348 'image/gif': ('image/gif', 'inline'),
351 'image/gif': ('image/gif', 'inline'),
349 'image/jpeg': ('image/jpeg', 'inline'),
352 'image/jpeg': ('image/jpeg', 'inline'),
350 }
353 }
351
354
352 mimetype = file_node.mimetype
355 mimetype = file_node.mimetype
353 try:
356 try:
354 mimetype, dispo = raw_mimetype_mapping[mimetype]
357 mimetype, dispo = raw_mimetype_mapping[mimetype]
355 except KeyError:
358 except KeyError:
356 # we don't know anything special about this, handle it safely
359 # we don't know anything special about this, handle it safely
357 if file_node.is_binary:
360 if file_node.is_binary:
358 # do same as download raw for binary files
361 # do same as download raw for binary files
359 mimetype, dispo = 'application/octet-stream', 'attachment'
362 mimetype, dispo = 'application/octet-stream', 'attachment'
360 else:
363 else:
361 # do not just use the original mimetype, but force text/plain,
364 # do not just use the original mimetype, but force text/plain,
362 # otherwise it would serve text/html and that might be unsafe.
365 # otherwise it would serve text/html and that might be unsafe.
363 # Note: underlying vcs library fakes text/plain mimetype if the
366 # Note: underlying vcs library fakes text/plain mimetype if the
364 # mimetype can not be determined and it thinks it is not
367 # mimetype can not be determined and it thinks it is not
365 # binary.This might lead to erroneous text display in some
368 # binary.This might lead to erroneous text display in some
366 # cases, but helps in other cases, like with text files
369 # cases, but helps in other cases, like with text files
367 # without extension.
370 # without extension.
368 mimetype, dispo = 'text/plain', 'inline'
371 mimetype, dispo = 'text/plain', 'inline'
369
372
370 if dispo == 'attachment':
373 if dispo == 'attachment':
371 dispo = 'attachment; filename=%s' % safe_str(
374 dispo = 'attachment; filename=%s' % safe_str(
372 f_path.split(os.sep)[-1])
375 f_path.split(os.sep)[-1])
373
376
374 response.content_disposition = dispo
377 response.content_disposition = dispo
375 response.content_type = mimetype
378 response.content_type = mimetype
376 charset = self._get_default_encoding()
379 charset = self._get_default_encoding()
377 if charset:
380 if charset:
378 response.charset = charset
381 response.charset = charset
379 return file_node.content
382 return file_node.content
380
383
381 @CSRFRequired()
384 @CSRFRequired()
382 @LoginRequired()
385 @LoginRequired()
383 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
386 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
384 def delete(self, repo_name, revision, f_path):
387 def delete(self, repo_name, revision, f_path):
385 commit_id = revision
388 commit_id = revision
386
389
387 repo = c.rhodecode_db_repo
390 repo = c.rhodecode_db_repo
388 if repo.enable_locking and repo.locked[0]:
391 if repo.enable_locking and repo.locked[0]:
389 h.flash(_('This repository has been locked by %s on %s')
392 h.flash(_('This repository has been locked by %s on %s')
390 % (h.person_by_id(repo.locked[0]),
393 % (h.person_by_id(repo.locked[0]),
391 h.format_date(h.time_to_datetime(repo.locked[1]))),
394 h.format_date(h.time_to_datetime(repo.locked[1]))),
392 'warning')
395 'warning')
393 return redirect(h.url('files_home',
396 return redirect(h.url('files_home',
394 repo_name=repo_name, revision='tip'))
397 repo_name=repo_name, revision='tip'))
395
398
396 if not self._is_valid_head(commit_id, repo.scm_instance()):
399 if not self._is_valid_head(commit_id, repo.scm_instance()):
397 h.flash(_('You can only delete files with revision '
400 h.flash(_('You can only delete files with revision '
398 'being a valid branch '), category='warning')
401 'being a valid branch '), category='warning')
399 return redirect(h.url('files_home',
402 return redirect(h.url('files_home',
400 repo_name=repo_name, revision='tip',
403 repo_name=repo_name, revision='tip',
401 f_path=f_path))
404 f_path=f_path))
402
405
403 c.commit = self.__get_commit_or_redirect(commit_id, repo_name)
406 c.commit = self.__get_commit_or_redirect(commit_id, repo_name)
404 c.file = self.__get_filenode_or_redirect(repo_name, c.commit, f_path)
407 c.file = self.__get_filenode_or_redirect(repo_name, c.commit, f_path)
405
408
406 c.default_message = _(
409 c.default_message = _(
407 'Deleted file %s via RhodeCode Enterprise') % (f_path)
410 'Deleted file %s via RhodeCode Enterprise') % (f_path)
408 c.f_path = f_path
411 c.f_path = f_path
409 node_path = f_path
412 node_path = f_path
410 author = c.rhodecode_user.full_contact
413 author = c.rhodecode_user.full_contact
411 message = request.POST.get('message') or c.default_message
414 message = request.POST.get('message') or c.default_message
412 try:
415 try:
413 nodes = {
416 nodes = {
414 node_path: {
417 node_path: {
415 'content': ''
418 'content': ''
416 }
419 }
417 }
420 }
418 self.scm_model.delete_nodes(
421 self.scm_model.delete_nodes(
419 user=c.rhodecode_user.user_id, repo=c.rhodecode_db_repo,
422 user=c.rhodecode_user.user_id, repo=c.rhodecode_db_repo,
420 message=message,
423 message=message,
421 nodes=nodes,
424 nodes=nodes,
422 parent_commit=c.commit,
425 parent_commit=c.commit,
423 author=author,
426 author=author,
424 )
427 )
425
428
426 h.flash(_('Successfully deleted file %s') % f_path,
429 h.flash(_('Successfully deleted file %s') % f_path,
427 category='success')
430 category='success')
428 except Exception:
431 except Exception:
429 msg = _('Error occurred during commit')
432 msg = _('Error occurred during commit')
430 log.exception(msg)
433 log.exception(msg)
431 h.flash(msg, category='error')
434 h.flash(msg, category='error')
432 return redirect(url('changeset_home',
435 return redirect(url('changeset_home',
433 repo_name=c.repo_name, revision='tip'))
436 repo_name=c.repo_name, revision='tip'))
434
437
435 @LoginRequired()
438 @LoginRequired()
436 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
439 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
437 def delete_home(self, repo_name, revision, f_path):
440 def delete_home(self, repo_name, revision, f_path):
438 commit_id = revision
441 commit_id = revision
439
442
440 repo = c.rhodecode_db_repo
443 repo = c.rhodecode_db_repo
441 if repo.enable_locking and repo.locked[0]:
444 if repo.enable_locking and repo.locked[0]:
442 h.flash(_('This repository has been locked by %s on %s')
445 h.flash(_('This repository has been locked by %s on %s')
443 % (h.person_by_id(repo.locked[0]),
446 % (h.person_by_id(repo.locked[0]),
444 h.format_date(h.time_to_datetime(repo.locked[1]))),
447 h.format_date(h.time_to_datetime(repo.locked[1]))),
445 'warning')
448 'warning')
446 return redirect(h.url('files_home',
449 return redirect(h.url('files_home',
447 repo_name=repo_name, revision='tip'))
450 repo_name=repo_name, revision='tip'))
448
451
449 if not self._is_valid_head(commit_id, repo.scm_instance()):
452 if not self._is_valid_head(commit_id, repo.scm_instance()):
450 h.flash(_('You can only delete files with revision '
453 h.flash(_('You can only delete files with revision '
451 'being a valid branch '), category='warning')
454 'being a valid branch '), category='warning')
452 return redirect(h.url('files_home',
455 return redirect(h.url('files_home',
453 repo_name=repo_name, revision='tip',
456 repo_name=repo_name, revision='tip',
454 f_path=f_path))
457 f_path=f_path))
455
458
456 c.commit = self.__get_commit_or_redirect(commit_id, repo_name)
459 c.commit = self.__get_commit_or_redirect(commit_id, repo_name)
457 c.file = self.__get_filenode_or_redirect(repo_name, c.commit, f_path)
460 c.file = self.__get_filenode_or_redirect(repo_name, c.commit, f_path)
458
461
459 c.default_message = _(
462 c.default_message = _(
460 'Deleted file %s via RhodeCode Enterprise') % (f_path)
463 'Deleted file %s via RhodeCode Enterprise') % (f_path)
461 c.f_path = f_path
464 c.f_path = f_path
462
465
463 return render('files/files_delete.html')
466 return render('files/files_delete.html')
464
467
465 @CSRFRequired()
468 @CSRFRequired()
466 @LoginRequired()
469 @LoginRequired()
467 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
470 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
468 def edit(self, repo_name, revision, f_path):
471 def edit(self, repo_name, revision, f_path):
469 commit_id = revision
472 commit_id = revision
470
473
471 repo = c.rhodecode_db_repo
474 repo = c.rhodecode_db_repo
472 if repo.enable_locking and repo.locked[0]:
475 if repo.enable_locking and repo.locked[0]:
473 h.flash(_('This repository has been locked by %s on %s')
476 h.flash(_('This repository has been locked by %s on %s')
474 % (h.person_by_id(repo.locked[0]),
477 % (h.person_by_id(repo.locked[0]),
475 h.format_date(h.time_to_datetime(repo.locked[1]))),
478 h.format_date(h.time_to_datetime(repo.locked[1]))),
476 'warning')
479 'warning')
477 return redirect(h.url('files_home',
480 return redirect(h.url('files_home',
478 repo_name=repo_name, revision='tip'))
481 repo_name=repo_name, revision='tip'))
479
482
480 if not self._is_valid_head(commit_id, repo.scm_instance()):
483 if not self._is_valid_head(commit_id, repo.scm_instance()):
481 h.flash(_('You can only edit files with revision '
484 h.flash(_('You can only edit files with revision '
482 'being a valid branch '), category='warning')
485 'being a valid branch '), category='warning')
483 return redirect(h.url('files_home',
486 return redirect(h.url('files_home',
484 repo_name=repo_name, revision='tip',
487 repo_name=repo_name, revision='tip',
485 f_path=f_path))
488 f_path=f_path))
486
489
487 c.commit = self.__get_commit_or_redirect(commit_id, repo_name)
490 c.commit = self.__get_commit_or_redirect(commit_id, repo_name)
488 c.file = self.__get_filenode_or_redirect(repo_name, c.commit, f_path)
491 c.file = self.__get_filenode_or_redirect(repo_name, c.commit, f_path)
489
492
490 if c.file.is_binary:
493 if c.file.is_binary:
491 return redirect(url('files_home', repo_name=c.repo_name,
494 return redirect(url('files_home', repo_name=c.repo_name,
492 revision=c.commit.raw_id, f_path=f_path))
495 revision=c.commit.raw_id, f_path=f_path))
493 c.default_message = _(
496 c.default_message = _(
494 'Edited file %s via RhodeCode Enterprise') % (f_path)
497 'Edited file %s via RhodeCode Enterprise') % (f_path)
495 c.f_path = f_path
498 c.f_path = f_path
496 old_content = c.file.content
499 old_content = c.file.content
497 sl = old_content.splitlines(1)
500 sl = old_content.splitlines(1)
498 first_line = sl[0] if sl else ''
501 first_line = sl[0] if sl else ''
499
502
500 # modes: 0 - Unix, 1 - Mac, 2 - DOS
503 # modes: 0 - Unix, 1 - Mac, 2 - DOS
501 mode = detect_mode(first_line, 0)
504 mode = detect_mode(first_line, 0)
502 content = convert_line_endings(request.POST.get('content', ''), mode)
505 content = convert_line_endings(request.POST.get('content', ''), mode)
503
506
504 message = request.POST.get('message') or c.default_message
507 message = request.POST.get('message') or c.default_message
505 org_f_path = c.file.unicode_path
508 org_f_path = c.file.unicode_path
506 filename = request.POST['filename']
509 filename = request.POST['filename']
507 org_filename = c.file.name
510 org_filename = c.file.name
508
511
509 if content == old_content and filename == org_filename:
512 if content == old_content and filename == org_filename:
510 h.flash(_('No changes'), category='warning')
513 h.flash(_('No changes'), category='warning')
511 return redirect(url('changeset_home', repo_name=c.repo_name,
514 return redirect(url('changeset_home', repo_name=c.repo_name,
512 revision='tip'))
515 revision='tip'))
513 try:
516 try:
514 mapping = {
517 mapping = {
515 org_f_path: {
518 org_f_path: {
516 'org_filename': org_f_path,
519 'org_filename': org_f_path,
517 'filename': os.path.join(c.file.dir_path, filename),
520 'filename': os.path.join(c.file.dir_path, filename),
518 'content': content,
521 'content': content,
519 'lexer': '',
522 'lexer': '',
520 'op': 'mod',
523 'op': 'mod',
521 }
524 }
522 }
525 }
523
526
524 ScmModel().update_nodes(
527 ScmModel().update_nodes(
525 user=c.rhodecode_user.user_id,
528 user=c.rhodecode_user.user_id,
526 repo=c.rhodecode_db_repo,
529 repo=c.rhodecode_db_repo,
527 message=message,
530 message=message,
528 nodes=mapping,
531 nodes=mapping,
529 parent_commit=c.commit,
532 parent_commit=c.commit,
530 )
533 )
531
534
532 h.flash(_('Successfully committed to %s') % f_path,
535 h.flash(_('Successfully committed to %s') % f_path,
533 category='success')
536 category='success')
534 except Exception:
537 except Exception:
535 msg = _('Error occurred during commit')
538 msg = _('Error occurred during commit')
536 log.exception(msg)
539 log.exception(msg)
537 h.flash(msg, category='error')
540 h.flash(msg, category='error')
538 return redirect(url('changeset_home',
541 return redirect(url('changeset_home',
539 repo_name=c.repo_name, revision='tip'))
542 repo_name=c.repo_name, revision='tip'))
540
543
541 @LoginRequired()
544 @LoginRequired()
542 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
545 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
543 def edit_home(self, repo_name, revision, f_path):
546 def edit_home(self, repo_name, revision, f_path):
544 commit_id = revision
547 commit_id = revision
545
548
546 repo = c.rhodecode_db_repo
549 repo = c.rhodecode_db_repo
547 if repo.enable_locking and repo.locked[0]:
550 if repo.enable_locking and repo.locked[0]:
548 h.flash(_('This repository has been locked by %s on %s')
551 h.flash(_('This repository has been locked by %s on %s')
549 % (h.person_by_id(repo.locked[0]),
552 % (h.person_by_id(repo.locked[0]),
550 h.format_date(h.time_to_datetime(repo.locked[1]))),
553 h.format_date(h.time_to_datetime(repo.locked[1]))),
551 'warning')
554 'warning')
552 return redirect(h.url('files_home',
555 return redirect(h.url('files_home',
553 repo_name=repo_name, revision='tip'))
556 repo_name=repo_name, revision='tip'))
554
557
555 if not self._is_valid_head(commit_id, repo.scm_instance()):
558 if not self._is_valid_head(commit_id, repo.scm_instance()):
556 h.flash(_('You can only edit files with revision '
559 h.flash(_('You can only edit files with revision '
557 'being a valid branch '), category='warning')
560 'being a valid branch '), category='warning')
558 return redirect(h.url('files_home',
561 return redirect(h.url('files_home',
559 repo_name=repo_name, revision='tip',
562 repo_name=repo_name, revision='tip',
560 f_path=f_path))
563 f_path=f_path))
561
564
562 c.commit = self.__get_commit_or_redirect(commit_id, repo_name)
565 c.commit = self.__get_commit_or_redirect(commit_id, repo_name)
563 c.file = self.__get_filenode_or_redirect(repo_name, c.commit, f_path)
566 c.file = self.__get_filenode_or_redirect(repo_name, c.commit, f_path)
564
567
565 if c.file.is_binary:
568 if c.file.is_binary:
566 return redirect(url('files_home', repo_name=c.repo_name,
569 return redirect(url('files_home', repo_name=c.repo_name,
567 revision=c.commit.raw_id, f_path=f_path))
570 revision=c.commit.raw_id, f_path=f_path))
568 c.default_message = _(
571 c.default_message = _(
569 'Edited file %s via RhodeCode Enterprise') % (f_path)
572 'Edited file %s via RhodeCode Enterprise') % (f_path)
570 c.f_path = f_path
573 c.f_path = f_path
571
574
572 return render('files/files_edit.html')
575 return render('files/files_edit.html')
573
576
574 def _is_valid_head(self, commit_id, repo):
577 def _is_valid_head(self, commit_id, repo):
575 # check if commit is a branch identifier- basically we cannot
578 # check if commit is a branch identifier- basically we cannot
576 # create multiple heads via file editing
579 # create multiple heads via file editing
577 valid_heads = repo.branches.keys() + repo.branches.values()
580 valid_heads = repo.branches.keys() + repo.branches.values()
578
581
579 if h.is_svn(repo) and not repo.is_empty():
582 if h.is_svn(repo) and not repo.is_empty():
580 # Note: Subversion only has one head, we add it here in case there
583 # Note: Subversion only has one head, we add it here in case there
581 # is no branch matched.
584 # is no branch matched.
582 valid_heads.append(repo.get_commit(commit_idx=-1).raw_id)
585 valid_heads.append(repo.get_commit(commit_idx=-1).raw_id)
583
586
584 # check if commit is a branch name or branch hash
587 # check if commit is a branch name or branch hash
585 return commit_id in valid_heads
588 return commit_id in valid_heads
586
589
587 @CSRFRequired()
590 @CSRFRequired()
588 @LoginRequired()
591 @LoginRequired()
589 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
592 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
590 def add(self, repo_name, revision, f_path):
593 def add(self, repo_name, revision, f_path):
591 repo = Repository.get_by_repo_name(repo_name)
594 repo = Repository.get_by_repo_name(repo_name)
592 if repo.enable_locking and repo.locked[0]:
595 if repo.enable_locking and repo.locked[0]:
593 h.flash(_('This repository has been locked by %s on %s')
596 h.flash(_('This repository has been locked by %s on %s')
594 % (h.person_by_id(repo.locked[0]),
597 % (h.person_by_id(repo.locked[0]),
595 h.format_date(h.time_to_datetime(repo.locked[1]))),
598 h.format_date(h.time_to_datetime(repo.locked[1]))),
596 'warning')
599 'warning')
597 return redirect(h.url('files_home',
600 return redirect(h.url('files_home',
598 repo_name=repo_name, revision='tip'))
601 repo_name=repo_name, revision='tip'))
599
602
600 r_post = request.POST
603 r_post = request.POST
601
604
602 c.commit = self.__get_commit_or_redirect(
605 c.commit = self.__get_commit_or_redirect(
603 revision, repo_name, redirect_after=False)
606 revision, repo_name, redirect_after=False)
604 if c.commit is None:
607 if c.commit is None:
605 c.commit = EmptyCommit(alias=c.rhodecode_repo.alias)
608 c.commit = EmptyCommit(alias=c.rhodecode_repo.alias)
606 c.default_message = (_('Added file via RhodeCode Enterprise'))
609 c.default_message = (_('Added file via RhodeCode Enterprise'))
607 c.f_path = f_path
610 c.f_path = f_path
608 unix_mode = 0
611 unix_mode = 0
609 content = convert_line_endings(r_post.get('content', ''), unix_mode)
612 content = convert_line_endings(r_post.get('content', ''), unix_mode)
610
613
611 message = r_post.get('message') or c.default_message
614 message = r_post.get('message') or c.default_message
612 filename = r_post.get('filename')
615 filename = r_post.get('filename')
613 location = r_post.get('location', '') # dir location
616 location = r_post.get('location', '') # dir location
614 file_obj = r_post.get('upload_file', None)
617 file_obj = r_post.get('upload_file', None)
615
618
616 if file_obj is not None and hasattr(file_obj, 'filename'):
619 if file_obj is not None and hasattr(file_obj, 'filename'):
617 filename = file_obj.filename
620 filename = file_obj.filename
618 content = file_obj.file
621 content = file_obj.file
619
622
620 if hasattr(content, 'file'):
623 if hasattr(content, 'file'):
621 # non posix systems store real file under file attr
624 # non posix systems store real file under file attr
622 content = content.file
625 content = content.file
623
626
624 # If there's no commit, redirect to repo summary
627 # If there's no commit, redirect to repo summary
625 if type(c.commit) is EmptyCommit:
628 if type(c.commit) is EmptyCommit:
626 redirect_url = "summary_home"
629 redirect_url = "summary_home"
627 else:
630 else:
628 redirect_url = "changeset_home"
631 redirect_url = "changeset_home"
629
632
630 if not filename:
633 if not filename:
631 h.flash(_('No filename'), category='warning')
634 h.flash(_('No filename'), category='warning')
632 return redirect(url(redirect_url, repo_name=c.repo_name,
635 return redirect(url(redirect_url, repo_name=c.repo_name,
633 revision='tip'))
636 revision='tip'))
634
637
635 # extract the location from filename,
638 # extract the location from filename,
636 # allows using foo/bar.txt syntax to create subdirectories
639 # allows using foo/bar.txt syntax to create subdirectories
637 subdir_loc = filename.rsplit('/', 1)
640 subdir_loc = filename.rsplit('/', 1)
638 if len(subdir_loc) == 2:
641 if len(subdir_loc) == 2:
639 location = os.path.join(location, subdir_loc[0])
642 location = os.path.join(location, subdir_loc[0])
640
643
641 # strip all crap out of file, just leave the basename
644 # strip all crap out of file, just leave the basename
642 filename = os.path.basename(filename)
645 filename = os.path.basename(filename)
643 node_path = os.path.join(location, filename)
646 node_path = os.path.join(location, filename)
644 author = c.rhodecode_user.full_contact
647 author = c.rhodecode_user.full_contact
645
648
646 try:
649 try:
647 nodes = {
650 nodes = {
648 node_path: {
651 node_path: {
649 'content': content
652 'content': content
650 }
653 }
651 }
654 }
652 self.scm_model.create_nodes(
655 self.scm_model.create_nodes(
653 user=c.rhodecode_user.user_id,
656 user=c.rhodecode_user.user_id,
654 repo=c.rhodecode_db_repo,
657 repo=c.rhodecode_db_repo,
655 message=message,
658 message=message,
656 nodes=nodes,
659 nodes=nodes,
657 parent_commit=c.commit,
660 parent_commit=c.commit,
658 author=author,
661 author=author,
659 )
662 )
660
663
661 h.flash(_('Successfully committed to %s') % node_path,
664 h.flash(_('Successfully committed to %s') % node_path,
662 category='success')
665 category='success')
663 except NonRelativePathError as e:
666 except NonRelativePathError as e:
664 h.flash(_(
667 h.flash(_(
665 'The location specified must be a relative path and must not '
668 'The location specified must be a relative path and must not '
666 'contain .. in the path'), category='warning')
669 'contain .. in the path'), category='warning')
667 return redirect(url('changeset_home', repo_name=c.repo_name,
670 return redirect(url('changeset_home', repo_name=c.repo_name,
668 revision='tip'))
671 revision='tip'))
669 except (NodeError, NodeAlreadyExistsError) as e:
672 except (NodeError, NodeAlreadyExistsError) as e:
670 h.flash(_(e), category='error')
673 h.flash(_(e), category='error')
671 except Exception:
674 except Exception:
672 msg = _('Error occurred during commit')
675 msg = _('Error occurred during commit')
673 log.exception(msg)
676 log.exception(msg)
674 h.flash(msg, category='error')
677 h.flash(msg, category='error')
675 return redirect(url('changeset_home',
678 return redirect(url('changeset_home',
676 repo_name=c.repo_name, revision='tip'))
679 repo_name=c.repo_name, revision='tip'))
677
680
678 @LoginRequired()
681 @LoginRequired()
679 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
682 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
680 def add_home(self, repo_name, revision, f_path):
683 def add_home(self, repo_name, revision, f_path):
681
684
682 repo = Repository.get_by_repo_name(repo_name)
685 repo = Repository.get_by_repo_name(repo_name)
683 if repo.enable_locking and repo.locked[0]:
686 if repo.enable_locking and repo.locked[0]:
684 h.flash(_('This repository has been locked by %s on %s')
687 h.flash(_('This repository has been locked by %s on %s')
685 % (h.person_by_id(repo.locked[0]),
688 % (h.person_by_id(repo.locked[0]),
686 h.format_date(h.time_to_datetime(repo.locked[1]))),
689 h.format_date(h.time_to_datetime(repo.locked[1]))),
687 'warning')
690 'warning')
688 return redirect(h.url('files_home',
691 return redirect(h.url('files_home',
689 repo_name=repo_name, revision='tip'))
692 repo_name=repo_name, revision='tip'))
690
693
691 c.commit = self.__get_commit_or_redirect(
694 c.commit = self.__get_commit_or_redirect(
692 revision, repo_name, redirect_after=False)
695 revision, repo_name, redirect_after=False)
693 if c.commit is None:
696 if c.commit is None:
694 c.commit = EmptyCommit(alias=c.rhodecode_repo.alias)
697 c.commit = EmptyCommit(alias=c.rhodecode_repo.alias)
695 c.default_message = (_('Added file via RhodeCode Enterprise'))
698 c.default_message = (_('Added file via RhodeCode Enterprise'))
696 c.f_path = f_path
699 c.f_path = f_path
697
700
698 return render('files/files_add.html')
701 return render('files/files_add.html')
699
702
700 @LoginRequired()
703 @LoginRequired()
701 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
704 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
702 'repository.admin')
705 'repository.admin')
703 def archivefile(self, repo_name, fname):
706 def archivefile(self, repo_name, fname):
704 fileformat = None
707 fileformat = None
705 commit_id = None
708 commit_id = None
706 ext = None
709 ext = None
707 subrepos = request.GET.get('subrepos') == 'true'
710 subrepos = request.GET.get('subrepos') == 'true'
708
711
709 for a_type, ext_data in settings.ARCHIVE_SPECS.items():
712 for a_type, ext_data in settings.ARCHIVE_SPECS.items():
710 archive_spec = fname.split(ext_data[1])
713 archive_spec = fname.split(ext_data[1])
711 if len(archive_spec) == 2 and archive_spec[1] == '':
714 if len(archive_spec) == 2 and archive_spec[1] == '':
712 fileformat = a_type or ext_data[1]
715 fileformat = a_type or ext_data[1]
713 commit_id = archive_spec[0]
716 commit_id = archive_spec[0]
714 ext = ext_data[1]
717 ext = ext_data[1]
715
718
716 dbrepo = RepoModel().get_by_repo_name(repo_name)
719 dbrepo = RepoModel().get_by_repo_name(repo_name)
717 if not dbrepo.enable_downloads:
720 if not dbrepo.enable_downloads:
718 return _('Downloads disabled')
721 return _('Downloads disabled')
719
722
720 try:
723 try:
721 commit = c.rhodecode_repo.get_commit(commit_id)
724 commit = c.rhodecode_repo.get_commit(commit_id)
722 content_type = settings.ARCHIVE_SPECS[fileformat][0]
725 content_type = settings.ARCHIVE_SPECS[fileformat][0]
723 except CommitDoesNotExistError:
726 except CommitDoesNotExistError:
724 return _('Unknown revision %s') % commit_id
727 return _('Unknown revision %s') % commit_id
725 except EmptyRepositoryError:
728 except EmptyRepositoryError:
726 return _('Empty repository')
729 return _('Empty repository')
727 except KeyError:
730 except KeyError:
728 return _('Unknown archive type')
731 return _('Unknown archive type')
729
732
730 # archive cache
733 # archive cache
731 from rhodecode import CONFIG
734 from rhodecode import CONFIG
732
735
733 archive_name = '%s-%s%s%s' % (
736 archive_name = '%s-%s%s%s' % (
734 safe_str(repo_name.replace('/', '_')),
737 safe_str(repo_name.replace('/', '_')),
735 '-sub' if subrepos else '',
738 '-sub' if subrepos else '',
736 safe_str(commit.short_id), ext)
739 safe_str(commit.short_id), ext)
737
740
738 use_cached_archive = False
741 use_cached_archive = False
739 archive_cache_enabled = CONFIG.get(
742 archive_cache_enabled = CONFIG.get(
740 'archive_cache_dir') and not request.GET.get('no_cache')
743 'archive_cache_dir') and not request.GET.get('no_cache')
741
744
742 if archive_cache_enabled:
745 if archive_cache_enabled:
743 # check if we it's ok to write
746 # check if we it's ok to write
744 if not os.path.isdir(CONFIG['archive_cache_dir']):
747 if not os.path.isdir(CONFIG['archive_cache_dir']):
745 os.makedirs(CONFIG['archive_cache_dir'])
748 os.makedirs(CONFIG['archive_cache_dir'])
746 cached_archive_path = os.path.join(
749 cached_archive_path = os.path.join(
747 CONFIG['archive_cache_dir'], archive_name)
750 CONFIG['archive_cache_dir'], archive_name)
748 if os.path.isfile(cached_archive_path):
751 if os.path.isfile(cached_archive_path):
749 log.debug('Found cached archive in %s', cached_archive_path)
752 log.debug('Found cached archive in %s', cached_archive_path)
750 fd, archive = None, cached_archive_path
753 fd, archive = None, cached_archive_path
751 use_cached_archive = True
754 use_cached_archive = True
752 else:
755 else:
753 log.debug('Archive %s is not yet cached', archive_name)
756 log.debug('Archive %s is not yet cached', archive_name)
754
757
755 if not use_cached_archive:
758 if not use_cached_archive:
756 # generate new archive
759 # generate new archive
757 fd, archive = tempfile.mkstemp()
760 fd, archive = tempfile.mkstemp()
758 log.debug('Creating new temp archive in %s' % (archive,))
761 log.debug('Creating new temp archive in %s' % (archive,))
759 try:
762 try:
760 commit.archive_repo(archive, kind=fileformat, subrepos=subrepos)
763 commit.archive_repo(archive, kind=fileformat, subrepos=subrepos)
761 except ImproperArchiveTypeError:
764 except ImproperArchiveTypeError:
762 return _('Unknown archive type')
765 return _('Unknown archive type')
763 if archive_cache_enabled:
766 if archive_cache_enabled:
764 # if we generated the archive and we have cache enabled
767 # if we generated the archive and we have cache enabled
765 # let's use this for future
768 # let's use this for future
766 log.debug('Storing new archive in %s' % (cached_archive_path,))
769 log.debug('Storing new archive in %s' % (cached_archive_path,))
767 shutil.move(archive, cached_archive_path)
770 shutil.move(archive, cached_archive_path)
768 archive = cached_archive_path
771 archive = cached_archive_path
769
772
770 def get_chunked_archive(archive):
773 def get_chunked_archive(archive):
771 with open(archive, 'rb') as stream:
774 with open(archive, 'rb') as stream:
772 while True:
775 while True:
773 data = stream.read(16 * 1024)
776 data = stream.read(16 * 1024)
774 if not data:
777 if not data:
775 if fd: # fd means we used temporary file
778 if fd: # fd means we used temporary file
776 os.close(fd)
779 os.close(fd)
777 if not archive_cache_enabled:
780 if not archive_cache_enabled:
778 log.debug('Destroying temp archive %s', archive)
781 log.debug('Destroying temp archive %s', archive)
779 os.remove(archive)
782 os.remove(archive)
780 break
783 break
781 yield data
784 yield data
782
785
783 # store download action
786 # store download action
784 action_logger(user=c.rhodecode_user,
787 action_logger(user=c.rhodecode_user,
785 action='user_downloaded_archive:%s' % archive_name,
788 action='user_downloaded_archive:%s' % archive_name,
786 repo=repo_name, ipaddr=self.ip_addr, commit=True)
789 repo=repo_name, ipaddr=self.ip_addr, commit=True)
787 response.content_disposition = str(
790 response.content_disposition = str(
788 'attachment; filename=%s' % archive_name)
791 'attachment; filename=%s' % archive_name)
789 response.content_type = str(content_type)
792 response.content_type = str(content_type)
790
793
791 return get_chunked_archive(archive)
794 return get_chunked_archive(archive)
792
795
793 @LoginRequired()
796 @LoginRequired()
794 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
797 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
795 'repository.admin')
798 'repository.admin')
796 def diff(self, repo_name, f_path):
799 def diff(self, repo_name, f_path):
797 ignore_whitespace = request.GET.get('ignorews') == '1'
800 ignore_whitespace = request.GET.get('ignorews') == '1'
798 line_context = request.GET.get('context', 3)
801 line_context = request.GET.get('context', 3)
799 diff1 = request.GET.get('diff1', '')
802 diff1 = request.GET.get('diff1', '')
800
803
801 path1, diff1 = parse_path_ref(diff1, default_path=f_path)
804 path1, diff1 = parse_path_ref(diff1, default_path=f_path)
802
805
803 diff2 = request.GET.get('diff2', '')
806 diff2 = request.GET.get('diff2', '')
804 c.action = request.GET.get('diff')
807 c.action = request.GET.get('diff')
805 c.no_changes = diff1 == diff2
808 c.no_changes = diff1 == diff2
806 c.f_path = f_path
809 c.f_path = f_path
807 c.big_diff = False
810 c.big_diff = False
808 c.ignorews_url = _ignorews_url
811 c.ignorews_url = _ignorews_url
809 c.context_url = _context_url
812 c.context_url = _context_url
810 c.changes = OrderedDict()
813 c.changes = OrderedDict()
811 c.changes[diff2] = []
814 c.changes[diff2] = []
812
815
813 if not any((diff1, diff2)):
816 if not any((diff1, diff2)):
814 h.flash(
817 h.flash(
815 'Need query parameter "diff1" or "diff2" to generate a diff.',
818 'Need query parameter "diff1" or "diff2" to generate a diff.',
816 category='error')
819 category='error')
817 raise HTTPBadRequest()
820 raise HTTPBadRequest()
818
821
819 # special case if we want a show commit_id only, it's impl here
822 # special case if we want a show commit_id only, it's impl here
820 # to reduce JS and callbacks
823 # to reduce JS and callbacks
821
824
822 if request.GET.get('show_rev') and diff1:
825 if request.GET.get('show_rev') and diff1:
823 if str2bool(request.GET.get('annotate', 'False')):
826 if str2bool(request.GET.get('annotate', 'False')):
824 _url = url('files_annotate_home', repo_name=c.repo_name,
827 _url = url('files_annotate_home', repo_name=c.repo_name,
825 revision=diff1, f_path=path1)
828 revision=diff1, f_path=path1)
826 else:
829 else:
827 _url = url('files_home', repo_name=c.repo_name,
830 _url = url('files_home', repo_name=c.repo_name,
828 revision=diff1, f_path=path1)
831 revision=diff1, f_path=path1)
829
832
830 return redirect(_url)
833 return redirect(_url)
831
834
832 try:
835 try:
833 node1 = self._get_file_node(diff1, path1)
836 node1 = self._get_file_node(diff1, path1)
834 node2 = self._get_file_node(diff2, f_path)
837 node2 = self._get_file_node(diff2, f_path)
835 except (RepositoryError, NodeError):
838 except (RepositoryError, NodeError):
836 log.exception("Exception while trying to get node from repository")
839 log.exception("Exception while trying to get node from repository")
837 return redirect(url(
840 return redirect(url(
838 'files_home', repo_name=c.repo_name, f_path=f_path))
841 'files_home', repo_name=c.repo_name, f_path=f_path))
839
842
840 if all(isinstance(node.commit, EmptyCommit)
843 if all(isinstance(node.commit, EmptyCommit)
841 for node in (node1, node2)):
844 for node in (node1, node2)):
842 raise HTTPNotFound
845 raise HTTPNotFound
843
846
844 c.commit_1 = node1.commit
847 c.commit_1 = node1.commit
845 c.commit_2 = node2.commit
848 c.commit_2 = node2.commit
846
849
847 if c.action == 'download':
850 if c.action == 'download':
848 _diff = diffs.get_gitdiff(node1, node2,
851 _diff = diffs.get_gitdiff(node1, node2,
849 ignore_whitespace=ignore_whitespace,
852 ignore_whitespace=ignore_whitespace,
850 context=line_context)
853 context=line_context)
851 diff = diffs.DiffProcessor(_diff, format='gitdiff')
854 diff = diffs.DiffProcessor(_diff, format='gitdiff')
852
855
853 diff_name = '%s_vs_%s.diff' % (diff1, diff2)
856 diff_name = '%s_vs_%s.diff' % (diff1, diff2)
854 response.content_type = 'text/plain'
857 response.content_type = 'text/plain'
855 response.content_disposition = (
858 response.content_disposition = (
856 'attachment; filename=%s' % (diff_name,)
859 'attachment; filename=%s' % (diff_name,)
857 )
860 )
858 charset = self._get_default_encoding()
861 charset = self._get_default_encoding()
859 if charset:
862 if charset:
860 response.charset = charset
863 response.charset = charset
861 return diff.as_raw()
864 return diff.as_raw()
862
865
863 elif c.action == 'raw':
866 elif c.action == 'raw':
864 _diff = diffs.get_gitdiff(node1, node2,
867 _diff = diffs.get_gitdiff(node1, node2,
865 ignore_whitespace=ignore_whitespace,
868 ignore_whitespace=ignore_whitespace,
866 context=line_context)
869 context=line_context)
867 diff = diffs.DiffProcessor(_diff, format='gitdiff')
870 diff = diffs.DiffProcessor(_diff, format='gitdiff')
868 response.content_type = 'text/plain'
871 response.content_type = 'text/plain'
869 charset = self._get_default_encoding()
872 charset = self._get_default_encoding()
870 if charset:
873 if charset:
871 response.charset = charset
874 response.charset = charset
872 return diff.as_raw()
875 return diff.as_raw()
873
876
874 else:
877 else:
875 fid = h.FID(diff2, node2.path)
878 fid = h.FID(diff2, node2.path)
876 line_context_lcl = get_line_ctx(fid, request.GET)
879 line_context_lcl = get_line_ctx(fid, request.GET)
877 ign_whitespace_lcl = get_ignore_ws(fid, request.GET)
880 ign_whitespace_lcl = get_ignore_ws(fid, request.GET)
878
881
879 __, commit1, commit2, diff, st, data = diffs.wrapped_diff(
882 __, commit1, commit2, diff, st, data = diffs.wrapped_diff(
880 filenode_old=node1,
883 filenode_old=node1,
881 filenode_new=node2,
884 filenode_new=node2,
882 diff_limit=self.cut_off_limit_diff,
885 diff_limit=self.cut_off_limit_diff,
883 file_limit=self.cut_off_limit_file,
886 file_limit=self.cut_off_limit_file,
884 show_full_diff=request.GET.get('fulldiff'),
887 show_full_diff=request.GET.get('fulldiff'),
885 ignore_whitespace=ign_whitespace_lcl,
888 ignore_whitespace=ign_whitespace_lcl,
886 line_context=line_context_lcl,)
889 line_context=line_context_lcl,)
887
890
888 c.lines_added = data['stats']['added'] if data else 0
891 c.lines_added = data['stats']['added'] if data else 0
889 c.lines_deleted = data['stats']['deleted'] if data else 0
892 c.lines_deleted = data['stats']['deleted'] if data else 0
890 c.files = [data]
893 c.files = [data]
891 c.commit_ranges = [c.commit_1, c.commit_2]
894 c.commit_ranges = [c.commit_1, c.commit_2]
892 c.ancestor = None
895 c.ancestor = None
893 c.statuses = []
896 c.statuses = []
894 c.target_repo = c.rhodecode_db_repo
897 c.target_repo = c.rhodecode_db_repo
895 c.filename1 = node1.path
898 c.filename1 = node1.path
896 c.filename = node2.path
899 c.filename = node2.path
897 c.binary_file = node1.is_binary or node2.is_binary
900 c.binary_file = node1.is_binary or node2.is_binary
898 operation = data['operation'] if data else ''
901 operation = data['operation'] if data else ''
899
902
900 commit_changes = {
903 commit_changes = {
901 # TODO: it's passing the old file to the diff to keep the
904 # TODO: it's passing the old file to the diff to keep the
902 # standard but this is not being used for this template,
905 # standard but this is not being used for this template,
903 # but might need both files in the future or a more standard
906 # but might need both files in the future or a more standard
904 # way to work with that
907 # way to work with that
905 'fid': [commit1, commit2, operation,
908 'fid': [commit1, commit2, operation,
906 c.filename, diff, st, data]
909 c.filename, diff, st, data]
907 }
910 }
908
911
909 c.changes = commit_changes
912 c.changes = commit_changes
910
913
911 return render('files/file_diff.html')
914 return render('files/file_diff.html')
912
915
913 @LoginRequired()
916 @LoginRequired()
914 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
917 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
915 'repository.admin')
918 'repository.admin')
916 def diff_2way(self, repo_name, f_path):
919 def diff_2way(self, repo_name, f_path):
917 diff1 = request.GET.get('diff1', '')
920 diff1 = request.GET.get('diff1', '')
918 diff2 = request.GET.get('diff2', '')
921 diff2 = request.GET.get('diff2', '')
919
922
920 nodes = []
923 nodes = []
921 unknown_commits = []
924 unknown_commits = []
922 for commit in [diff1, diff2]:
925 for commit in [diff1, diff2]:
923 try:
926 try:
924 nodes.append(self._get_file_node(commit, f_path))
927 nodes.append(self._get_file_node(commit, f_path))
925 except (RepositoryError, NodeError):
928 except (RepositoryError, NodeError):
926 log.exception('%(commit)s does not exist' % {'commit': commit})
929 log.exception('%(commit)s does not exist' % {'commit': commit})
927 unknown_commits.append(commit)
930 unknown_commits.append(commit)
928 h.flash(h.literal(
931 h.flash(h.literal(
929 _('Commit %(commit)s does not exist.') % {'commit': commit}
932 _('Commit %(commit)s does not exist.') % {'commit': commit}
930 ), category='error')
933 ), category='error')
931
934
932 if unknown_commits:
935 if unknown_commits:
933 return redirect(url('files_home', repo_name=c.repo_name,
936 return redirect(url('files_home', repo_name=c.repo_name,
934 f_path=f_path))
937 f_path=f_path))
935
938
936 if all(isinstance(node.commit, EmptyCommit) for node in nodes):
939 if all(isinstance(node.commit, EmptyCommit) for node in nodes):
937 raise HTTPNotFound
940 raise HTTPNotFound
938
941
939 node1, node2 = nodes
942 node1, node2 = nodes
940
943
941 f_gitdiff = diffs.get_gitdiff(node1, node2, ignore_whitespace=False)
944 f_gitdiff = diffs.get_gitdiff(node1, node2, ignore_whitespace=False)
942 diff_processor = diffs.DiffProcessor(f_gitdiff, format='gitdiff')
945 diff_processor = diffs.DiffProcessor(f_gitdiff, format='gitdiff')
943 diff_data = diff_processor.prepare()
946 diff_data = diff_processor.prepare()
944
947
945 if not diff_data or diff_data[0]['raw_diff'] == '':
948 if not diff_data or diff_data[0]['raw_diff'] == '':
946 h.flash(h.literal(_('%(file_path)s has not changed '
949 h.flash(h.literal(_('%(file_path)s has not changed '
947 'between %(commit_1)s and %(commit_2)s.') % {
950 'between %(commit_1)s and %(commit_2)s.') % {
948 'file_path': f_path,
951 'file_path': f_path,
949 'commit_1': node1.commit.id,
952 'commit_1': node1.commit.id,
950 'commit_2': node2.commit.id
953 'commit_2': node2.commit.id
951 }), category='error')
954 }), category='error')
952 return redirect(url('files_home', repo_name=c.repo_name,
955 return redirect(url('files_home', repo_name=c.repo_name,
953 f_path=f_path))
956 f_path=f_path))
954
957
955 c.diff_data = diff_data[0]
958 c.diff_data = diff_data[0]
956 c.FID = h.FID(diff2, node2.path)
959 c.FID = h.FID(diff2, node2.path)
957 # cleanup some unneeded data
960 # cleanup some unneeded data
958 del c.diff_data['raw_diff']
961 del c.diff_data['raw_diff']
959 del c.diff_data['chunks']
962 del c.diff_data['chunks']
960
963
961 c.node1 = node1
964 c.node1 = node1
962 c.commit_1 = node1.commit
965 c.commit_1 = node1.commit
963 c.node2 = node2
966 c.node2 = node2
964 c.commit_2 = node2.commit
967 c.commit_2 = node2.commit
965
968
966 return render('files/diff_2way.html')
969 return render('files/diff_2way.html')
967
970
968 def _get_file_node(self, commit_id, f_path):
971 def _get_file_node(self, commit_id, f_path):
969 if commit_id not in ['', None, 'None', '0' * 12, '0' * 40]:
972 if commit_id not in ['', None, 'None', '0' * 12, '0' * 40]:
970 commit = c.rhodecode_repo.get_commit(commit_id=commit_id)
973 commit = c.rhodecode_repo.get_commit(commit_id=commit_id)
971 try:
974 try:
972 node = commit.get_node(f_path)
975 node = commit.get_node(f_path)
973 if node.is_dir():
976 if node.is_dir():
974 raise NodeError('%s path is a %s not a file'
977 raise NodeError('%s path is a %s not a file'
975 % (node, type(node)))
978 % (node, type(node)))
976 except NodeDoesNotExistError:
979 except NodeDoesNotExistError:
977 commit = EmptyCommit(
980 commit = EmptyCommit(
978 commit_id=commit_id,
981 commit_id=commit_id,
979 idx=commit.idx,
982 idx=commit.idx,
980 repo=commit.repository,
983 repo=commit.repository,
981 alias=commit.repository.alias,
984 alias=commit.repository.alias,
982 message=commit.message,
985 message=commit.message,
983 author=commit.author,
986 author=commit.author,
984 date=commit.date)
987 date=commit.date)
985 node = FileNode(f_path, '', commit=commit)
988 node = FileNode(f_path, '', commit=commit)
986 else:
989 else:
987 commit = EmptyCommit(
990 commit = EmptyCommit(
988 repo=c.rhodecode_repo,
991 repo=c.rhodecode_repo,
989 alias=c.rhodecode_repo.alias)
992 alias=c.rhodecode_repo.alias)
990 node = FileNode(f_path, '', commit=commit)
993 node = FileNode(f_path, '', commit=commit)
991 return node
994 return node
992
995
993 def _get_node_history(self, commit, f_path, commits=None):
996 def _get_node_history(self, commit, f_path, commits=None):
994 """
997 """
995 get commit history for given node
998 get commit history for given node
996
999
997 :param commit: commit to calculate history
1000 :param commit: commit to calculate history
998 :param f_path: path for node to calculate history for
1001 :param f_path: path for node to calculate history for
999 :param commits: if passed don't calculate history and take
1002 :param commits: if passed don't calculate history and take
1000 commits defined in this list
1003 commits defined in this list
1001 """
1004 """
1002 # calculate history based on tip
1005 # calculate history based on tip
1003 tip = c.rhodecode_repo.get_commit()
1006 tip = c.rhodecode_repo.get_commit()
1004 if commits is None:
1007 if commits is None:
1005 pre_load = ["author", "branch"]
1008 pre_load = ["author", "branch"]
1006 try:
1009 try:
1007 commits = tip.get_file_history(f_path, pre_load=pre_load)
1010 commits = tip.get_file_history(f_path, pre_load=pre_load)
1008 except (NodeDoesNotExistError, CommitError):
1011 except (NodeDoesNotExistError, CommitError):
1009 # this node is not present at tip!
1012 # this node is not present at tip!
1010 commits = commit.get_file_history(f_path, pre_load=pre_load)
1013 commits = commit.get_file_history(f_path, pre_load=pre_load)
1011
1014
1012 history = []
1015 history = []
1013 commits_group = ([], _("Changesets"))
1016 commits_group = ([], _("Changesets"))
1014 for commit in commits:
1017 for commit in commits:
1015 branch = ' (%s)' % commit.branch if commit.branch else ''
1018 branch = ' (%s)' % commit.branch if commit.branch else ''
1016 n_desc = 'r%s:%s%s' % (commit.idx, commit.short_id, branch)
1019 n_desc = 'r%s:%s%s' % (commit.idx, commit.short_id, branch)
1017 commits_group[0].append((commit.raw_id, n_desc,))
1020 commits_group[0].append((commit.raw_id, n_desc,))
1018 history.append(commits_group)
1021 history.append(commits_group)
1019
1022
1020 symbolic_reference = self._symbolic_reference
1023 symbolic_reference = self._symbolic_reference
1021
1024
1022 if c.rhodecode_repo.alias == 'svn':
1025 if c.rhodecode_repo.alias == 'svn':
1023 adjusted_f_path = self._adjust_file_path_for_svn(
1026 adjusted_f_path = self._adjust_file_path_for_svn(
1024 f_path, c.rhodecode_repo)
1027 f_path, c.rhodecode_repo)
1025 if adjusted_f_path != f_path:
1028 if adjusted_f_path != f_path:
1026 log.debug(
1029 log.debug(
1027 'Recognized svn tag or branch in file "%s", using svn '
1030 'Recognized svn tag or branch in file "%s", using svn '
1028 'specific symbolic references', f_path)
1031 'specific symbolic references', f_path)
1029 f_path = adjusted_f_path
1032 f_path = adjusted_f_path
1030 symbolic_reference = self._symbolic_reference_svn
1033 symbolic_reference = self._symbolic_reference_svn
1031
1034
1032 branches = self._create_references(
1035 branches = self._create_references(
1033 c.rhodecode_repo.branches, symbolic_reference, f_path)
1036 c.rhodecode_repo.branches, symbolic_reference, f_path)
1034 branches_group = (branches, _("Branches"))
1037 branches_group = (branches, _("Branches"))
1035
1038
1036 tags = self._create_references(
1039 tags = self._create_references(
1037 c.rhodecode_repo.tags, symbolic_reference, f_path)
1040 c.rhodecode_repo.tags, symbolic_reference, f_path)
1038 tags_group = (tags, _("Tags"))
1041 tags_group = (tags, _("Tags"))
1039
1042
1040 history.append(branches_group)
1043 history.append(branches_group)
1041 history.append(tags_group)
1044 history.append(tags_group)
1042
1045
1043 return history, commits
1046 return history, commits
1044
1047
1045 def _adjust_file_path_for_svn(self, f_path, repo):
1048 def _adjust_file_path_for_svn(self, f_path, repo):
1046 """
1049 """
1047 Computes the relative path of `f_path`.
1050 Computes the relative path of `f_path`.
1048
1051
1049 This is mainly based on prefix matching of the recognized tags and
1052 This is mainly based on prefix matching of the recognized tags and
1050 branches in the underlying repository.
1053 branches in the underlying repository.
1051 """
1054 """
1052 tags_and_branches = itertools.chain(
1055 tags_and_branches = itertools.chain(
1053 repo.branches.iterkeys(),
1056 repo.branches.iterkeys(),
1054 repo.tags.iterkeys())
1057 repo.tags.iterkeys())
1055 tags_and_branches = sorted(tags_and_branches, key=len, reverse=True)
1058 tags_and_branches = sorted(tags_and_branches, key=len, reverse=True)
1056
1059
1057 for name in tags_and_branches:
1060 for name in tags_and_branches:
1058 if f_path.startswith(name + '/'):
1061 if f_path.startswith(name + '/'):
1059 f_path = vcspath.relpath(f_path, name)
1062 f_path = vcspath.relpath(f_path, name)
1060 break
1063 break
1061 return f_path
1064 return f_path
1062
1065
1063 def _create_references(
1066 def _create_references(
1064 self, branches_or_tags, symbolic_reference, f_path):
1067 self, branches_or_tags, symbolic_reference, f_path):
1065 items = []
1068 items = []
1066 for name, commit_id in branches_or_tags.items():
1069 for name, commit_id in branches_or_tags.items():
1067 sym_ref = symbolic_reference(commit_id, name, f_path)
1070 sym_ref = symbolic_reference(commit_id, name, f_path)
1068 items.append((sym_ref, name))
1071 items.append((sym_ref, name))
1069 return items
1072 return items
1070
1073
1071 def _symbolic_reference(self, commit_id, name, f_path):
1074 def _symbolic_reference(self, commit_id, name, f_path):
1072 return commit_id
1075 return commit_id
1073
1076
1074 def _symbolic_reference_svn(self, commit_id, name, f_path):
1077 def _symbolic_reference_svn(self, commit_id, name, f_path):
1075 new_f_path = vcspath.join(name, f_path)
1078 new_f_path = vcspath.join(name, f_path)
1076 return u'%s@%s' % (new_f_path, commit_id)
1079 return u'%s@%s' % (new_f_path, commit_id)
1077
1080
1078 @LoginRequired()
1081 @LoginRequired()
1079 @XHRRequired()
1082 @XHRRequired()
1080 @HasRepoPermissionAnyDecorator(
1083 @HasRepoPermissionAnyDecorator(
1081 'repository.read', 'repository.write', 'repository.admin')
1084 'repository.read', 'repository.write', 'repository.admin')
1082 @jsonify
1085 @jsonify
1083 def nodelist(self, repo_name, revision, f_path):
1086 def nodelist(self, repo_name, revision, f_path):
1084 commit = self.__get_commit_or_redirect(revision, repo_name)
1087 commit = self.__get_commit_or_redirect(revision, repo_name)
1085
1088
1086 metadata = self._get_nodelist_at_commit(
1089 metadata = self._get_nodelist_at_commit(
1087 repo_name, commit.raw_id, f_path)
1090 repo_name, commit.raw_id, f_path)
1088 return {'nodes': metadata}
1091 return {'nodes': metadata}
1089
1092
1090 @LoginRequired()
1093 @LoginRequired()
1091 @XHRRequired()
1094 @XHRRequired()
1092 @HasRepoPermissionAnyDecorator(
1095 @HasRepoPermissionAnyDecorator(
1093 'repository.read', 'repository.write', 'repository.admin')
1096 'repository.read', 'repository.write', 'repository.admin')
1094 def nodetree_full(self, repo_name, commit_id, f_path):
1097 def nodetree_full(self, repo_name, commit_id, f_path):
1095 """
1098 """
1096 Returns rendered html of file tree that contains commit date,
1099 Returns rendered html of file tree that contains commit date,
1097 author, revision for the specified combination of
1100 author, revision for the specified combination of
1098 repo, commit_id and file path
1101 repo, commit_id and file path
1099
1102
1100 :param repo_name: name of the repository
1103 :param repo_name: name of the repository
1101 :param commit_id: commit_id of file tree
1104 :param commit_id: commit_id of file tree
1102 :param f_path: file path of the requested directory
1105 :param f_path: file path of the requested directory
1103 """
1106 """
1104
1107
1105 commit = self.__get_commit_or_redirect(commit_id, repo_name)
1108 commit = self.__get_commit_or_redirect(commit_id, repo_name)
1106 try:
1109 try:
1107 dir_node = commit.get_node(f_path)
1110 dir_node = commit.get_node(f_path)
1108 except RepositoryError as e:
1111 except RepositoryError as e:
1109 return 'error {}'.format(safe_str(e))
1112 return 'error {}'.format(safe_str(e))
1110
1113
1111 if dir_node.is_file():
1114 if dir_node.is_file():
1112 return ''
1115 return ''
1113
1116
1114 c.file = dir_node
1117 c.file = dir_node
1115 c.commit = commit
1118 c.commit = commit
1116
1119
1117 # using force=True here, make a little trick. We flush the cache and
1120 # using force=True here, make a little trick. We flush the cache and
1118 # compute it using the same key as without full_load, so the fully
1121 # compute it using the same key as without full_load, so the fully
1119 # loaded cached tree is now returned instead of partial
1122 # loaded cached tree is now returned instead of partial
1120 return self._get_tree_at_commit(
1123 return self._get_tree_at_commit(
1121 repo_name, commit.raw_id, dir_node.path, full_load=True,
1124 repo_name, commit.raw_id, dir_node.path, full_load=True,
1122 force=True)
1125 force=True)
General Comments 0
You need to be logged in to leave comments. Login now