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