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