##// END OF EJS Templates
added missing perms check on history call
marcink -
r3755:f5b20478 default
parent child Browse files
Show More
@@ -1,647 +1,650
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.controllers.files
3 rhodecode.controllers.files
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 Files controller for RhodeCode
6 Files controller for RhodeCode
7
7
8 :created_on: Apr 21, 2010
8 :created_on: Apr 21, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
16 # (at your option) any later version.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 from __future__ import with_statement
25 from __future__ import with_statement
26 import os
26 import os
27 import logging
27 import logging
28 import traceback
28 import traceback
29 import tempfile
29 import tempfile
30 import shutil
30 import shutil
31
31
32 from pylons import request, response, tmpl_context as c, url
32 from pylons import request, response, tmpl_context as c, url
33 from pylons.i18n.translation import _
33 from pylons.i18n.translation import _
34 from pylons.controllers.util import redirect
34 from pylons.controllers.util import redirect
35 from rhodecode.lib.utils import jsonify
35 from rhodecode.lib.utils import jsonify
36
36
37 from rhodecode.lib import diffs
37 from rhodecode.lib import diffs
38 from rhodecode.lib import helpers as h
38 from rhodecode.lib import helpers as h
39
39
40 from rhodecode.lib.compat import OrderedDict
40 from rhodecode.lib.compat import OrderedDict
41 from rhodecode.lib.utils2 import convert_line_endings, detect_mode, safe_str,\
41 from rhodecode.lib.utils2 import convert_line_endings, detect_mode, safe_str,\
42 str2bool
42 str2bool
43 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
43 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
44 from rhodecode.lib.base import BaseRepoController, render
44 from rhodecode.lib.base import BaseRepoController, render
45 from rhodecode.lib.vcs.backends.base import EmptyChangeset
45 from rhodecode.lib.vcs.backends.base import EmptyChangeset
46 from rhodecode.lib.vcs.conf import settings
46 from rhodecode.lib.vcs.conf import settings
47 from rhodecode.lib.vcs.exceptions import RepositoryError, \
47 from rhodecode.lib.vcs.exceptions import RepositoryError, \
48 ChangesetDoesNotExistError, EmptyRepositoryError, \
48 ChangesetDoesNotExistError, EmptyRepositoryError, \
49 ImproperArchiveTypeError, VCSError, NodeAlreadyExistsError,\
49 ImproperArchiveTypeError, VCSError, NodeAlreadyExistsError,\
50 NodeDoesNotExistError, ChangesetError, NodeError
50 NodeDoesNotExistError, ChangesetError, NodeError
51 from rhodecode.lib.vcs.nodes import FileNode
51 from rhodecode.lib.vcs.nodes import FileNode
52
52
53 from rhodecode.model.repo import RepoModel
53 from rhodecode.model.repo import RepoModel
54 from rhodecode.model.scm import ScmModel
54 from rhodecode.model.scm import ScmModel
55 from rhodecode.model.db import Repository
55 from rhodecode.model.db import Repository
56
56
57 from rhodecode.controllers.changeset import anchor_url, _ignorews_url,\
57 from rhodecode.controllers.changeset import anchor_url, _ignorews_url,\
58 _context_url, get_line_ctx, get_ignore_ws
58 _context_url, get_line_ctx, get_ignore_ws
59 from webob.exc import HTTPNotFound
59 from webob.exc import HTTPNotFound
60
60
61
61
62 log = logging.getLogger(__name__)
62 log = logging.getLogger(__name__)
63
63
64
64
65 class FilesController(BaseRepoController):
65 class FilesController(BaseRepoController):
66
66
67 def __before__(self):
67 def __before__(self):
68 super(FilesController, self).__before__()
68 super(FilesController, self).__before__()
69 c.cut_off_limit = self.cut_off_limit
69 c.cut_off_limit = self.cut_off_limit
70
70
71 def __get_cs_or_redirect(self, rev, repo_name, redirect_after=True):
71 def __get_cs_or_redirect(self, rev, repo_name, redirect_after=True):
72 """
72 """
73 Safe way to get changeset if error occur it redirects to tip with
73 Safe way to get changeset if error occur it redirects to tip with
74 proper message
74 proper message
75
75
76 :param rev: revision to fetch
76 :param rev: revision to fetch
77 :param repo_name: repo name to redirect after
77 :param repo_name: repo name to redirect after
78 """
78 """
79
79
80 try:
80 try:
81 return c.rhodecode_repo.get_changeset(rev)
81 return c.rhodecode_repo.get_changeset(rev)
82 except EmptyRepositoryError, e:
82 except EmptyRepositoryError, e:
83 if not redirect_after:
83 if not redirect_after:
84 return None
84 return None
85 url_ = url('files_add_home',
85 url_ = url('files_add_home',
86 repo_name=c.repo_name,
86 repo_name=c.repo_name,
87 revision=0, f_path='')
87 revision=0, f_path='')
88 add_new = h.link_to(_('Click here to add new file'), url_)
88 add_new = h.link_to(_('Click here to add new file'), url_)
89 h.flash(h.literal(_('There are no files yet %s') % add_new),
89 h.flash(h.literal(_('There are no files yet %s') % add_new),
90 category='warning')
90 category='warning')
91 redirect(h.url('summary_home', repo_name=repo_name))
91 redirect(h.url('summary_home', repo_name=repo_name))
92
92
93 except RepositoryError, e: # including ChangesetDoesNotExistError
93 except RepositoryError, e: # including ChangesetDoesNotExistError
94 h.flash(str(e), category='error')
94 h.flash(str(e), category='error')
95 raise HTTPNotFound()
95 raise HTTPNotFound()
96
96
97 def __get_filenode_or_redirect(self, repo_name, cs, path):
97 def __get_filenode_or_redirect(self, repo_name, cs, path):
98 """
98 """
99 Returns file_node, if error occurs or given path is directory,
99 Returns file_node, if error occurs or given path is directory,
100 it'll redirect to top level path
100 it'll redirect to top level path
101
101
102 :param repo_name: repo_name
102 :param repo_name: repo_name
103 :param cs: given changeset
103 :param cs: given changeset
104 :param path: path to lookup
104 :param path: path to lookup
105 """
105 """
106
106
107 try:
107 try:
108 file_node = cs.get_node(path)
108 file_node = cs.get_node(path)
109 if file_node.is_dir():
109 if file_node.is_dir():
110 raise RepositoryError('given path is a directory')
110 raise RepositoryError('given path is a directory')
111 except RepositoryError, e:
111 except RepositoryError, e:
112 h.flash(str(e), category='error')
112 h.flash(str(e), category='error')
113 raise HTTPNotFound()
113 raise HTTPNotFound()
114
114
115 return file_node
115 return file_node
116
116
117 @LoginRequired()
117 @LoginRequired()
118 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
118 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
119 'repository.admin')
119 'repository.admin')
120 def index(self, repo_name, revision, f_path, annotate=False):
120 def index(self, repo_name, revision, f_path, annotate=False):
121 # redirect to given revision from form if given
121 # redirect to given revision from form if given
122 post_revision = request.POST.get('at_rev', None)
122 post_revision = request.POST.get('at_rev', None)
123 if post_revision:
123 if post_revision:
124 cs = self.__get_cs_or_redirect(post_revision, repo_name)
124 cs = self.__get_cs_or_redirect(post_revision, repo_name)
125
125
126 c.changeset = self.__get_cs_or_redirect(revision, repo_name)
126 c.changeset = self.__get_cs_or_redirect(revision, repo_name)
127 c.branch = request.GET.get('branch', None)
127 c.branch = request.GET.get('branch', None)
128 c.f_path = f_path
128 c.f_path = f_path
129 c.annotate = annotate
129 c.annotate = annotate
130 c.changeset = self.__get_cs_or_redirect(revision, repo_name)
130 c.changeset = self.__get_cs_or_redirect(revision, repo_name)
131 cur_rev = c.changeset.revision
131 cur_rev = c.changeset.revision
132
132
133 # prev link
133 # prev link
134 try:
134 try:
135 prev_rev = c.rhodecode_repo.get_changeset(cur_rev).prev(c.branch)
135 prev_rev = c.rhodecode_repo.get_changeset(cur_rev).prev(c.branch)
136 c.url_prev = url('files_home', repo_name=c.repo_name,
136 c.url_prev = url('files_home', repo_name=c.repo_name,
137 revision=prev_rev.raw_id, f_path=f_path)
137 revision=prev_rev.raw_id, f_path=f_path)
138 if c.branch:
138 if c.branch:
139 c.url_prev += '?branch=%s' % c.branch
139 c.url_prev += '?branch=%s' % c.branch
140 except (ChangesetDoesNotExistError, VCSError):
140 except (ChangesetDoesNotExistError, VCSError):
141 c.url_prev = '#'
141 c.url_prev = '#'
142
142
143 # next link
143 # next link
144 try:
144 try:
145 next_rev = c.rhodecode_repo.get_changeset(cur_rev).next(c.branch)
145 next_rev = c.rhodecode_repo.get_changeset(cur_rev).next(c.branch)
146 c.url_next = url('files_home', repo_name=c.repo_name,
146 c.url_next = url('files_home', repo_name=c.repo_name,
147 revision=next_rev.raw_id, f_path=f_path)
147 revision=next_rev.raw_id, f_path=f_path)
148 if c.branch:
148 if c.branch:
149 c.url_next += '?branch=%s' % c.branch
149 c.url_next += '?branch=%s' % c.branch
150 except (ChangesetDoesNotExistError, VCSError):
150 except (ChangesetDoesNotExistError, VCSError):
151 c.url_next = '#'
151 c.url_next = '#'
152
152
153 # files or dirs
153 # files or dirs
154 try:
154 try:
155 c.file = c.changeset.get_node(f_path)
155 c.file = c.changeset.get_node(f_path)
156
156
157 if c.file.is_file():
157 if c.file.is_file():
158 c.load_full_history = False
158 c.load_full_history = False
159 file_last_cs = c.file.last_changeset
159 file_last_cs = c.file.last_changeset
160 c.file_changeset = (c.changeset
160 c.file_changeset = (c.changeset
161 if c.changeset.revision < file_last_cs.revision
161 if c.changeset.revision < file_last_cs.revision
162 else file_last_cs)
162 else file_last_cs)
163 #determine if we're on branch head
163 #determine if we're on branch head
164 _branches = c.rhodecode_repo.branches
164 _branches = c.rhodecode_repo.branches
165 c.on_branch_head = revision in _branches.keys() + _branches.values()
165 c.on_branch_head = revision in _branches.keys() + _branches.values()
166 _hist = []
166 _hist = []
167 c.file_history = []
167 c.file_history = []
168 if c.load_full_history:
168 if c.load_full_history:
169 c.file_history, _hist = self._get_node_history(c.changeset, f_path)
169 c.file_history, _hist = self._get_node_history(c.changeset, f_path)
170
170
171 c.authors = []
171 c.authors = []
172 for a in set([x.author for x in _hist]):
172 for a in set([x.author for x in _hist]):
173 c.authors.append((h.email(a), h.person(a)))
173 c.authors.append((h.email(a), h.person(a)))
174 else:
174 else:
175 c.authors = c.file_history = []
175 c.authors = c.file_history = []
176 except RepositoryError, e:
176 except RepositoryError, e:
177 h.flash(str(e), category='error')
177 h.flash(str(e), category='error')
178 raise HTTPNotFound()
178 raise HTTPNotFound()
179
179
180 if request.environ.get('HTTP_X_PARTIAL_XHR'):
180 if request.environ.get('HTTP_X_PARTIAL_XHR'):
181 return render('files/files_ypjax.html')
181 return render('files/files_ypjax.html')
182
182
183 return render('files/files.html')
183 return render('files/files.html')
184
184
185 @LoginRequired()
186 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
187 'repository.admin')
185 def history(self, repo_name, revision, f_path, annotate=False):
188 def history(self, repo_name, revision, f_path, annotate=False):
186 if request.environ.get('HTTP_X_PARTIAL_XHR'):
189 if request.environ.get('HTTP_X_PARTIAL_XHR'):
187 c.changeset = self.__get_cs_or_redirect(revision, repo_name)
190 c.changeset = self.__get_cs_or_redirect(revision, repo_name)
188 c.f_path = f_path
191 c.f_path = f_path
189 c.annotate = annotate
192 c.annotate = annotate
190 c.file = c.changeset.get_node(f_path)
193 c.file = c.changeset.get_node(f_path)
191 if c.file.is_file():
194 if c.file.is_file():
192 file_last_cs = c.file.last_changeset
195 file_last_cs = c.file.last_changeset
193 c.file_changeset = (c.changeset
196 c.file_changeset = (c.changeset
194 if c.changeset.revision < file_last_cs.revision
197 if c.changeset.revision < file_last_cs.revision
195 else file_last_cs)
198 else file_last_cs)
196 c.file_history, _hist = self._get_node_history(c.changeset, f_path)
199 c.file_history, _hist = self._get_node_history(c.changeset, f_path)
197 c.authors = []
200 c.authors = []
198 for a in set([x.author for x in _hist]):
201 for a in set([x.author for x in _hist]):
199 c.authors.append((h.email(a), h.person(a)))
202 c.authors.append((h.email(a), h.person(a)))
200 return render('files/files_history_box.html')
203 return render('files/files_history_box.html')
201
204
202 @LoginRequired()
205 @LoginRequired()
203 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
206 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
204 'repository.admin')
207 'repository.admin')
205 def rawfile(self, repo_name, revision, f_path):
208 def rawfile(self, repo_name, revision, f_path):
206 cs = self.__get_cs_or_redirect(revision, repo_name)
209 cs = self.__get_cs_or_redirect(revision, repo_name)
207 file_node = self.__get_filenode_or_redirect(repo_name, cs, f_path)
210 file_node = self.__get_filenode_or_redirect(repo_name, cs, f_path)
208
211
209 response.content_disposition = 'attachment; filename=%s' % \
212 response.content_disposition = 'attachment; filename=%s' % \
210 safe_str(f_path.split(Repository.url_sep())[-1])
213 safe_str(f_path.split(Repository.url_sep())[-1])
211
214
212 response.content_type = file_node.mimetype
215 response.content_type = file_node.mimetype
213 return file_node.content
216 return file_node.content
214
217
215 @LoginRequired()
218 @LoginRequired()
216 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
219 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
217 'repository.admin')
220 'repository.admin')
218 def raw(self, repo_name, revision, f_path):
221 def raw(self, repo_name, revision, f_path):
219 cs = self.__get_cs_or_redirect(revision, repo_name)
222 cs = self.__get_cs_or_redirect(revision, repo_name)
220 file_node = self.__get_filenode_or_redirect(repo_name, cs, f_path)
223 file_node = self.__get_filenode_or_redirect(repo_name, cs, f_path)
221
224
222 raw_mimetype_mapping = {
225 raw_mimetype_mapping = {
223 # map original mimetype to a mimetype used for "show as raw"
226 # map original mimetype to a mimetype used for "show as raw"
224 # you can also provide a content-disposition to override the
227 # you can also provide a content-disposition to override the
225 # default "attachment" disposition.
228 # default "attachment" disposition.
226 # orig_type: (new_type, new_dispo)
229 # orig_type: (new_type, new_dispo)
227
230
228 # show images inline:
231 # show images inline:
229 'image/x-icon': ('image/x-icon', 'inline'),
232 'image/x-icon': ('image/x-icon', 'inline'),
230 'image/png': ('image/png', 'inline'),
233 'image/png': ('image/png', 'inline'),
231 'image/gif': ('image/gif', 'inline'),
234 'image/gif': ('image/gif', 'inline'),
232 'image/jpeg': ('image/jpeg', 'inline'),
235 'image/jpeg': ('image/jpeg', 'inline'),
233 'image/svg+xml': ('image/svg+xml', 'inline'),
236 'image/svg+xml': ('image/svg+xml', 'inline'),
234 }
237 }
235
238
236 mimetype = file_node.mimetype
239 mimetype = file_node.mimetype
237 try:
240 try:
238 mimetype, dispo = raw_mimetype_mapping[mimetype]
241 mimetype, dispo = raw_mimetype_mapping[mimetype]
239 except KeyError:
242 except KeyError:
240 # we don't know anything special about this, handle it safely
243 # we don't know anything special about this, handle it safely
241 if file_node.is_binary:
244 if file_node.is_binary:
242 # do same as download raw for binary files
245 # do same as download raw for binary files
243 mimetype, dispo = 'application/octet-stream', 'attachment'
246 mimetype, dispo = 'application/octet-stream', 'attachment'
244 else:
247 else:
245 # do not just use the original mimetype, but force text/plain,
248 # do not just use the original mimetype, but force text/plain,
246 # otherwise it would serve text/html and that might be unsafe.
249 # otherwise it would serve text/html and that might be unsafe.
247 # Note: underlying vcs library fakes text/plain mimetype if the
250 # Note: underlying vcs library fakes text/plain mimetype if the
248 # mimetype can not be determined and it thinks it is not
251 # mimetype can not be determined and it thinks it is not
249 # binary.This might lead to erroneous text display in some
252 # binary.This might lead to erroneous text display in some
250 # cases, but helps in other cases, like with text files
253 # cases, but helps in other cases, like with text files
251 # without extension.
254 # without extension.
252 mimetype, dispo = 'text/plain', 'inline'
255 mimetype, dispo = 'text/plain', 'inline'
253
256
254 if dispo == 'attachment':
257 if dispo == 'attachment':
255 dispo = 'attachment; filename=%s' % \
258 dispo = 'attachment; filename=%s' % \
256 safe_str(f_path.split(os.sep)[-1])
259 safe_str(f_path.split(os.sep)[-1])
257
260
258 response.content_disposition = dispo
261 response.content_disposition = dispo
259 response.content_type = mimetype
262 response.content_type = mimetype
260 return file_node.content
263 return file_node.content
261
264
262 @LoginRequired()
265 @LoginRequired()
263 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
266 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
264 def edit(self, repo_name, revision, f_path):
267 def edit(self, repo_name, revision, f_path):
265 repo = c.rhodecode_db_repo
268 repo = c.rhodecode_db_repo
266 if repo.enable_locking and repo.locked[0]:
269 if repo.enable_locking and repo.locked[0]:
267 h.flash(_('This repository is has been locked by %s on %s')
270 h.flash(_('This repository is has been locked by %s on %s')
268 % (h.person_by_id(repo.locked[0]),
271 % (h.person_by_id(repo.locked[0]),
269 h.fmt_date(h.time_to_datetime(repo.locked[1]))),
272 h.fmt_date(h.time_to_datetime(repo.locked[1]))),
270 'warning')
273 'warning')
271 return redirect(h.url('files_home',
274 return redirect(h.url('files_home',
272 repo_name=repo_name, revision='tip'))
275 repo_name=repo_name, revision='tip'))
273
276
274 # check if revision is a branch identifier- basically we cannot
277 # check if revision is a branch identifier- basically we cannot
275 # create multiple heads via file editing
278 # create multiple heads via file editing
276 _branches = repo.scm_instance.branches
279 _branches = repo.scm_instance.branches
277 # check if revision is a branch name or branch hash
280 # check if revision is a branch name or branch hash
278 if revision not in _branches.keys() + _branches.values():
281 if revision not in _branches.keys() + _branches.values():
279 h.flash(_('You can only edit files with revision '
282 h.flash(_('You can only edit files with revision '
280 'being a valid branch '), category='warning')
283 'being a valid branch '), category='warning')
281 return redirect(h.url('files_home',
284 return redirect(h.url('files_home',
282 repo_name=repo_name, revision='tip',
285 repo_name=repo_name, revision='tip',
283 f_path=f_path))
286 f_path=f_path))
284
287
285 r_post = request.POST
288 r_post = request.POST
286
289
287 c.cs = self.__get_cs_or_redirect(revision, repo_name)
290 c.cs = self.__get_cs_or_redirect(revision, repo_name)
288 c.file = self.__get_filenode_or_redirect(repo_name, c.cs, f_path)
291 c.file = self.__get_filenode_or_redirect(repo_name, c.cs, f_path)
289
292
290 if c.file.is_binary:
293 if c.file.is_binary:
291 return redirect(url('files_home', repo_name=c.repo_name,
294 return redirect(url('files_home', repo_name=c.repo_name,
292 revision=c.cs.raw_id, f_path=f_path))
295 revision=c.cs.raw_id, f_path=f_path))
293 c.default_message = _('Edited file %s via RhodeCode') % (f_path)
296 c.default_message = _('Edited file %s via RhodeCode') % (f_path)
294 c.f_path = f_path
297 c.f_path = f_path
295
298
296 if r_post:
299 if r_post:
297
300
298 old_content = c.file.content
301 old_content = c.file.content
299 sl = old_content.splitlines(1)
302 sl = old_content.splitlines(1)
300 first_line = sl[0] if sl else ''
303 first_line = sl[0] if sl else ''
301 # modes: 0 - Unix, 1 - Mac, 2 - DOS
304 # modes: 0 - Unix, 1 - Mac, 2 - DOS
302 mode = detect_mode(first_line, 0)
305 mode = detect_mode(first_line, 0)
303 content = convert_line_endings(r_post.get('content'), mode)
306 content = convert_line_endings(r_post.get('content'), mode)
304
307
305 message = r_post.get('message') or c.default_message
308 message = r_post.get('message') or c.default_message
306 author = self.rhodecode_user.full_contact
309 author = self.rhodecode_user.full_contact
307
310
308 if content == old_content:
311 if content == old_content:
309 h.flash(_('No changes'), category='warning')
312 h.flash(_('No changes'), category='warning')
310 return redirect(url('changeset_home', repo_name=c.repo_name,
313 return redirect(url('changeset_home', repo_name=c.repo_name,
311 revision='tip'))
314 revision='tip'))
312 try:
315 try:
313 self.scm_model.commit_change(repo=c.rhodecode_repo,
316 self.scm_model.commit_change(repo=c.rhodecode_repo,
314 repo_name=repo_name, cs=c.cs,
317 repo_name=repo_name, cs=c.cs,
315 user=self.rhodecode_user.user_id,
318 user=self.rhodecode_user.user_id,
316 author=author, message=message,
319 author=author, message=message,
317 content=content, f_path=f_path)
320 content=content, f_path=f_path)
318 h.flash(_('Successfully committed to %s') % f_path,
321 h.flash(_('Successfully committed to %s') % f_path,
319 category='success')
322 category='success')
320
323
321 except Exception:
324 except Exception:
322 log.error(traceback.format_exc())
325 log.error(traceback.format_exc())
323 h.flash(_('Error occurred during commit'), category='error')
326 h.flash(_('Error occurred during commit'), category='error')
324 return redirect(url('changeset_home',
327 return redirect(url('changeset_home',
325 repo_name=c.repo_name, revision='tip'))
328 repo_name=c.repo_name, revision='tip'))
326
329
327 return render('files/files_edit.html')
330 return render('files/files_edit.html')
328
331
329 @LoginRequired()
332 @LoginRequired()
330 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
333 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
331 def add(self, repo_name, revision, f_path):
334 def add(self, repo_name, revision, f_path):
332
335
333 repo = Repository.get_by_repo_name(repo_name)
336 repo = Repository.get_by_repo_name(repo_name)
334 if repo.enable_locking and repo.locked[0]:
337 if repo.enable_locking and repo.locked[0]:
335 h.flash(_('This repository is has been locked by %s on %s')
338 h.flash(_('This repository is has been locked by %s on %s')
336 % (h.person_by_id(repo.locked[0]),
339 % (h.person_by_id(repo.locked[0]),
337 h.fmt_date(h.time_to_datetime(repo.locked[1]))),
340 h.fmt_date(h.time_to_datetime(repo.locked[1]))),
338 'warning')
341 'warning')
339 return redirect(h.url('files_home',
342 return redirect(h.url('files_home',
340 repo_name=repo_name, revision='tip'))
343 repo_name=repo_name, revision='tip'))
341
344
342 r_post = request.POST
345 r_post = request.POST
343 c.cs = self.__get_cs_or_redirect(revision, repo_name,
346 c.cs = self.__get_cs_or_redirect(revision, repo_name,
344 redirect_after=False)
347 redirect_after=False)
345 if c.cs is None:
348 if c.cs is None:
346 c.cs = EmptyChangeset(alias=c.rhodecode_repo.alias)
349 c.cs = EmptyChangeset(alias=c.rhodecode_repo.alias)
347 c.default_message = (_('Added file via RhodeCode'))
350 c.default_message = (_('Added file via RhodeCode'))
348 c.f_path = f_path
351 c.f_path = f_path
349
352
350 if r_post:
353 if r_post:
351 unix_mode = 0
354 unix_mode = 0
352 content = convert_line_endings(r_post.get('content'), unix_mode)
355 content = convert_line_endings(r_post.get('content'), unix_mode)
353
356
354 message = r_post.get('message') or c.default_message
357 message = r_post.get('message') or c.default_message
355 filename = r_post.get('filename')
358 filename = r_post.get('filename')
356 location = r_post.get('location')
359 location = r_post.get('location')
357 file_obj = r_post.get('upload_file', None)
360 file_obj = r_post.get('upload_file', None)
358
361
359 if file_obj is not None and hasattr(file_obj, 'filename'):
362 if file_obj is not None and hasattr(file_obj, 'filename'):
360 filename = file_obj.filename
363 filename = file_obj.filename
361 content = file_obj.file
364 content = file_obj.file
362
365
363 if not content:
366 if not content:
364 h.flash(_('No content'), category='warning')
367 h.flash(_('No content'), category='warning')
365 return redirect(url('changeset_home', repo_name=c.repo_name,
368 return redirect(url('changeset_home', repo_name=c.repo_name,
366 revision='tip'))
369 revision='tip'))
367 if not filename:
370 if not filename:
368 h.flash(_('No filename'), category='warning')
371 h.flash(_('No filename'), category='warning')
369 return redirect(url('changeset_home', repo_name=c.repo_name,
372 return redirect(url('changeset_home', repo_name=c.repo_name,
370 revision='tip'))
373 revision='tip'))
371 if location.startswith('/') or location.startswith('.') or '../' in location:
374 if location.startswith('/') or location.startswith('.') or '../' in location:
372 h.flash(_('Location must be relative path and must not '
375 h.flash(_('Location must be relative path and must not '
373 'contain .. in path'), category='warning')
376 'contain .. in path'), category='warning')
374 return redirect(url('changeset_home', repo_name=c.repo_name,
377 return redirect(url('changeset_home', repo_name=c.repo_name,
375 revision='tip'))
378 revision='tip'))
376 if location:
379 if location:
377 location = os.path.normpath(location)
380 location = os.path.normpath(location)
378 filename = os.path.basename(filename)
381 filename = os.path.basename(filename)
379 node_path = os.path.join(location, filename)
382 node_path = os.path.join(location, filename)
380 author = self.rhodecode_user.full_contact
383 author = self.rhodecode_user.full_contact
381
384
382 try:
385 try:
383 self.scm_model.create_node(repo=c.rhodecode_repo,
386 self.scm_model.create_node(repo=c.rhodecode_repo,
384 repo_name=repo_name, cs=c.cs,
387 repo_name=repo_name, cs=c.cs,
385 user=self.rhodecode_user.user_id,
388 user=self.rhodecode_user.user_id,
386 author=author, message=message,
389 author=author, message=message,
387 content=content, f_path=node_path)
390 content=content, f_path=node_path)
388 h.flash(_('Successfully committed to %s') % node_path,
391 h.flash(_('Successfully committed to %s') % node_path,
389 category='success')
392 category='success')
390 except (NodeError, NodeAlreadyExistsError), e:
393 except (NodeError, NodeAlreadyExistsError), e:
391 h.flash(_(e), category='error')
394 h.flash(_(e), category='error')
392 except Exception:
395 except Exception:
393 log.error(traceback.format_exc())
396 log.error(traceback.format_exc())
394 h.flash(_('Error occurred during commit'), category='error')
397 h.flash(_('Error occurred during commit'), category='error')
395 return redirect(url('changeset_home',
398 return redirect(url('changeset_home',
396 repo_name=c.repo_name, revision='tip'))
399 repo_name=c.repo_name, revision='tip'))
397
400
398 return render('files/files_add.html')
401 return render('files/files_add.html')
399
402
400 @LoginRequired()
403 @LoginRequired()
401 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
404 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
402 'repository.admin')
405 'repository.admin')
403 def archivefile(self, repo_name, fname):
406 def archivefile(self, repo_name, fname):
404
407
405 fileformat = None
408 fileformat = None
406 revision = None
409 revision = None
407 ext = None
410 ext = None
408 subrepos = request.GET.get('subrepos') == 'true'
411 subrepos = request.GET.get('subrepos') == 'true'
409
412
410 for a_type, ext_data in settings.ARCHIVE_SPECS.items():
413 for a_type, ext_data in settings.ARCHIVE_SPECS.items():
411 archive_spec = fname.split(ext_data[1])
414 archive_spec = fname.split(ext_data[1])
412 if len(archive_spec) == 2 and archive_spec[1] == '':
415 if len(archive_spec) == 2 and archive_spec[1] == '':
413 fileformat = a_type or ext_data[1]
416 fileformat = a_type or ext_data[1]
414 revision = archive_spec[0]
417 revision = archive_spec[0]
415 ext = ext_data[1]
418 ext = ext_data[1]
416
419
417 try:
420 try:
418 dbrepo = RepoModel().get_by_repo_name(repo_name)
421 dbrepo = RepoModel().get_by_repo_name(repo_name)
419 if not dbrepo.enable_downloads:
422 if not dbrepo.enable_downloads:
420 return _('Downloads disabled')
423 return _('Downloads disabled')
421
424
422 if c.rhodecode_repo.alias == 'hg':
425 if c.rhodecode_repo.alias == 'hg':
423 # patch and reset hooks section of UI config to not run any
426 # patch and reset hooks section of UI config to not run any
424 # hooks on fetching archives with subrepos
427 # hooks on fetching archives with subrepos
425 for k, v in c.rhodecode_repo._repo.ui.configitems('hooks'):
428 for k, v in c.rhodecode_repo._repo.ui.configitems('hooks'):
426 c.rhodecode_repo._repo.ui.setconfig('hooks', k, None)
429 c.rhodecode_repo._repo.ui.setconfig('hooks', k, None)
427
430
428 cs = c.rhodecode_repo.get_changeset(revision)
431 cs = c.rhodecode_repo.get_changeset(revision)
429 content_type = settings.ARCHIVE_SPECS[fileformat][0]
432 content_type = settings.ARCHIVE_SPECS[fileformat][0]
430 except ChangesetDoesNotExistError:
433 except ChangesetDoesNotExistError:
431 return _('Unknown revision %s') % revision
434 return _('Unknown revision %s') % revision
432 except EmptyRepositoryError:
435 except EmptyRepositoryError:
433 return _('Empty repository')
436 return _('Empty repository')
434 except (ImproperArchiveTypeError, KeyError):
437 except (ImproperArchiveTypeError, KeyError):
435 return _('Unknown archive type')
438 return _('Unknown archive type')
436 # archive cache
439 # archive cache
437 from rhodecode import CONFIG
440 from rhodecode import CONFIG
438 rev_name = cs.raw_id[:12]
441 rev_name = cs.raw_id[:12]
439 archive_name = '%s-%s%s' % (safe_str(repo_name.replace('/', '_')),
442 archive_name = '%s-%s%s' % (safe_str(repo_name.replace('/', '_')),
440 safe_str(rev_name), ext)
443 safe_str(rev_name), ext)
441
444
442 use_cached_archive = False # defines if we use cached version of archive
445 use_cached_archive = False # defines if we use cached version of archive
443 archive_cache_enabled = CONFIG.get('archive_cache_dir')
446 archive_cache_enabled = CONFIG.get('archive_cache_dir')
444 if not subrepos and archive_cache_enabled:
447 if not subrepos and archive_cache_enabled:
445 #check if we it's ok to write
448 #check if we it's ok to write
446 if not os.path.isdir(CONFIG['archive_cache_dir']):
449 if not os.path.isdir(CONFIG['archive_cache_dir']):
447 os.makedirs(CONFIG['archive_cache_dir'])
450 os.makedirs(CONFIG['archive_cache_dir'])
448 cached_archive_path = os.path.join(CONFIG['archive_cache_dir'], archive_name)
451 cached_archive_path = os.path.join(CONFIG['archive_cache_dir'], archive_name)
449 if os.path.isfile(cached_archive_path):
452 if os.path.isfile(cached_archive_path):
450 log.debug('Found cached archive in %s' % cached_archive_path)
453 log.debug('Found cached archive in %s' % cached_archive_path)
451 fd, archive = None, cached_archive_path
454 fd, archive = None, cached_archive_path
452 use_cached_archive = True
455 use_cached_archive = True
453 else:
456 else:
454 log.debug('Archive %s is not yet cached' % (archive_name))
457 log.debug('Archive %s is not yet cached' % (archive_name))
455
458
456 if not use_cached_archive:
459 if not use_cached_archive:
457 #generate new archive
460 #generate new archive
458 try:
461 try:
459 fd, archive = tempfile.mkstemp()
462 fd, archive = tempfile.mkstemp()
460 t = open(archive, 'wb')
463 t = open(archive, 'wb')
461 log.debug('Creating new temp archive in %s' % archive)
464 log.debug('Creating new temp archive in %s' % archive)
462 cs.fill_archive(stream=t, kind=fileformat, subrepos=subrepos)
465 cs.fill_archive(stream=t, kind=fileformat, subrepos=subrepos)
463 if archive_cache_enabled:
466 if archive_cache_enabled:
464 #if we generated the archive and use cache rename that
467 #if we generated the archive and use cache rename that
465 log.debug('Storing new archive in %s' % cached_archive_path)
468 log.debug('Storing new archive in %s' % cached_archive_path)
466 shutil.move(archive, cached_archive_path)
469 shutil.move(archive, cached_archive_path)
467 archive = cached_archive_path
470 archive = cached_archive_path
468 finally:
471 finally:
469 t.close()
472 t.close()
470
473
471 def get_chunked_archive(archive):
474 def get_chunked_archive(archive):
472 stream = open(archive, 'rb')
475 stream = open(archive, 'rb')
473 while True:
476 while True:
474 data = stream.read(16 * 1024)
477 data = stream.read(16 * 1024)
475 if not data:
478 if not data:
476 stream.close()
479 stream.close()
477 if fd: # fd means we used temporary file
480 if fd: # fd means we used temporary file
478 os.close(fd)
481 os.close(fd)
479 if not archive_cache_enabled:
482 if not archive_cache_enabled:
480 log.debug('Destroing temp archive %s' % archive)
483 log.debug('Destroing temp archive %s' % archive)
481 os.remove(archive)
484 os.remove(archive)
482 break
485 break
483 yield data
486 yield data
484
487
485 response.content_disposition = str('attachment; filename=%s' % (archive_name))
488 response.content_disposition = str('attachment; filename=%s' % (archive_name))
486 response.content_type = str(content_type)
489 response.content_type = str(content_type)
487 return get_chunked_archive(archive)
490 return get_chunked_archive(archive)
488
491
489 @LoginRequired()
492 @LoginRequired()
490 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
493 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
491 'repository.admin')
494 'repository.admin')
492 def diff(self, repo_name, f_path):
495 def diff(self, repo_name, f_path):
493 ignore_whitespace = request.GET.get('ignorews') == '1'
496 ignore_whitespace = request.GET.get('ignorews') == '1'
494 line_context = request.GET.get('context', 3)
497 line_context = request.GET.get('context', 3)
495 diff1 = request.GET.get('diff1', '')
498 diff1 = request.GET.get('diff1', '')
496 diff2 = request.GET.get('diff2', '')
499 diff2 = request.GET.get('diff2', '')
497 c.action = request.GET.get('diff')
500 c.action = request.GET.get('diff')
498 c.no_changes = diff1 == diff2
501 c.no_changes = diff1 == diff2
499 c.f_path = f_path
502 c.f_path = f_path
500 c.big_diff = False
503 c.big_diff = False
501 c.anchor_url = anchor_url
504 c.anchor_url = anchor_url
502 c.ignorews_url = _ignorews_url
505 c.ignorews_url = _ignorews_url
503 c.context_url = _context_url
506 c.context_url = _context_url
504 c.changes = OrderedDict()
507 c.changes = OrderedDict()
505 c.changes[diff2] = []
508 c.changes[diff2] = []
506
509
507 #special case if we want a show rev only, it's impl here
510 #special case if we want a show rev only, it's impl here
508 #to reduce JS and callbacks
511 #to reduce JS and callbacks
509
512
510 if request.GET.get('show_rev'):
513 if request.GET.get('show_rev'):
511 if str2bool(request.GET.get('annotate', 'False')):
514 if str2bool(request.GET.get('annotate', 'False')):
512 _url = url('files_annotate_home', repo_name=c.repo_name,
515 _url = url('files_annotate_home', repo_name=c.repo_name,
513 revision=diff1, f_path=c.f_path)
516 revision=diff1, f_path=c.f_path)
514 else:
517 else:
515 _url = url('files_home', repo_name=c.repo_name,
518 _url = url('files_home', repo_name=c.repo_name,
516 revision=diff1, f_path=c.f_path)
519 revision=diff1, f_path=c.f_path)
517
520
518 return redirect(_url)
521 return redirect(_url)
519 try:
522 try:
520 if diff1 not in ['', None, 'None', '0' * 12, '0' * 40]:
523 if diff1 not in ['', None, 'None', '0' * 12, '0' * 40]:
521 c.changeset_1 = c.rhodecode_repo.get_changeset(diff1)
524 c.changeset_1 = c.rhodecode_repo.get_changeset(diff1)
522 try:
525 try:
523 node1 = c.changeset_1.get_node(f_path)
526 node1 = c.changeset_1.get_node(f_path)
524 if node1.is_dir():
527 if node1.is_dir():
525 raise NodeError('%s path is a %s not a file'
528 raise NodeError('%s path is a %s not a file'
526 % (node1, type(node1)))
529 % (node1, type(node1)))
527 except NodeDoesNotExistError:
530 except NodeDoesNotExistError:
528 c.changeset_1 = EmptyChangeset(cs=diff1,
531 c.changeset_1 = EmptyChangeset(cs=diff1,
529 revision=c.changeset_1.revision,
532 revision=c.changeset_1.revision,
530 repo=c.rhodecode_repo)
533 repo=c.rhodecode_repo)
531 node1 = FileNode(f_path, '', changeset=c.changeset_1)
534 node1 = FileNode(f_path, '', changeset=c.changeset_1)
532 else:
535 else:
533 c.changeset_1 = EmptyChangeset(repo=c.rhodecode_repo)
536 c.changeset_1 = EmptyChangeset(repo=c.rhodecode_repo)
534 node1 = FileNode(f_path, '', changeset=c.changeset_1)
537 node1 = FileNode(f_path, '', changeset=c.changeset_1)
535
538
536 if diff2 not in ['', None, 'None', '0' * 12, '0' * 40]:
539 if diff2 not in ['', None, 'None', '0' * 12, '0' * 40]:
537 c.changeset_2 = c.rhodecode_repo.get_changeset(diff2)
540 c.changeset_2 = c.rhodecode_repo.get_changeset(diff2)
538 try:
541 try:
539 node2 = c.changeset_2.get_node(f_path)
542 node2 = c.changeset_2.get_node(f_path)
540 if node2.is_dir():
543 if node2.is_dir():
541 raise NodeError('%s path is a %s not a file'
544 raise NodeError('%s path is a %s not a file'
542 % (node2, type(node2)))
545 % (node2, type(node2)))
543 except NodeDoesNotExistError:
546 except NodeDoesNotExistError:
544 c.changeset_2 = EmptyChangeset(cs=diff2,
547 c.changeset_2 = EmptyChangeset(cs=diff2,
545 revision=c.changeset_2.revision,
548 revision=c.changeset_2.revision,
546 repo=c.rhodecode_repo)
549 repo=c.rhodecode_repo)
547 node2 = FileNode(f_path, '', changeset=c.changeset_2)
550 node2 = FileNode(f_path, '', changeset=c.changeset_2)
548 else:
551 else:
549 c.changeset_2 = EmptyChangeset(repo=c.rhodecode_repo)
552 c.changeset_2 = EmptyChangeset(repo=c.rhodecode_repo)
550 node2 = FileNode(f_path, '', changeset=c.changeset_2)
553 node2 = FileNode(f_path, '', changeset=c.changeset_2)
551 except (RepositoryError, NodeError):
554 except (RepositoryError, NodeError):
552 log.error(traceback.format_exc())
555 log.error(traceback.format_exc())
553 return redirect(url('files_home', repo_name=c.repo_name,
556 return redirect(url('files_home', repo_name=c.repo_name,
554 f_path=f_path))
557 f_path=f_path))
555
558
556 if c.action == 'download':
559 if c.action == 'download':
557 _diff = diffs.get_gitdiff(node1, node2,
560 _diff = diffs.get_gitdiff(node1, node2,
558 ignore_whitespace=ignore_whitespace,
561 ignore_whitespace=ignore_whitespace,
559 context=line_context)
562 context=line_context)
560 diff = diffs.DiffProcessor(_diff, format='gitdiff')
563 diff = diffs.DiffProcessor(_diff, format='gitdiff')
561
564
562 diff_name = '%s_vs_%s.diff' % (diff1, diff2)
565 diff_name = '%s_vs_%s.diff' % (diff1, diff2)
563 response.content_type = 'text/plain'
566 response.content_type = 'text/plain'
564 response.content_disposition = (
567 response.content_disposition = (
565 'attachment; filename=%s' % diff_name
568 'attachment; filename=%s' % diff_name
566 )
569 )
567 return diff.as_raw()
570 return diff.as_raw()
568
571
569 elif c.action == 'raw':
572 elif c.action == 'raw':
570 _diff = diffs.get_gitdiff(node1, node2,
573 _diff = diffs.get_gitdiff(node1, node2,
571 ignore_whitespace=ignore_whitespace,
574 ignore_whitespace=ignore_whitespace,
572 context=line_context)
575 context=line_context)
573 diff = diffs.DiffProcessor(_diff, format='gitdiff')
576 diff = diffs.DiffProcessor(_diff, format='gitdiff')
574 response.content_type = 'text/plain'
577 response.content_type = 'text/plain'
575 return diff.as_raw()
578 return diff.as_raw()
576
579
577 else:
580 else:
578 fid = h.FID(diff2, node2.path)
581 fid = h.FID(diff2, node2.path)
579 line_context_lcl = get_line_ctx(fid, request.GET)
582 line_context_lcl = get_line_ctx(fid, request.GET)
580 ign_whitespace_lcl = get_ignore_ws(fid, request.GET)
583 ign_whitespace_lcl = get_ignore_ws(fid, request.GET)
581
584
582 lim = request.GET.get('fulldiff') or self.cut_off_limit
585 lim = request.GET.get('fulldiff') or self.cut_off_limit
583 _, cs1, cs2, diff, st = diffs.wrapped_diff(filenode_old=node1,
586 _, cs1, cs2, diff, st = diffs.wrapped_diff(filenode_old=node1,
584 filenode_new=node2,
587 filenode_new=node2,
585 cut_off_limit=lim,
588 cut_off_limit=lim,
586 ignore_whitespace=ign_whitespace_lcl,
589 ignore_whitespace=ign_whitespace_lcl,
587 line_context=line_context_lcl,
590 line_context=line_context_lcl,
588 enable_comments=False)
591 enable_comments=False)
589 op = ''
592 op = ''
590 filename = node1.path
593 filename = node1.path
591 cs_changes = {
594 cs_changes = {
592 'fid': [cs1, cs2, op, filename, diff, st]
595 'fid': [cs1, cs2, op, filename, diff, st]
593 }
596 }
594 c.changes = cs_changes
597 c.changes = cs_changes
595
598
596 return render('files/file_diff.html')
599 return render('files/file_diff.html')
597
600
598 def _get_node_history(self, cs, f_path, changesets=None):
601 def _get_node_history(self, cs, f_path, changesets=None):
599 """
602 """
600 get changesets history for given node
603 get changesets history for given node
601
604
602 :param cs: changeset to calculate history
605 :param cs: changeset to calculate history
603 :param f_path: path for node to calculate history for
606 :param f_path: path for node to calculate history for
604 :param changesets: if passed don't calculate history and take
607 :param changesets: if passed don't calculate history and take
605 changesets defined in this list
608 changesets defined in this list
606 """
609 """
607 # calculate history based on tip
610 # calculate history based on tip
608 tip_cs = c.rhodecode_repo.get_changeset()
611 tip_cs = c.rhodecode_repo.get_changeset()
609 if changesets is None:
612 if changesets is None:
610 try:
613 try:
611 changesets = tip_cs.get_file_history(f_path)
614 changesets = tip_cs.get_file_history(f_path)
612 except (NodeDoesNotExistError, ChangesetError):
615 except (NodeDoesNotExistError, ChangesetError):
613 #this node is not present at tip !
616 #this node is not present at tip !
614 changesets = cs.get_file_history(f_path)
617 changesets = cs.get_file_history(f_path)
615 hist_l = []
618 hist_l = []
616
619
617 changesets_group = ([], _("Changesets"))
620 changesets_group = ([], _("Changesets"))
618 branches_group = ([], _("Branches"))
621 branches_group = ([], _("Branches"))
619 tags_group = ([], _("Tags"))
622 tags_group = ([], _("Tags"))
620 _hg = cs.repository.alias == 'hg'
623 _hg = cs.repository.alias == 'hg'
621 for chs in changesets:
624 for chs in changesets:
622 #_branch = '(%s)' % chs.branch if _hg else ''
625 #_branch = '(%s)' % chs.branch if _hg else ''
623 _branch = chs.branch
626 _branch = chs.branch
624 n_desc = 'r%s:%s (%s)' % (chs.revision, chs.short_id, _branch)
627 n_desc = 'r%s:%s (%s)' % (chs.revision, chs.short_id, _branch)
625 changesets_group[0].append((chs.raw_id, n_desc,))
628 changesets_group[0].append((chs.raw_id, n_desc,))
626 hist_l.append(changesets_group)
629 hist_l.append(changesets_group)
627
630
628 for name, chs in c.rhodecode_repo.branches.items():
631 for name, chs in c.rhodecode_repo.branches.items():
629 branches_group[0].append((chs, name),)
632 branches_group[0].append((chs, name),)
630 hist_l.append(branches_group)
633 hist_l.append(branches_group)
631
634
632 for name, chs in c.rhodecode_repo.tags.items():
635 for name, chs in c.rhodecode_repo.tags.items():
633 tags_group[0].append((chs, name),)
636 tags_group[0].append((chs, name),)
634 hist_l.append(tags_group)
637 hist_l.append(tags_group)
635
638
636 return hist_l, changesets
639 return hist_l, changesets
637
640
638 @LoginRequired()
641 @LoginRequired()
639 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
642 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
640 'repository.admin')
643 'repository.admin')
641 @jsonify
644 @jsonify
642 def nodelist(self, repo_name, revision, f_path):
645 def nodelist(self, repo_name, revision, f_path):
643 if request.environ.get('HTTP_X_PARTIAL_XHR'):
646 if request.environ.get('HTTP_X_PARTIAL_XHR'):
644 cs = self.__get_cs_or_redirect(revision, repo_name)
647 cs = self.__get_cs_or_redirect(revision, repo_name)
645 _d, _f = ScmModel().get_nodes(repo_name, cs.raw_id, f_path,
648 _d, _f = ScmModel().get_nodes(repo_name, cs.raw_id, f_path,
646 flat=False)
649 flat=False)
647 return {'nodes': _d + _f}
650 return {'nodes': _d + _f}
General Comments 0
You need to be logged in to leave comments. Login now