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