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