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