##// END OF EJS Templates
files: allow to show inline pdf in browser using embedded file from vcs
marcink -
r1581:9d336780 default
parent child Browse files
Show More
@@ -1,1097 +1,1098 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 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.mako')
147 return render('files/files_browser_tree.mako')
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.lf_node = c.file.get_largefile_node()
226 c.lf_node = c.file.get_largefile_node()
227
227
228 c.file_source_page = 'true'
228 c.file_source_page = 'true'
229 c.file_last_commit = c.file.last_commit
229 c.file_last_commit = c.file.last_commit
230 if c.file.size < self.cut_off_limit_file:
230 if c.file.size < self.cut_off_limit_file:
231 if c.annotate: # annotation has precedence over renderer
231 if c.annotate: # annotation has precedence over renderer
232 c.annotated_lines = filenode_as_annotated_lines_tokens(
232 c.annotated_lines = filenode_as_annotated_lines_tokens(
233 c.file
233 c.file
234 )
234 )
235 else:
235 else:
236 c.renderer = (
236 c.renderer = (
237 c.renderer and h.renderer_from_filename(c.file.path)
237 c.renderer and h.renderer_from_filename(c.file.path)
238 )
238 )
239 if not c.renderer:
239 if not c.renderer:
240 c.lines = filenode_as_lines_tokens(c.file)
240 c.lines = filenode_as_lines_tokens(c.file)
241
241
242 c.on_branch_head = self._is_valid_head(
242 c.on_branch_head = self._is_valid_head(
243 commit_id, c.rhodecode_repo)
243 commit_id, c.rhodecode_repo)
244 c.branch_or_raw_id = c.commit.branch or c.commit.raw_id
244 c.branch_or_raw_id = c.commit.branch or c.commit.raw_id
245
245
246 author = c.file_last_commit.author
246 author = c.file_last_commit.author
247 c.authors = [(h.email(author),
247 c.authors = [(h.email(author),
248 h.person(author, 'username_or_name_or_email'))]
248 h.person(author, 'username_or_name_or_email'))]
249 else:
249 else:
250 c.file_source_page = 'false'
250 c.file_source_page = 'false'
251 c.authors = []
251 c.authors = []
252 c.file_tree = self._get_tree_at_commit(
252 c.file_tree = self._get_tree_at_commit(
253 repo_name, c.commit.raw_id, f_path)
253 repo_name, c.commit.raw_id, f_path)
254
254
255 except RepositoryError as e:
255 except RepositoryError as e:
256 h.flash(safe_str(e), category='error')
256 h.flash(safe_str(e), category='error')
257 raise HTTPNotFound()
257 raise HTTPNotFound()
258
258
259 if request.environ.get('HTTP_X_PJAX'):
259 if request.environ.get('HTTP_X_PJAX'):
260 return render('files/files_pjax.mako')
260 return render('files/files_pjax.mako')
261
261
262 return render('files/files.mako')
262 return render('files/files.mako')
263
263
264 @LoginRequired()
264 @LoginRequired()
265 @HasRepoPermissionAnyDecorator(
265 @HasRepoPermissionAnyDecorator(
266 'repository.read', 'repository.write', 'repository.admin')
266 'repository.read', 'repository.write', 'repository.admin')
267 def annotate_previous(self, repo_name, revision, f_path):
267 def annotate_previous(self, repo_name, revision, f_path):
268
268
269 commit_id = revision
269 commit_id = revision
270 commit = self.__get_commit_or_redirect(commit_id, repo_name)
270 commit = self.__get_commit_or_redirect(commit_id, repo_name)
271 prev_commit_id = commit.raw_id
271 prev_commit_id = commit.raw_id
272
272
273 f_path = f_path
273 f_path = f_path
274 is_file = False
274 is_file = False
275 try:
275 try:
276 _file = commit.get_node(f_path)
276 _file = commit.get_node(f_path)
277 is_file = _file.is_file()
277 is_file = _file.is_file()
278 except (NodeDoesNotExistError, CommitDoesNotExistError, VCSError):
278 except (NodeDoesNotExistError, CommitDoesNotExistError, VCSError):
279 pass
279 pass
280
280
281 if is_file:
281 if is_file:
282 history = commit.get_file_history(f_path)
282 history = commit.get_file_history(f_path)
283 prev_commit_id = history[1].raw_id \
283 prev_commit_id = history[1].raw_id \
284 if len(history) > 1 else prev_commit_id
284 if len(history) > 1 else prev_commit_id
285
285
286 return redirect(h.url(
286 return redirect(h.url(
287 'files_annotate_home', repo_name=repo_name,
287 'files_annotate_home', repo_name=repo_name,
288 revision=prev_commit_id, f_path=f_path))
288 revision=prev_commit_id, f_path=f_path))
289
289
290 @LoginRequired()
290 @LoginRequired()
291 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
291 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
292 'repository.admin')
292 'repository.admin')
293 @jsonify
293 @jsonify
294 def history(self, repo_name, revision, f_path):
294 def history(self, repo_name, revision, f_path):
295 commit = self.__get_commit_or_redirect(revision, repo_name)
295 commit = self.__get_commit_or_redirect(revision, repo_name)
296 f_path = f_path
296 f_path = f_path
297 _file = commit.get_node(f_path)
297 _file = commit.get_node(f_path)
298 if _file.is_file():
298 if _file.is_file():
299 file_history, _hist = self._get_node_history(commit, f_path)
299 file_history, _hist = self._get_node_history(commit, f_path)
300
300
301 res = []
301 res = []
302 for obj in file_history:
302 for obj in file_history:
303 res.append({
303 res.append({
304 'text': obj[1],
304 'text': obj[1],
305 'children': [{'id': o[0], 'text': o[1]} for o in obj[0]]
305 'children': [{'id': o[0], 'text': o[1]} for o in obj[0]]
306 })
306 })
307
307
308 data = {
308 data = {
309 'more': False,
309 'more': False,
310 'results': res
310 'results': res
311 }
311 }
312 return data
312 return data
313
313
314 @LoginRequired()
314 @LoginRequired()
315 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
315 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
316 'repository.admin')
316 'repository.admin')
317 def authors(self, repo_name, revision, f_path):
317 def authors(self, repo_name, revision, f_path):
318 commit = self.__get_commit_or_redirect(revision, repo_name)
318 commit = self.__get_commit_or_redirect(revision, repo_name)
319 file_node = commit.get_node(f_path)
319 file_node = commit.get_node(f_path)
320 if file_node.is_file():
320 if file_node.is_file():
321 c.file_last_commit = file_node.last_commit
321 c.file_last_commit = file_node.last_commit
322 if request.GET.get('annotate') == '1':
322 if request.GET.get('annotate') == '1':
323 # use _hist from annotation if annotation mode is on
323 # use _hist from annotation if annotation mode is on
324 commit_ids = set(x[1] for x in file_node.annotate)
324 commit_ids = set(x[1] for x in file_node.annotate)
325 _hist = (
325 _hist = (
326 c.rhodecode_repo.get_commit(commit_id)
326 c.rhodecode_repo.get_commit(commit_id)
327 for commit_id in commit_ids)
327 for commit_id in commit_ids)
328 else:
328 else:
329 _f_history, _hist = self._get_node_history(commit, f_path)
329 _f_history, _hist = self._get_node_history(commit, f_path)
330 c.file_author = False
330 c.file_author = False
331 c.authors = []
331 c.authors = []
332 for author in set(commit.author for commit in _hist):
332 for author in set(commit.author for commit in _hist):
333 c.authors.append((
333 c.authors.append((
334 h.email(author),
334 h.email(author),
335 h.person(author, 'username_or_name_or_email')))
335 h.person(author, 'username_or_name_or_email')))
336 return render('files/file_authors_box.mako')
336 return render('files/file_authors_box.mako')
337
337
338 @LoginRequired()
338 @LoginRequired()
339 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
339 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
340 'repository.admin')
340 'repository.admin')
341 def rawfile(self, repo_name, revision, f_path):
341 def rawfile(self, repo_name, revision, f_path):
342 """
342 """
343 Action for download as raw
343 Action for download as raw
344 """
344 """
345 commit = self.__get_commit_or_redirect(revision, repo_name)
345 commit = self.__get_commit_or_redirect(revision, repo_name)
346 file_node = self.__get_filenode_or_redirect(repo_name, commit, f_path)
346 file_node = self.__get_filenode_or_redirect(repo_name, commit, f_path)
347
347
348 if request.GET.get('lf'):
348 if request.GET.get('lf'):
349 # only if lf get flag is passed, we download this file
349 # only if lf get flag is passed, we download this file
350 # as LFS/Largefile
350 # as LFS/Largefile
351 lf_node = file_node.get_largefile_node()
351 lf_node = file_node.get_largefile_node()
352 if lf_node:
352 if lf_node:
353 # overwrite our pointer with the REAL large-file
353 # overwrite our pointer with the REAL large-file
354 file_node = lf_node
354 file_node = lf_node
355
355
356 response.content_disposition = 'attachment; filename=%s' % \
356 response.content_disposition = 'attachment; filename=%s' % \
357 safe_str(f_path.split(Repository.NAME_SEP)[-1])
357 safe_str(f_path.split(Repository.NAME_SEP)[-1])
358
358
359 response.content_type = file_node.mimetype
359 response.content_type = file_node.mimetype
360 charset = self._get_default_encoding()
360 charset = self._get_default_encoding()
361 if charset:
361 if charset:
362 response.charset = charset
362 response.charset = charset
363
363
364 return file_node.content
364 return file_node.content
365
365
366 @LoginRequired()
366 @LoginRequired()
367 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
367 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
368 'repository.admin')
368 'repository.admin')
369 def raw(self, repo_name, revision, f_path):
369 def raw(self, repo_name, revision, f_path):
370 """
370 """
371 Action for show as raw, some mimetypes are "rendered",
371 Action for show as raw, some mimetypes are "rendered",
372 those include images, icons.
372 those include images, icons.
373 """
373 """
374 commit = self.__get_commit_or_redirect(revision, repo_name)
374 commit = self.__get_commit_or_redirect(revision, repo_name)
375 file_node = self.__get_filenode_or_redirect(repo_name, commit, f_path)
375 file_node = self.__get_filenode_or_redirect(repo_name, commit, f_path)
376
376
377 raw_mimetype_mapping = {
377 raw_mimetype_mapping = {
378 # map original mimetype to a mimetype used for "show as raw"
378 # map original mimetype to a mimetype used for "show as raw"
379 # you can also provide a content-disposition to override the
379 # you can also provide a content-disposition to override the
380 # default "attachment" disposition.
380 # default "attachment" disposition.
381 # orig_type: (new_type, new_dispo)
381 # orig_type: (new_type, new_dispo)
382
382
383 # show images inline:
383 # show images inline:
384 # Do not re-add SVG: it is unsafe and permits XSS attacks. One can
384 # Do not re-add SVG: it is unsafe and permits XSS attacks. One can
385 # for example render an SVG with javascript inside or even render
385 # for example render an SVG with javascript inside or even render
386 # HTML.
386 # HTML.
387 'image/x-icon': ('image/x-icon', 'inline'),
387 'image/x-icon': ('image/x-icon', 'inline'),
388 'image/png': ('image/png', 'inline'),
388 'image/png': ('image/png', 'inline'),
389 'image/gif': ('image/gif', 'inline'),
389 'image/gif': ('image/gif', 'inline'),
390 'image/jpeg': ('image/jpeg', 'inline'),
390 'image/jpeg': ('image/jpeg', 'inline'),
391 'application/pdf': ('application/pdf', 'inline'),
391 }
392 }
392
393
393 mimetype = file_node.mimetype
394 mimetype = file_node.mimetype
394 try:
395 try:
395 mimetype, dispo = raw_mimetype_mapping[mimetype]
396 mimetype, dispo = raw_mimetype_mapping[mimetype]
396 except KeyError:
397 except KeyError:
397 # we don't know anything special about this, handle it safely
398 # we don't know anything special about this, handle it safely
398 if file_node.is_binary:
399 if file_node.is_binary:
399 # do same as download raw for binary files
400 # do same as download raw for binary files
400 mimetype, dispo = 'application/octet-stream', 'attachment'
401 mimetype, dispo = 'application/octet-stream', 'attachment'
401 else:
402 else:
402 # do not just use the original mimetype, but force text/plain,
403 # do not just use the original mimetype, but force text/plain,
403 # otherwise it would serve text/html and that might be unsafe.
404 # otherwise it would serve text/html and that might be unsafe.
404 # Note: underlying vcs library fakes text/plain mimetype if the
405 # Note: underlying vcs library fakes text/plain mimetype if the
405 # mimetype can not be determined and it thinks it is not
406 # mimetype can not be determined and it thinks it is not
406 # binary.This might lead to erroneous text display in some
407 # binary.This might lead to erroneous text display in some
407 # cases, but helps in other cases, like with text files
408 # cases, but helps in other cases, like with text files
408 # without extension.
409 # without extension.
409 mimetype, dispo = 'text/plain', 'inline'
410 mimetype, dispo = 'text/plain', 'inline'
410
411
411 if dispo == 'attachment':
412 if dispo == 'attachment':
412 dispo = 'attachment; filename=%s' % safe_str(
413 dispo = 'attachment; filename=%s' % safe_str(
413 f_path.split(os.sep)[-1])
414 f_path.split(os.sep)[-1])
414
415
415 response.content_disposition = dispo
416 response.content_disposition = dispo
416 response.content_type = mimetype
417 response.content_type = mimetype
417 charset = self._get_default_encoding()
418 charset = self._get_default_encoding()
418 if charset:
419 if charset:
419 response.charset = charset
420 response.charset = charset
420 return file_node.content
421 return file_node.content
421
422
422 @CSRFRequired()
423 @CSRFRequired()
423 @LoginRequired()
424 @LoginRequired()
424 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
425 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
425 def delete(self, repo_name, revision, f_path):
426 def delete(self, repo_name, revision, f_path):
426 commit_id = revision
427 commit_id = revision
427
428
428 repo = c.rhodecode_db_repo
429 repo = c.rhodecode_db_repo
429 if repo.enable_locking and repo.locked[0]:
430 if repo.enable_locking and repo.locked[0]:
430 h.flash(_('This repository has been locked by %s on %s')
431 h.flash(_('This repository has been locked by %s on %s')
431 % (h.person_by_id(repo.locked[0]),
432 % (h.person_by_id(repo.locked[0]),
432 h.format_date(h.time_to_datetime(repo.locked[1]))),
433 h.format_date(h.time_to_datetime(repo.locked[1]))),
433 'warning')
434 'warning')
434 return redirect(h.url('files_home',
435 return redirect(h.url('files_home',
435 repo_name=repo_name, revision='tip'))
436 repo_name=repo_name, revision='tip'))
436
437
437 if not self._is_valid_head(commit_id, repo.scm_instance()):
438 if not self._is_valid_head(commit_id, repo.scm_instance()):
438 h.flash(_('You can only delete files with revision '
439 h.flash(_('You can only delete files with revision '
439 'being a valid branch '), category='warning')
440 'being a valid branch '), category='warning')
440 return redirect(h.url('files_home',
441 return redirect(h.url('files_home',
441 repo_name=repo_name, revision='tip',
442 repo_name=repo_name, revision='tip',
442 f_path=f_path))
443 f_path=f_path))
443
444
444 c.commit = self.__get_commit_or_redirect(commit_id, repo_name)
445 c.commit = self.__get_commit_or_redirect(commit_id, repo_name)
445 c.file = self.__get_filenode_or_redirect(repo_name, c.commit, f_path)
446 c.file = self.__get_filenode_or_redirect(repo_name, c.commit, f_path)
446
447
447 c.default_message = _(
448 c.default_message = _(
448 'Deleted file %s via RhodeCode Enterprise') % (f_path)
449 'Deleted file %s via RhodeCode Enterprise') % (f_path)
449 c.f_path = f_path
450 c.f_path = f_path
450 node_path = f_path
451 node_path = f_path
451 author = c.rhodecode_user.full_contact
452 author = c.rhodecode_user.full_contact
452 message = request.POST.get('message') or c.default_message
453 message = request.POST.get('message') or c.default_message
453 try:
454 try:
454 nodes = {
455 nodes = {
455 node_path: {
456 node_path: {
456 'content': ''
457 'content': ''
457 }
458 }
458 }
459 }
459 self.scm_model.delete_nodes(
460 self.scm_model.delete_nodes(
460 user=c.rhodecode_user.user_id, repo=c.rhodecode_db_repo,
461 user=c.rhodecode_user.user_id, repo=c.rhodecode_db_repo,
461 message=message,
462 message=message,
462 nodes=nodes,
463 nodes=nodes,
463 parent_commit=c.commit,
464 parent_commit=c.commit,
464 author=author,
465 author=author,
465 )
466 )
466
467
467 h.flash(_('Successfully deleted file %s') % f_path,
468 h.flash(_('Successfully deleted file %s') % f_path,
468 category='success')
469 category='success')
469 except Exception:
470 except Exception:
470 msg = _('Error occurred during commit')
471 msg = _('Error occurred during commit')
471 log.exception(msg)
472 log.exception(msg)
472 h.flash(msg, category='error')
473 h.flash(msg, category='error')
473 return redirect(url('changeset_home',
474 return redirect(url('changeset_home',
474 repo_name=c.repo_name, revision='tip'))
475 repo_name=c.repo_name, revision='tip'))
475
476
476 @LoginRequired()
477 @LoginRequired()
477 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
478 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
478 def delete_home(self, repo_name, revision, f_path):
479 def delete_home(self, repo_name, revision, f_path):
479 commit_id = revision
480 commit_id = revision
480
481
481 repo = c.rhodecode_db_repo
482 repo = c.rhodecode_db_repo
482 if repo.enable_locking and repo.locked[0]:
483 if repo.enable_locking and repo.locked[0]:
483 h.flash(_('This repository has been locked by %s on %s')
484 h.flash(_('This repository has been locked by %s on %s')
484 % (h.person_by_id(repo.locked[0]),
485 % (h.person_by_id(repo.locked[0]),
485 h.format_date(h.time_to_datetime(repo.locked[1]))),
486 h.format_date(h.time_to_datetime(repo.locked[1]))),
486 'warning')
487 'warning')
487 return redirect(h.url('files_home',
488 return redirect(h.url('files_home',
488 repo_name=repo_name, revision='tip'))
489 repo_name=repo_name, revision='tip'))
489
490
490 if not self._is_valid_head(commit_id, repo.scm_instance()):
491 if not self._is_valid_head(commit_id, repo.scm_instance()):
491 h.flash(_('You can only delete files with revision '
492 h.flash(_('You can only delete files with revision '
492 'being a valid branch '), category='warning')
493 'being a valid branch '), category='warning')
493 return redirect(h.url('files_home',
494 return redirect(h.url('files_home',
494 repo_name=repo_name, revision='tip',
495 repo_name=repo_name, revision='tip',
495 f_path=f_path))
496 f_path=f_path))
496
497
497 c.commit = self.__get_commit_or_redirect(commit_id, repo_name)
498 c.commit = self.__get_commit_or_redirect(commit_id, repo_name)
498 c.file = self.__get_filenode_or_redirect(repo_name, c.commit, f_path)
499 c.file = self.__get_filenode_or_redirect(repo_name, c.commit, f_path)
499
500
500 c.default_message = _(
501 c.default_message = _(
501 'Deleted file %s via RhodeCode Enterprise') % (f_path)
502 'Deleted file %s via RhodeCode Enterprise') % (f_path)
502 c.f_path = f_path
503 c.f_path = f_path
503
504
504 return render('files/files_delete.mako')
505 return render('files/files_delete.mako')
505
506
506 @CSRFRequired()
507 @CSRFRequired()
507 @LoginRequired()
508 @LoginRequired()
508 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
509 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
509 def edit(self, repo_name, revision, f_path):
510 def edit(self, repo_name, revision, f_path):
510 commit_id = revision
511 commit_id = revision
511
512
512 repo = c.rhodecode_db_repo
513 repo = c.rhodecode_db_repo
513 if repo.enable_locking and repo.locked[0]:
514 if repo.enable_locking and repo.locked[0]:
514 h.flash(_('This repository has been locked by %s on %s')
515 h.flash(_('This repository has been locked by %s on %s')
515 % (h.person_by_id(repo.locked[0]),
516 % (h.person_by_id(repo.locked[0]),
516 h.format_date(h.time_to_datetime(repo.locked[1]))),
517 h.format_date(h.time_to_datetime(repo.locked[1]))),
517 'warning')
518 'warning')
518 return redirect(h.url('files_home',
519 return redirect(h.url('files_home',
519 repo_name=repo_name, revision='tip'))
520 repo_name=repo_name, revision='tip'))
520
521
521 if not self._is_valid_head(commit_id, repo.scm_instance()):
522 if not self._is_valid_head(commit_id, repo.scm_instance()):
522 h.flash(_('You can only edit files with revision '
523 h.flash(_('You can only edit files with revision '
523 'being a valid branch '), category='warning')
524 'being a valid branch '), category='warning')
524 return redirect(h.url('files_home',
525 return redirect(h.url('files_home',
525 repo_name=repo_name, revision='tip',
526 repo_name=repo_name, revision='tip',
526 f_path=f_path))
527 f_path=f_path))
527
528
528 c.commit = self.__get_commit_or_redirect(commit_id, repo_name)
529 c.commit = self.__get_commit_or_redirect(commit_id, repo_name)
529 c.file = self.__get_filenode_or_redirect(repo_name, c.commit, f_path)
530 c.file = self.__get_filenode_or_redirect(repo_name, c.commit, f_path)
530
531
531 if c.file.is_binary:
532 if c.file.is_binary:
532 return redirect(url('files_home', repo_name=c.repo_name,
533 return redirect(url('files_home', repo_name=c.repo_name,
533 revision=c.commit.raw_id, f_path=f_path))
534 revision=c.commit.raw_id, f_path=f_path))
534 c.default_message = _(
535 c.default_message = _(
535 'Edited file %s via RhodeCode Enterprise') % (f_path)
536 'Edited file %s via RhodeCode Enterprise') % (f_path)
536 c.f_path = f_path
537 c.f_path = f_path
537 old_content = c.file.content
538 old_content = c.file.content
538 sl = old_content.splitlines(1)
539 sl = old_content.splitlines(1)
539 first_line = sl[0] if sl else ''
540 first_line = sl[0] if sl else ''
540
541
541 # modes: 0 - Unix, 1 - Mac, 2 - DOS
542 # modes: 0 - Unix, 1 - Mac, 2 - DOS
542 mode = detect_mode(first_line, 0)
543 mode = detect_mode(first_line, 0)
543 content = convert_line_endings(request.POST.get('content', ''), mode)
544 content = convert_line_endings(request.POST.get('content', ''), mode)
544
545
545 message = request.POST.get('message') or c.default_message
546 message = request.POST.get('message') or c.default_message
546 org_f_path = c.file.unicode_path
547 org_f_path = c.file.unicode_path
547 filename = request.POST['filename']
548 filename = request.POST['filename']
548 org_filename = c.file.name
549 org_filename = c.file.name
549
550
550 if content == old_content and filename == org_filename:
551 if content == old_content and filename == org_filename:
551 h.flash(_('No changes'), category='warning')
552 h.flash(_('No changes'), category='warning')
552 return redirect(url('changeset_home', repo_name=c.repo_name,
553 return redirect(url('changeset_home', repo_name=c.repo_name,
553 revision='tip'))
554 revision='tip'))
554 try:
555 try:
555 mapping = {
556 mapping = {
556 org_f_path: {
557 org_f_path: {
557 'org_filename': org_f_path,
558 'org_filename': org_f_path,
558 'filename': os.path.join(c.file.dir_path, filename),
559 'filename': os.path.join(c.file.dir_path, filename),
559 'content': content,
560 'content': content,
560 'lexer': '',
561 'lexer': '',
561 'op': 'mod',
562 'op': 'mod',
562 }
563 }
563 }
564 }
564
565
565 ScmModel().update_nodes(
566 ScmModel().update_nodes(
566 user=c.rhodecode_user.user_id,
567 user=c.rhodecode_user.user_id,
567 repo=c.rhodecode_db_repo,
568 repo=c.rhodecode_db_repo,
568 message=message,
569 message=message,
569 nodes=mapping,
570 nodes=mapping,
570 parent_commit=c.commit,
571 parent_commit=c.commit,
571 )
572 )
572
573
573 h.flash(_('Successfully committed to %s') % f_path,
574 h.flash(_('Successfully committed to %s') % f_path,
574 category='success')
575 category='success')
575 except Exception:
576 except Exception:
576 msg = _('Error occurred during commit')
577 msg = _('Error occurred during commit')
577 log.exception(msg)
578 log.exception(msg)
578 h.flash(msg, category='error')
579 h.flash(msg, category='error')
579 return redirect(url('changeset_home',
580 return redirect(url('changeset_home',
580 repo_name=c.repo_name, revision='tip'))
581 repo_name=c.repo_name, revision='tip'))
581
582
582 @LoginRequired()
583 @LoginRequired()
583 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
584 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
584 def edit_home(self, repo_name, revision, f_path):
585 def edit_home(self, repo_name, revision, f_path):
585 commit_id = revision
586 commit_id = revision
586
587
587 repo = c.rhodecode_db_repo
588 repo = c.rhodecode_db_repo
588 if repo.enable_locking and repo.locked[0]:
589 if repo.enable_locking and repo.locked[0]:
589 h.flash(_('This repository has been locked by %s on %s')
590 h.flash(_('This repository has been locked by %s on %s')
590 % (h.person_by_id(repo.locked[0]),
591 % (h.person_by_id(repo.locked[0]),
591 h.format_date(h.time_to_datetime(repo.locked[1]))),
592 h.format_date(h.time_to_datetime(repo.locked[1]))),
592 'warning')
593 'warning')
593 return redirect(h.url('files_home',
594 return redirect(h.url('files_home',
594 repo_name=repo_name, revision='tip'))
595 repo_name=repo_name, revision='tip'))
595
596
596 if not self._is_valid_head(commit_id, repo.scm_instance()):
597 if not self._is_valid_head(commit_id, repo.scm_instance()):
597 h.flash(_('You can only edit files with revision '
598 h.flash(_('You can only edit files with revision '
598 'being a valid branch '), category='warning')
599 'being a valid branch '), category='warning')
599 return redirect(h.url('files_home',
600 return redirect(h.url('files_home',
600 repo_name=repo_name, revision='tip',
601 repo_name=repo_name, revision='tip',
601 f_path=f_path))
602 f_path=f_path))
602
603
603 c.commit = self.__get_commit_or_redirect(commit_id, repo_name)
604 c.commit = self.__get_commit_or_redirect(commit_id, repo_name)
604 c.file = self.__get_filenode_or_redirect(repo_name, c.commit, f_path)
605 c.file = self.__get_filenode_or_redirect(repo_name, c.commit, f_path)
605
606
606 if c.file.is_binary:
607 if c.file.is_binary:
607 return redirect(url('files_home', repo_name=c.repo_name,
608 return redirect(url('files_home', repo_name=c.repo_name,
608 revision=c.commit.raw_id, f_path=f_path))
609 revision=c.commit.raw_id, f_path=f_path))
609 c.default_message = _(
610 c.default_message = _(
610 'Edited file %s via RhodeCode Enterprise') % (f_path)
611 'Edited file %s via RhodeCode Enterprise') % (f_path)
611 c.f_path = f_path
612 c.f_path = f_path
612
613
613 return render('files/files_edit.mako')
614 return render('files/files_edit.mako')
614
615
615 def _is_valid_head(self, commit_id, repo):
616 def _is_valid_head(self, commit_id, repo):
616 # check if commit is a branch identifier- basically we cannot
617 # check if commit is a branch identifier- basically we cannot
617 # create multiple heads via file editing
618 # create multiple heads via file editing
618 valid_heads = repo.branches.keys() + repo.branches.values()
619 valid_heads = repo.branches.keys() + repo.branches.values()
619
620
620 if h.is_svn(repo) and not repo.is_empty():
621 if h.is_svn(repo) and not repo.is_empty():
621 # Note: Subversion only has one head, we add it here in case there
622 # Note: Subversion only has one head, we add it here in case there
622 # is no branch matched.
623 # is no branch matched.
623 valid_heads.append(repo.get_commit(commit_idx=-1).raw_id)
624 valid_heads.append(repo.get_commit(commit_idx=-1).raw_id)
624
625
625 # check if commit is a branch name or branch hash
626 # check if commit is a branch name or branch hash
626 return commit_id in valid_heads
627 return commit_id in valid_heads
627
628
628 @CSRFRequired()
629 @CSRFRequired()
629 @LoginRequired()
630 @LoginRequired()
630 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
631 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
631 def add(self, repo_name, revision, f_path):
632 def add(self, repo_name, revision, f_path):
632 repo = Repository.get_by_repo_name(repo_name)
633 repo = Repository.get_by_repo_name(repo_name)
633 if repo.enable_locking and repo.locked[0]:
634 if repo.enable_locking and repo.locked[0]:
634 h.flash(_('This repository has been locked by %s on %s')
635 h.flash(_('This repository has been locked by %s on %s')
635 % (h.person_by_id(repo.locked[0]),
636 % (h.person_by_id(repo.locked[0]),
636 h.format_date(h.time_to_datetime(repo.locked[1]))),
637 h.format_date(h.time_to_datetime(repo.locked[1]))),
637 'warning')
638 'warning')
638 return redirect(h.url('files_home',
639 return redirect(h.url('files_home',
639 repo_name=repo_name, revision='tip'))
640 repo_name=repo_name, revision='tip'))
640
641
641 r_post = request.POST
642 r_post = request.POST
642
643
643 c.commit = self.__get_commit_or_redirect(
644 c.commit = self.__get_commit_or_redirect(
644 revision, repo_name, redirect_after=False)
645 revision, repo_name, redirect_after=False)
645 if c.commit is None:
646 if c.commit is None:
646 c.commit = EmptyCommit(alias=c.rhodecode_repo.alias)
647 c.commit = EmptyCommit(alias=c.rhodecode_repo.alias)
647 c.default_message = (_('Added file via RhodeCode Enterprise'))
648 c.default_message = (_('Added file via RhodeCode Enterprise'))
648 c.f_path = f_path
649 c.f_path = f_path
649 unix_mode = 0
650 unix_mode = 0
650 content = convert_line_endings(r_post.get('content', ''), unix_mode)
651 content = convert_line_endings(r_post.get('content', ''), unix_mode)
651
652
652 message = r_post.get('message') or c.default_message
653 message = r_post.get('message') or c.default_message
653 filename = r_post.get('filename')
654 filename = r_post.get('filename')
654 location = r_post.get('location', '') # dir location
655 location = r_post.get('location', '') # dir location
655 file_obj = r_post.get('upload_file', None)
656 file_obj = r_post.get('upload_file', None)
656
657
657 if file_obj is not None and hasattr(file_obj, 'filename'):
658 if file_obj is not None and hasattr(file_obj, 'filename'):
658 filename = file_obj.filename
659 filename = file_obj.filename
659 content = file_obj.file
660 content = file_obj.file
660
661
661 if hasattr(content, 'file'):
662 if hasattr(content, 'file'):
662 # non posix systems store real file under file attr
663 # non posix systems store real file under file attr
663 content = content.file
664 content = content.file
664
665
665 # If there's no commit, redirect to repo summary
666 # If there's no commit, redirect to repo summary
666 if type(c.commit) is EmptyCommit:
667 if type(c.commit) is EmptyCommit:
667 redirect_url = "summary_home"
668 redirect_url = "summary_home"
668 else:
669 else:
669 redirect_url = "changeset_home"
670 redirect_url = "changeset_home"
670
671
671 if not filename:
672 if not filename:
672 h.flash(_('No filename'), category='warning')
673 h.flash(_('No filename'), category='warning')
673 return redirect(url(redirect_url, repo_name=c.repo_name,
674 return redirect(url(redirect_url, repo_name=c.repo_name,
674 revision='tip'))
675 revision='tip'))
675
676
676 # extract the location from filename,
677 # extract the location from filename,
677 # allows using foo/bar.txt syntax to create subdirectories
678 # allows using foo/bar.txt syntax to create subdirectories
678 subdir_loc = filename.rsplit('/', 1)
679 subdir_loc = filename.rsplit('/', 1)
679 if len(subdir_loc) == 2:
680 if len(subdir_loc) == 2:
680 location = os.path.join(location, subdir_loc[0])
681 location = os.path.join(location, subdir_loc[0])
681
682
682 # strip all crap out of file, just leave the basename
683 # strip all crap out of file, just leave the basename
683 filename = os.path.basename(filename)
684 filename = os.path.basename(filename)
684 node_path = os.path.join(location, filename)
685 node_path = os.path.join(location, filename)
685 author = c.rhodecode_user.full_contact
686 author = c.rhodecode_user.full_contact
686
687
687 try:
688 try:
688 nodes = {
689 nodes = {
689 node_path: {
690 node_path: {
690 'content': content
691 'content': content
691 }
692 }
692 }
693 }
693 self.scm_model.create_nodes(
694 self.scm_model.create_nodes(
694 user=c.rhodecode_user.user_id,
695 user=c.rhodecode_user.user_id,
695 repo=c.rhodecode_db_repo,
696 repo=c.rhodecode_db_repo,
696 message=message,
697 message=message,
697 nodes=nodes,
698 nodes=nodes,
698 parent_commit=c.commit,
699 parent_commit=c.commit,
699 author=author,
700 author=author,
700 )
701 )
701
702
702 h.flash(_('Successfully committed to %s') % node_path,
703 h.flash(_('Successfully committed to %s') % node_path,
703 category='success')
704 category='success')
704 except NonRelativePathError as e:
705 except NonRelativePathError as e:
705 h.flash(_(
706 h.flash(_(
706 'The location specified must be a relative path and must not '
707 'The location specified must be a relative path and must not '
707 'contain .. in the path'), category='warning')
708 'contain .. in the path'), category='warning')
708 return redirect(url('changeset_home', repo_name=c.repo_name,
709 return redirect(url('changeset_home', repo_name=c.repo_name,
709 revision='tip'))
710 revision='tip'))
710 except (NodeError, NodeAlreadyExistsError) as e:
711 except (NodeError, NodeAlreadyExistsError) as e:
711 h.flash(_(e), category='error')
712 h.flash(_(e), category='error')
712 except Exception:
713 except Exception:
713 msg = _('Error occurred during commit')
714 msg = _('Error occurred during commit')
714 log.exception(msg)
715 log.exception(msg)
715 h.flash(msg, category='error')
716 h.flash(msg, category='error')
716 return redirect(url('changeset_home',
717 return redirect(url('changeset_home',
717 repo_name=c.repo_name, revision='tip'))
718 repo_name=c.repo_name, revision='tip'))
718
719
719 @LoginRequired()
720 @LoginRequired()
720 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
721 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
721 def add_home(self, repo_name, revision, f_path):
722 def add_home(self, repo_name, revision, f_path):
722
723
723 repo = Repository.get_by_repo_name(repo_name)
724 repo = Repository.get_by_repo_name(repo_name)
724 if repo.enable_locking and repo.locked[0]:
725 if repo.enable_locking and repo.locked[0]:
725 h.flash(_('This repository has been locked by %s on %s')
726 h.flash(_('This repository has been locked by %s on %s')
726 % (h.person_by_id(repo.locked[0]),
727 % (h.person_by_id(repo.locked[0]),
727 h.format_date(h.time_to_datetime(repo.locked[1]))),
728 h.format_date(h.time_to_datetime(repo.locked[1]))),
728 'warning')
729 'warning')
729 return redirect(h.url('files_home',
730 return redirect(h.url('files_home',
730 repo_name=repo_name, revision='tip'))
731 repo_name=repo_name, revision='tip'))
731
732
732 c.commit = self.__get_commit_or_redirect(
733 c.commit = self.__get_commit_or_redirect(
733 revision, repo_name, redirect_after=False)
734 revision, repo_name, redirect_after=False)
734 if c.commit is None:
735 if c.commit is None:
735 c.commit = EmptyCommit(alias=c.rhodecode_repo.alias)
736 c.commit = EmptyCommit(alias=c.rhodecode_repo.alias)
736 c.default_message = (_('Added file via RhodeCode Enterprise'))
737 c.default_message = (_('Added file via RhodeCode Enterprise'))
737 c.f_path = f_path
738 c.f_path = f_path
738
739
739 return render('files/files_add.mako')
740 return render('files/files_add.mako')
740
741
741 @LoginRequired()
742 @LoginRequired()
742 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
743 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
743 'repository.admin')
744 'repository.admin')
744 def archivefile(self, repo_name, fname):
745 def archivefile(self, repo_name, fname):
745 fileformat = None
746 fileformat = None
746 commit_id = None
747 commit_id = None
747 ext = None
748 ext = None
748 subrepos = request.GET.get('subrepos') == 'true'
749 subrepos = request.GET.get('subrepos') == 'true'
749
750
750 for a_type, ext_data in settings.ARCHIVE_SPECS.items():
751 for a_type, ext_data in settings.ARCHIVE_SPECS.items():
751 archive_spec = fname.split(ext_data[1])
752 archive_spec = fname.split(ext_data[1])
752 if len(archive_spec) == 2 and archive_spec[1] == '':
753 if len(archive_spec) == 2 and archive_spec[1] == '':
753 fileformat = a_type or ext_data[1]
754 fileformat = a_type or ext_data[1]
754 commit_id = archive_spec[0]
755 commit_id = archive_spec[0]
755 ext = ext_data[1]
756 ext = ext_data[1]
756
757
757 dbrepo = RepoModel().get_by_repo_name(repo_name)
758 dbrepo = RepoModel().get_by_repo_name(repo_name)
758 if not dbrepo.enable_downloads:
759 if not dbrepo.enable_downloads:
759 return _('Downloads disabled')
760 return _('Downloads disabled')
760
761
761 try:
762 try:
762 commit = c.rhodecode_repo.get_commit(commit_id)
763 commit = c.rhodecode_repo.get_commit(commit_id)
763 content_type = settings.ARCHIVE_SPECS[fileformat][0]
764 content_type = settings.ARCHIVE_SPECS[fileformat][0]
764 except CommitDoesNotExistError:
765 except CommitDoesNotExistError:
765 return _('Unknown revision %s') % commit_id
766 return _('Unknown revision %s') % commit_id
766 except EmptyRepositoryError:
767 except EmptyRepositoryError:
767 return _('Empty repository')
768 return _('Empty repository')
768 except KeyError:
769 except KeyError:
769 return _('Unknown archive type')
770 return _('Unknown archive type')
770
771
771 # archive cache
772 # archive cache
772 from rhodecode import CONFIG
773 from rhodecode import CONFIG
773
774
774 archive_name = '%s-%s%s%s' % (
775 archive_name = '%s-%s%s%s' % (
775 safe_str(repo_name.replace('/', '_')),
776 safe_str(repo_name.replace('/', '_')),
776 '-sub' if subrepos else '',
777 '-sub' if subrepos else '',
777 safe_str(commit.short_id), ext)
778 safe_str(commit.short_id), ext)
778
779
779 use_cached_archive = False
780 use_cached_archive = False
780 archive_cache_enabled = CONFIG.get(
781 archive_cache_enabled = CONFIG.get(
781 'archive_cache_dir') and not request.GET.get('no_cache')
782 'archive_cache_dir') and not request.GET.get('no_cache')
782
783
783 if archive_cache_enabled:
784 if archive_cache_enabled:
784 # check if we it's ok to write
785 # check if we it's ok to write
785 if not os.path.isdir(CONFIG['archive_cache_dir']):
786 if not os.path.isdir(CONFIG['archive_cache_dir']):
786 os.makedirs(CONFIG['archive_cache_dir'])
787 os.makedirs(CONFIG['archive_cache_dir'])
787 cached_archive_path = os.path.join(
788 cached_archive_path = os.path.join(
788 CONFIG['archive_cache_dir'], archive_name)
789 CONFIG['archive_cache_dir'], archive_name)
789 if os.path.isfile(cached_archive_path):
790 if os.path.isfile(cached_archive_path):
790 log.debug('Found cached archive in %s', cached_archive_path)
791 log.debug('Found cached archive in %s', cached_archive_path)
791 fd, archive = None, cached_archive_path
792 fd, archive = None, cached_archive_path
792 use_cached_archive = True
793 use_cached_archive = True
793 else:
794 else:
794 log.debug('Archive %s is not yet cached', archive_name)
795 log.debug('Archive %s is not yet cached', archive_name)
795
796
796 if not use_cached_archive:
797 if not use_cached_archive:
797 # generate new archive
798 # generate new archive
798 fd, archive = tempfile.mkstemp()
799 fd, archive = tempfile.mkstemp()
799 log.debug('Creating new temp archive in %s' % (archive,))
800 log.debug('Creating new temp archive in %s' % (archive,))
800 try:
801 try:
801 commit.archive_repo(archive, kind=fileformat, subrepos=subrepos)
802 commit.archive_repo(archive, kind=fileformat, subrepos=subrepos)
802 except ImproperArchiveTypeError:
803 except ImproperArchiveTypeError:
803 return _('Unknown archive type')
804 return _('Unknown archive type')
804 if archive_cache_enabled:
805 if archive_cache_enabled:
805 # if we generated the archive and we have cache enabled
806 # if we generated the archive and we have cache enabled
806 # let's use this for future
807 # let's use this for future
807 log.debug('Storing new archive in %s' % (cached_archive_path,))
808 log.debug('Storing new archive in %s' % (cached_archive_path,))
808 shutil.move(archive, cached_archive_path)
809 shutil.move(archive, cached_archive_path)
809 archive = cached_archive_path
810 archive = cached_archive_path
810
811
811 def get_chunked_archive(archive):
812 def get_chunked_archive(archive):
812 with open(archive, 'rb') as stream:
813 with open(archive, 'rb') as stream:
813 while True:
814 while True:
814 data = stream.read(16 * 1024)
815 data = stream.read(16 * 1024)
815 if not data:
816 if not data:
816 if fd: # fd means we used temporary file
817 if fd: # fd means we used temporary file
817 os.close(fd)
818 os.close(fd)
818 if not archive_cache_enabled:
819 if not archive_cache_enabled:
819 log.debug('Destroying temp archive %s', archive)
820 log.debug('Destroying temp archive %s', archive)
820 os.remove(archive)
821 os.remove(archive)
821 break
822 break
822 yield data
823 yield data
823
824
824 # store download action
825 # store download action
825 action_logger(user=c.rhodecode_user,
826 action_logger(user=c.rhodecode_user,
826 action='user_downloaded_archive:%s' % archive_name,
827 action='user_downloaded_archive:%s' % archive_name,
827 repo=repo_name, ipaddr=self.ip_addr, commit=True)
828 repo=repo_name, ipaddr=self.ip_addr, commit=True)
828 response.content_disposition = str(
829 response.content_disposition = str(
829 'attachment; filename=%s' % archive_name)
830 'attachment; filename=%s' % archive_name)
830 response.content_type = str(content_type)
831 response.content_type = str(content_type)
831
832
832 return get_chunked_archive(archive)
833 return get_chunked_archive(archive)
833
834
834 @LoginRequired()
835 @LoginRequired()
835 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
836 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
836 'repository.admin')
837 'repository.admin')
837 def diff(self, repo_name, f_path):
838 def diff(self, repo_name, f_path):
838
839
839 c.action = request.GET.get('diff')
840 c.action = request.GET.get('diff')
840 diff1 = request.GET.get('diff1', '')
841 diff1 = request.GET.get('diff1', '')
841 diff2 = request.GET.get('diff2', '')
842 diff2 = request.GET.get('diff2', '')
842
843
843 path1, diff1 = parse_path_ref(diff1, default_path=f_path)
844 path1, diff1 = parse_path_ref(diff1, default_path=f_path)
844
845
845 ignore_whitespace = str2bool(request.GET.get('ignorews'))
846 ignore_whitespace = str2bool(request.GET.get('ignorews'))
846 line_context = request.GET.get('context', 3)
847 line_context = request.GET.get('context', 3)
847
848
848 if not any((diff1, diff2)):
849 if not any((diff1, diff2)):
849 h.flash(
850 h.flash(
850 'Need query parameter "diff1" or "diff2" to generate a diff.',
851 'Need query parameter "diff1" or "diff2" to generate a diff.',
851 category='error')
852 category='error')
852 raise HTTPBadRequest()
853 raise HTTPBadRequest()
853
854
854 if c.action not in ['download', 'raw']:
855 if c.action not in ['download', 'raw']:
855 # redirect to new view if we render diff
856 # redirect to new view if we render diff
856 return redirect(
857 return redirect(
857 url('compare_url', repo_name=repo_name,
858 url('compare_url', repo_name=repo_name,
858 source_ref_type='rev',
859 source_ref_type='rev',
859 source_ref=diff1,
860 source_ref=diff1,
860 target_repo=c.repo_name,
861 target_repo=c.repo_name,
861 target_ref_type='rev',
862 target_ref_type='rev',
862 target_ref=diff2,
863 target_ref=diff2,
863 f_path=f_path))
864 f_path=f_path))
864
865
865 try:
866 try:
866 node1 = self._get_file_node(diff1, path1)
867 node1 = self._get_file_node(diff1, path1)
867 node2 = self._get_file_node(diff2, f_path)
868 node2 = self._get_file_node(diff2, f_path)
868 except (RepositoryError, NodeError):
869 except (RepositoryError, NodeError):
869 log.exception("Exception while trying to get node from repository")
870 log.exception("Exception while trying to get node from repository")
870 return redirect(url(
871 return redirect(url(
871 'files_home', repo_name=c.repo_name, f_path=f_path))
872 'files_home', repo_name=c.repo_name, f_path=f_path))
872
873
873 if all(isinstance(node.commit, EmptyCommit)
874 if all(isinstance(node.commit, EmptyCommit)
874 for node in (node1, node2)):
875 for node in (node1, node2)):
875 raise HTTPNotFound
876 raise HTTPNotFound
876
877
877 c.commit_1 = node1.commit
878 c.commit_1 = node1.commit
878 c.commit_2 = node2.commit
879 c.commit_2 = node2.commit
879
880
880 if c.action == 'download':
881 if c.action == 'download':
881 _diff = diffs.get_gitdiff(node1, node2,
882 _diff = diffs.get_gitdiff(node1, node2,
882 ignore_whitespace=ignore_whitespace,
883 ignore_whitespace=ignore_whitespace,
883 context=line_context)
884 context=line_context)
884 diff = diffs.DiffProcessor(_diff, format='gitdiff')
885 diff = diffs.DiffProcessor(_diff, format='gitdiff')
885
886
886 diff_name = '%s_vs_%s.diff' % (diff1, diff2)
887 diff_name = '%s_vs_%s.diff' % (diff1, diff2)
887 response.content_type = 'text/plain'
888 response.content_type = 'text/plain'
888 response.content_disposition = (
889 response.content_disposition = (
889 'attachment; filename=%s' % (diff_name,)
890 'attachment; filename=%s' % (diff_name,)
890 )
891 )
891 charset = self._get_default_encoding()
892 charset = self._get_default_encoding()
892 if charset:
893 if charset:
893 response.charset = charset
894 response.charset = charset
894 return diff.as_raw()
895 return diff.as_raw()
895
896
896 elif c.action == 'raw':
897 elif c.action == 'raw':
897 _diff = diffs.get_gitdiff(node1, node2,
898 _diff = diffs.get_gitdiff(node1, node2,
898 ignore_whitespace=ignore_whitespace,
899 ignore_whitespace=ignore_whitespace,
899 context=line_context)
900 context=line_context)
900 diff = diffs.DiffProcessor(_diff, format='gitdiff')
901 diff = diffs.DiffProcessor(_diff, format='gitdiff')
901 response.content_type = 'text/plain'
902 response.content_type = 'text/plain'
902 charset = self._get_default_encoding()
903 charset = self._get_default_encoding()
903 if charset:
904 if charset:
904 response.charset = charset
905 response.charset = charset
905 return diff.as_raw()
906 return diff.as_raw()
906
907
907 else:
908 else:
908 return redirect(
909 return redirect(
909 url('compare_url', repo_name=repo_name,
910 url('compare_url', repo_name=repo_name,
910 source_ref_type='rev',
911 source_ref_type='rev',
911 source_ref=diff1,
912 source_ref=diff1,
912 target_repo=c.repo_name,
913 target_repo=c.repo_name,
913 target_ref_type='rev',
914 target_ref_type='rev',
914 target_ref=diff2,
915 target_ref=diff2,
915 f_path=f_path))
916 f_path=f_path))
916
917
917 @LoginRequired()
918 @LoginRequired()
918 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
919 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
919 'repository.admin')
920 'repository.admin')
920 def diff_2way(self, repo_name, f_path):
921 def diff_2way(self, repo_name, f_path):
921 """
922 """
922 Kept only to make OLD links work
923 Kept only to make OLD links work
923 """
924 """
924 diff1 = request.GET.get('diff1', '')
925 diff1 = request.GET.get('diff1', '')
925 diff2 = request.GET.get('diff2', '')
926 diff2 = request.GET.get('diff2', '')
926
927
927 if not any((diff1, diff2)):
928 if not any((diff1, diff2)):
928 h.flash(
929 h.flash(
929 'Need query parameter "diff1" or "diff2" to generate a diff.',
930 'Need query parameter "diff1" or "diff2" to generate a diff.',
930 category='error')
931 category='error')
931 raise HTTPBadRequest()
932 raise HTTPBadRequest()
932
933
933 return redirect(
934 return redirect(
934 url('compare_url', repo_name=repo_name,
935 url('compare_url', repo_name=repo_name,
935 source_ref_type='rev',
936 source_ref_type='rev',
936 source_ref=diff1,
937 source_ref=diff1,
937 target_repo=c.repo_name,
938 target_repo=c.repo_name,
938 target_ref_type='rev',
939 target_ref_type='rev',
939 target_ref=diff2,
940 target_ref=diff2,
940 f_path=f_path,
941 f_path=f_path,
941 diffmode='sideside'))
942 diffmode='sideside'))
942
943
943 def _get_file_node(self, commit_id, f_path):
944 def _get_file_node(self, commit_id, f_path):
944 if commit_id not in ['', None, 'None', '0' * 12, '0' * 40]:
945 if commit_id not in ['', None, 'None', '0' * 12, '0' * 40]:
945 commit = c.rhodecode_repo.get_commit(commit_id=commit_id)
946 commit = c.rhodecode_repo.get_commit(commit_id=commit_id)
946 try:
947 try:
947 node = commit.get_node(f_path)
948 node = commit.get_node(f_path)
948 if node.is_dir():
949 if node.is_dir():
949 raise NodeError('%s path is a %s not a file'
950 raise NodeError('%s path is a %s not a file'
950 % (node, type(node)))
951 % (node, type(node)))
951 except NodeDoesNotExistError:
952 except NodeDoesNotExistError:
952 commit = EmptyCommit(
953 commit = EmptyCommit(
953 commit_id=commit_id,
954 commit_id=commit_id,
954 idx=commit.idx,
955 idx=commit.idx,
955 repo=commit.repository,
956 repo=commit.repository,
956 alias=commit.repository.alias,
957 alias=commit.repository.alias,
957 message=commit.message,
958 message=commit.message,
958 author=commit.author,
959 author=commit.author,
959 date=commit.date)
960 date=commit.date)
960 node = FileNode(f_path, '', commit=commit)
961 node = FileNode(f_path, '', commit=commit)
961 else:
962 else:
962 commit = EmptyCommit(
963 commit = EmptyCommit(
963 repo=c.rhodecode_repo,
964 repo=c.rhodecode_repo,
964 alias=c.rhodecode_repo.alias)
965 alias=c.rhodecode_repo.alias)
965 node = FileNode(f_path, '', commit=commit)
966 node = FileNode(f_path, '', commit=commit)
966 return node
967 return node
967
968
968 def _get_node_history(self, commit, f_path, commits=None):
969 def _get_node_history(self, commit, f_path, commits=None):
969 """
970 """
970 get commit history for given node
971 get commit history for given node
971
972
972 :param commit: commit to calculate history
973 :param commit: commit to calculate history
973 :param f_path: path for node to calculate history for
974 :param f_path: path for node to calculate history for
974 :param commits: if passed don't calculate history and take
975 :param commits: if passed don't calculate history and take
975 commits defined in this list
976 commits defined in this list
976 """
977 """
977 # calculate history based on tip
978 # calculate history based on tip
978 tip = c.rhodecode_repo.get_commit()
979 tip = c.rhodecode_repo.get_commit()
979 if commits is None:
980 if commits is None:
980 pre_load = ["author", "branch"]
981 pre_load = ["author", "branch"]
981 try:
982 try:
982 commits = tip.get_file_history(f_path, pre_load=pre_load)
983 commits = tip.get_file_history(f_path, pre_load=pre_load)
983 except (NodeDoesNotExistError, CommitError):
984 except (NodeDoesNotExistError, CommitError):
984 # this node is not present at tip!
985 # this node is not present at tip!
985 commits = commit.get_file_history(f_path, pre_load=pre_load)
986 commits = commit.get_file_history(f_path, pre_load=pre_load)
986
987
987 history = []
988 history = []
988 commits_group = ([], _("Changesets"))
989 commits_group = ([], _("Changesets"))
989 for commit in commits:
990 for commit in commits:
990 branch = ' (%s)' % commit.branch if commit.branch else ''
991 branch = ' (%s)' % commit.branch if commit.branch else ''
991 n_desc = 'r%s:%s%s' % (commit.idx, commit.short_id, branch)
992 n_desc = 'r%s:%s%s' % (commit.idx, commit.short_id, branch)
992 commits_group[0].append((commit.raw_id, n_desc,))
993 commits_group[0].append((commit.raw_id, n_desc,))
993 history.append(commits_group)
994 history.append(commits_group)
994
995
995 symbolic_reference = self._symbolic_reference
996 symbolic_reference = self._symbolic_reference
996
997
997 if c.rhodecode_repo.alias == 'svn':
998 if c.rhodecode_repo.alias == 'svn':
998 adjusted_f_path = self._adjust_file_path_for_svn(
999 adjusted_f_path = self._adjust_file_path_for_svn(
999 f_path, c.rhodecode_repo)
1000 f_path, c.rhodecode_repo)
1000 if adjusted_f_path != f_path:
1001 if adjusted_f_path != f_path:
1001 log.debug(
1002 log.debug(
1002 'Recognized svn tag or branch in file "%s", using svn '
1003 'Recognized svn tag or branch in file "%s", using svn '
1003 'specific symbolic references', f_path)
1004 'specific symbolic references', f_path)
1004 f_path = adjusted_f_path
1005 f_path = adjusted_f_path
1005 symbolic_reference = self._symbolic_reference_svn
1006 symbolic_reference = self._symbolic_reference_svn
1006
1007
1007 branches = self._create_references(
1008 branches = self._create_references(
1008 c.rhodecode_repo.branches, symbolic_reference, f_path)
1009 c.rhodecode_repo.branches, symbolic_reference, f_path)
1009 branches_group = (branches, _("Branches"))
1010 branches_group = (branches, _("Branches"))
1010
1011
1011 tags = self._create_references(
1012 tags = self._create_references(
1012 c.rhodecode_repo.tags, symbolic_reference, f_path)
1013 c.rhodecode_repo.tags, symbolic_reference, f_path)
1013 tags_group = (tags, _("Tags"))
1014 tags_group = (tags, _("Tags"))
1014
1015
1015 history.append(branches_group)
1016 history.append(branches_group)
1016 history.append(tags_group)
1017 history.append(tags_group)
1017
1018
1018 return history, commits
1019 return history, commits
1019
1020
1020 def _adjust_file_path_for_svn(self, f_path, repo):
1021 def _adjust_file_path_for_svn(self, f_path, repo):
1021 """
1022 """
1022 Computes the relative path of `f_path`.
1023 Computes the relative path of `f_path`.
1023
1024
1024 This is mainly based on prefix matching of the recognized tags and
1025 This is mainly based on prefix matching of the recognized tags and
1025 branches in the underlying repository.
1026 branches in the underlying repository.
1026 """
1027 """
1027 tags_and_branches = itertools.chain(
1028 tags_and_branches = itertools.chain(
1028 repo.branches.iterkeys(),
1029 repo.branches.iterkeys(),
1029 repo.tags.iterkeys())
1030 repo.tags.iterkeys())
1030 tags_and_branches = sorted(tags_and_branches, key=len, reverse=True)
1031 tags_and_branches = sorted(tags_and_branches, key=len, reverse=True)
1031
1032
1032 for name in tags_and_branches:
1033 for name in tags_and_branches:
1033 if f_path.startswith(name + '/'):
1034 if f_path.startswith(name + '/'):
1034 f_path = vcspath.relpath(f_path, name)
1035 f_path = vcspath.relpath(f_path, name)
1035 break
1036 break
1036 return f_path
1037 return f_path
1037
1038
1038 def _create_references(
1039 def _create_references(
1039 self, branches_or_tags, symbolic_reference, f_path):
1040 self, branches_or_tags, symbolic_reference, f_path):
1040 items = []
1041 items = []
1041 for name, commit_id in branches_or_tags.items():
1042 for name, commit_id in branches_or_tags.items():
1042 sym_ref = symbolic_reference(commit_id, name, f_path)
1043 sym_ref = symbolic_reference(commit_id, name, f_path)
1043 items.append((sym_ref, name))
1044 items.append((sym_ref, name))
1044 return items
1045 return items
1045
1046
1046 def _symbolic_reference(self, commit_id, name, f_path):
1047 def _symbolic_reference(self, commit_id, name, f_path):
1047 return commit_id
1048 return commit_id
1048
1049
1049 def _symbolic_reference_svn(self, commit_id, name, f_path):
1050 def _symbolic_reference_svn(self, commit_id, name, f_path):
1050 new_f_path = vcspath.join(name, f_path)
1051 new_f_path = vcspath.join(name, f_path)
1051 return u'%s@%s' % (new_f_path, commit_id)
1052 return u'%s@%s' % (new_f_path, commit_id)
1052
1053
1053 @LoginRequired()
1054 @LoginRequired()
1054 @XHRRequired()
1055 @XHRRequired()
1055 @HasRepoPermissionAnyDecorator(
1056 @HasRepoPermissionAnyDecorator(
1056 'repository.read', 'repository.write', 'repository.admin')
1057 'repository.read', 'repository.write', 'repository.admin')
1057 @jsonify
1058 @jsonify
1058 def nodelist(self, repo_name, revision, f_path):
1059 def nodelist(self, repo_name, revision, f_path):
1059 commit = self.__get_commit_or_redirect(revision, repo_name)
1060 commit = self.__get_commit_or_redirect(revision, repo_name)
1060
1061
1061 metadata = self._get_nodelist_at_commit(
1062 metadata = self._get_nodelist_at_commit(
1062 repo_name, commit.raw_id, f_path)
1063 repo_name, commit.raw_id, f_path)
1063 return {'nodes': metadata}
1064 return {'nodes': metadata}
1064
1065
1065 @LoginRequired()
1066 @LoginRequired()
1066 @XHRRequired()
1067 @XHRRequired()
1067 @HasRepoPermissionAnyDecorator(
1068 @HasRepoPermissionAnyDecorator(
1068 'repository.read', 'repository.write', 'repository.admin')
1069 'repository.read', 'repository.write', 'repository.admin')
1069 def nodetree_full(self, repo_name, commit_id, f_path):
1070 def nodetree_full(self, repo_name, commit_id, f_path):
1070 """
1071 """
1071 Returns rendered html of file tree that contains commit date,
1072 Returns rendered html of file tree that contains commit date,
1072 author, revision for the specified combination of
1073 author, revision for the specified combination of
1073 repo, commit_id and file path
1074 repo, commit_id and file path
1074
1075
1075 :param repo_name: name of the repository
1076 :param repo_name: name of the repository
1076 :param commit_id: commit_id of file tree
1077 :param commit_id: commit_id of file tree
1077 :param f_path: file path of the requested directory
1078 :param f_path: file path of the requested directory
1078 """
1079 """
1079
1080
1080 commit = self.__get_commit_or_redirect(commit_id, repo_name)
1081 commit = self.__get_commit_or_redirect(commit_id, repo_name)
1081 try:
1082 try:
1082 dir_node = commit.get_node(f_path)
1083 dir_node = commit.get_node(f_path)
1083 except RepositoryError as e:
1084 except RepositoryError as e:
1084 return 'error {}'.format(safe_str(e))
1085 return 'error {}'.format(safe_str(e))
1085
1086
1086 if dir_node.is_file():
1087 if dir_node.is_file():
1087 return ''
1088 return ''
1088
1089
1089 c.file = dir_node
1090 c.file = dir_node
1090 c.commit = commit
1091 c.commit = commit
1091
1092
1092 # using force=True here, make a little trick. We flush the cache and
1093 # using force=True here, make a little trick. We flush the cache and
1093 # compute it using the same key as without full_load, so the fully
1094 # compute it using the same key as without full_load, so the fully
1094 # loaded cached tree is now returned instead of partial
1095 # loaded cached tree is now returned instead of partial
1095 return self._get_tree_at_commit(
1096 return self._get_tree_at_commit(
1096 repo_name, commit.raw_id, dir_node.path, full_load=True,
1097 repo_name, commit.raw_id, dir_node.path, full_load=True,
1097 force=True)
1098 force=True)
General Comments 0
You need to be logged in to leave comments. Login now