##// END OF EJS Templates
moved login required into seperate calls for files due to optional API access option...
marcink -
r2469:8bd3b7f7 beta
parent child Browse files
Show More
@@ -1,496 +1,504 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.controllers.files
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 Files controller for RhodeCode
7 7
8 8 :created_on: Apr 21, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
14 14 # it under the terms of the GNU General Public License as published by
15 15 # the Free Software Foundation, either version 3 of the License, or
16 16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25 from __future__ import with_statement
26 26 import os
27 27 import logging
28 28 import traceback
29 29 import tempfile
30 30
31 31 from pylons import request, response, tmpl_context as c, url
32 32 from pylons.i18n.translation import _
33 33 from pylons.controllers.util import redirect
34 34 from pylons.decorators import jsonify
35 35 from paste.fileapp import FileApp, _FileIter
36 36
37 37 from rhodecode.lib import diffs
38 38 from rhodecode.lib import helpers as h
39 39
40 40 from rhodecode.lib.compat import OrderedDict
41 41 from rhodecode.lib.utils2 import convert_line_endings, detect_mode, safe_str
42 42 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
43 43 from rhodecode.lib.base import BaseRepoController, render
44 44 from rhodecode.lib.utils import EmptyChangeset
45 45 from rhodecode.lib.vcs.conf import settings
46 46 from rhodecode.lib.vcs.exceptions import RepositoryError, \
47 47 ChangesetDoesNotExistError, EmptyRepositoryError, \
48 48 ImproperArchiveTypeError, VCSError, NodeAlreadyExistsError
49 49 from rhodecode.lib.vcs.nodes import FileNode
50 50
51 51 from rhodecode.model.repo import RepoModel
52 52 from rhodecode.model.scm import ScmModel
53 53 from rhodecode.model.db import Repository
54 54
55 55 from rhodecode.controllers.changeset import anchor_url, _ignorews_url,\
56 56 _context_url, get_line_ctx, get_ignore_ws
57 57
58 58
59 59 log = logging.getLogger(__name__)
60 60
61 61
62 62 class FilesController(BaseRepoController):
63 63
64 @LoginRequired()
64
65 65 def __before__(self):
66 66 super(FilesController, self).__before__()
67 67 c.cut_off_limit = self.cut_off_limit
68 68
69 69 def __get_cs_or_redirect(self, rev, repo_name, redirect_after=True):
70 70 """
71 71 Safe way to get changeset if error occur it redirects to tip with
72 72 proper message
73 73
74 74 :param rev: revision to fetch
75 75 :param repo_name: repo name to redirect after
76 76 """
77 77
78 78 try:
79 79 return c.rhodecode_repo.get_changeset(rev)
80 80 except EmptyRepositoryError, e:
81 81 if not redirect_after:
82 82 return None
83 83 url_ = url('files_add_home',
84 84 repo_name=c.repo_name,
85 85 revision=0, f_path='')
86 86 add_new = '<a href="%s">[%s]</a>' % (url_, _('add new'))
87 87 h.flash(h.literal(_('There are no files yet %s' % add_new)),
88 88 category='warning')
89 89 redirect(h.url('summary_home', repo_name=repo_name))
90 90
91 91 except RepositoryError, e:
92 92 h.flash(str(e), category='warning')
93 93 redirect(h.url('files_home', repo_name=repo_name, revision='tip'))
94 94
95 95 def __get_filenode_or_redirect(self, repo_name, cs, path):
96 96 """
97 97 Returns file_node, if error occurs or given path is directory,
98 98 it'll redirect to top level path
99 99
100 100 :param repo_name: repo_name
101 101 :param cs: given changeset
102 102 :param path: path to lookup
103 103 """
104 104
105 105 try:
106 106 file_node = cs.get_node(path)
107 107 if file_node.is_dir():
108 108 raise RepositoryError('given path is a directory')
109 109 except RepositoryError, e:
110 110 h.flash(str(e), category='warning')
111 111 redirect(h.url('files_home', repo_name=repo_name,
112 112 revision=cs.raw_id))
113 113
114 114 return file_node
115 115
116 @LoginRequired()
116 117 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
117 118 'repository.admin')
118 119 def index(self, repo_name, revision, f_path, annotate=False):
119 120 # redirect to given revision from form if given
120 121 post_revision = request.POST.get('at_rev', None)
121 122 if post_revision:
122 123 cs = self.__get_cs_or_redirect(post_revision, repo_name)
123 124 redirect(url('files_home', repo_name=c.repo_name,
124 125 revision=cs.raw_id, f_path=f_path))
125 126
126 127 c.changeset = self.__get_cs_or_redirect(revision, repo_name)
127 128 c.branch = request.GET.get('branch', None)
128 129 c.f_path = f_path
129 130 c.annotate = annotate
130 131 cur_rev = c.changeset.revision
131 132
132 133 # prev link
133 134 try:
134 135 prev_rev = c.rhodecode_repo.get_changeset(cur_rev).prev(c.branch)
135 136 c.url_prev = url('files_home', repo_name=c.repo_name,
136 137 revision=prev_rev.raw_id, f_path=f_path)
137 138 if c.branch:
138 139 c.url_prev += '?branch=%s' % c.branch
139 140 except (ChangesetDoesNotExistError, VCSError):
140 141 c.url_prev = '#'
141 142
142 143 # next link
143 144 try:
144 145 next_rev = c.rhodecode_repo.get_changeset(cur_rev).next(c.branch)
145 146 c.url_next = url('files_home', repo_name=c.repo_name,
146 147 revision=next_rev.raw_id, f_path=f_path)
147 148 if c.branch:
148 149 c.url_next += '?branch=%s' % c.branch
149 150 except (ChangesetDoesNotExistError, VCSError):
150 151 c.url_next = '#'
151 152
152 153 # files or dirs
153 154 try:
154 155 c.file = c.changeset.get_node(f_path)
155 156
156 157 if c.file.is_file():
157 158 _hist = c.changeset.get_file_history(f_path)
158 159 c.file_history = self._get_node_history(c.changeset, f_path,
159 160 _hist)
160 161 c.authors = []
161 162 for a in set([x.author for x in _hist]):
162 163 c.authors.append((h.email(a), h.person(a)))
163 164 else:
164 165 c.authors = c.file_history = []
165 166 except RepositoryError, e:
166 167 h.flash(str(e), category='warning')
167 168 redirect(h.url('files_home', repo_name=repo_name,
168 169 revision=revision))
169 170
170 171 return render('files/files.html')
171 172
173 @LoginRequired()
172 174 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
173 175 'repository.admin')
174 176 def rawfile(self, repo_name, revision, f_path):
175 177 cs = self.__get_cs_or_redirect(revision, repo_name)
176 178 file_node = self.__get_filenode_or_redirect(repo_name, cs, f_path)
177 179
178 180 response.content_disposition = 'attachment; filename=%s' % \
179 181 safe_str(f_path.split(Repository.url_sep())[-1])
180 182
181 183 response.content_type = file_node.mimetype
182 184 return file_node.content
183
185
186 @LoginRequired()
184 187 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
185 188 'repository.admin')
186 189 def raw(self, repo_name, revision, f_path):
187 190 cs = self.__get_cs_or_redirect(revision, repo_name)
188 191 file_node = self.__get_filenode_or_redirect(repo_name, cs, f_path)
189 192
190 193 raw_mimetype_mapping = {
191 194 # map original mimetype to a mimetype used for "show as raw"
192 195 # you can also provide a content-disposition to override the
193 196 # default "attachment" disposition.
194 197 # orig_type: (new_type, new_dispo)
195 198
196 199 # show images inline:
197 200 'image/x-icon': ('image/x-icon', 'inline'),
198 201 'image/png': ('image/png', 'inline'),
199 202 'image/gif': ('image/gif', 'inline'),
200 203 'image/jpeg': ('image/jpeg', 'inline'),
201 204 'image/svg+xml': ('image/svg+xml', 'inline'),
202 205 }
203 206
204 207 mimetype = file_node.mimetype
205 208 try:
206 209 mimetype, dispo = raw_mimetype_mapping[mimetype]
207 210 except KeyError:
208 211 # we don't know anything special about this, handle it safely
209 212 if file_node.is_binary:
210 213 # do same as download raw for binary files
211 214 mimetype, dispo = 'application/octet-stream', 'attachment'
212 215 else:
213 216 # do not just use the original mimetype, but force text/plain,
214 217 # otherwise it would serve text/html and that might be unsafe.
215 218 # Note: underlying vcs library fakes text/plain mimetype if the
216 219 # mimetype can not be determined and it thinks it is not
217 220 # binary.This might lead to erroneous text display in some
218 221 # cases, but helps in other cases, like with text files
219 222 # without extension.
220 223 mimetype, dispo = 'text/plain', 'inline'
221 224
222 225 if dispo == 'attachment':
223 226 dispo = 'attachment; filename=%s' % \
224 227 safe_str(f_path.split(os.sep)[-1])
225 228
226 229 response.content_disposition = dispo
227 230 response.content_type = mimetype
228 231 return file_node.content
229 232
233 @LoginRequired()
230 234 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
231 235 def edit(self, repo_name, revision, f_path):
232 236 r_post = request.POST
233 237
234 238 c.cs = self.__get_cs_or_redirect(revision, repo_name)
235 239 c.file = self.__get_filenode_or_redirect(repo_name, c.cs, f_path)
236 240
237 241 if c.file.is_binary:
238 242 return redirect(url('files_home', repo_name=c.repo_name,
239 243 revision=c.cs.raw_id, f_path=f_path))
240 244
241 245 c.f_path = f_path
242 246
243 247 if r_post:
244 248
245 249 old_content = c.file.content
246 250 sl = old_content.splitlines(1)
247 251 first_line = sl[0] if sl else ''
248 252 # modes: 0 - Unix, 1 - Mac, 2 - DOS
249 253 mode = detect_mode(first_line, 0)
250 254 content = convert_line_endings(r_post.get('content'), mode)
251 255
252 256 message = r_post.get('message') or (_('Edited %s via RhodeCode')
253 257 % (f_path))
254 258 author = self.rhodecode_user.full_contact
255 259
256 260 if content == old_content:
257 261 h.flash(_('No changes'),
258 262 category='warning')
259 263 return redirect(url('changeset_home', repo_name=c.repo_name,
260 264 revision='tip'))
261 265
262 266 try:
263 267 self.scm_model.commit_change(repo=c.rhodecode_repo,
264 268 repo_name=repo_name, cs=c.cs,
265 269 user=self.rhodecode_user,
266 270 author=author, message=message,
267 271 content=content, f_path=f_path)
268 272 h.flash(_('Successfully committed to %s' % f_path),
269 273 category='success')
270 274
271 275 except Exception:
272 276 log.error(traceback.format_exc())
273 277 h.flash(_('Error occurred during commit'), category='error')
274 278 return redirect(url('changeset_home',
275 279 repo_name=c.repo_name, revision='tip'))
276 280
277 281 return render('files/files_edit.html')
278 282
283 @LoginRequired()
279 284 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
280 285 def add(self, repo_name, revision, f_path):
281 286 r_post = request.POST
282 287 c.cs = self.__get_cs_or_redirect(revision, repo_name,
283 288 redirect_after=False)
284 289 if c.cs is None:
285 290 c.cs = EmptyChangeset(alias=c.rhodecode_repo.alias)
286 291
287 292 c.f_path = f_path
288 293
289 294 if r_post:
290 295 unix_mode = 0
291 296 content = convert_line_endings(r_post.get('content'), unix_mode)
292 297
293 298 message = r_post.get('message') or (_('Added %s via RhodeCode')
294 299 % (f_path))
295 300 location = r_post.get('location')
296 301 filename = r_post.get('filename')
297 302 file_obj = r_post.get('upload_file', None)
298 303
299 304 if file_obj is not None and hasattr(file_obj, 'filename'):
300 305 filename = file_obj.filename
301 306 content = file_obj.file
302 307
303 308 node_path = os.path.join(location, filename)
304 309 author = self.rhodecode_user.full_contact
305 310
306 311 if not content:
307 312 h.flash(_('No content'), category='warning')
308 313 return redirect(url('changeset_home', repo_name=c.repo_name,
309 314 revision='tip'))
310 315 if not filename:
311 316 h.flash(_('No filename'), category='warning')
312 317 return redirect(url('changeset_home', repo_name=c.repo_name,
313 318 revision='tip'))
314 319
315 320 try:
316 321 self.scm_model.create_node(repo=c.rhodecode_repo,
317 322 repo_name=repo_name, cs=c.cs,
318 323 user=self.rhodecode_user,
319 324 author=author, message=message,
320 325 content=content, f_path=node_path)
321 326 h.flash(_('Successfully committed to %s' % node_path),
322 327 category='success')
323 328 except NodeAlreadyExistsError, e:
324 329 h.flash(_(e), category='error')
325 330 except Exception:
326 331 log.error(traceback.format_exc())
327 332 h.flash(_('Error occurred during commit'), category='error')
328 333 return redirect(url('changeset_home',
329 334 repo_name=c.repo_name, revision='tip'))
330 335
331 336 return render('files/files_add.html')
332 337
338 @LoginRequired()
333 339 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
334 340 'repository.admin')
335 341 def archivefile(self, repo_name, fname):
336 342
337 343 fileformat = None
338 344 revision = None
339 345 ext = None
340 346 subrepos = request.GET.get('subrepos') == 'true'
341 347
342 348 for a_type, ext_data in settings.ARCHIVE_SPECS.items():
343 349 archive_spec = fname.split(ext_data[1])
344 350 if len(archive_spec) == 2 and archive_spec[1] == '':
345 351 fileformat = a_type or ext_data[1]
346 352 revision = archive_spec[0]
347 353 ext = ext_data[1]
348 354
349 355 try:
350 356 dbrepo = RepoModel().get_by_repo_name(repo_name)
351 357 if dbrepo.enable_downloads is False:
352 358 return _('downloads disabled')
353 359
354 360 if c.rhodecode_repo.alias == 'hg':
355 361 # patch and reset hooks section of UI config to not run any
356 362 # hooks on fetching archives with subrepos
357 363 for k, v in c.rhodecode_repo._repo.ui.configitems('hooks'):
358 364 c.rhodecode_repo._repo.ui.setconfig('hooks', k, None)
359 365
360 366 cs = c.rhodecode_repo.get_changeset(revision)
361 367 content_type = settings.ARCHIVE_SPECS[fileformat][0]
362 368 except ChangesetDoesNotExistError:
363 369 return _('Unknown revision %s') % revision
364 370 except EmptyRepositoryError:
365 371 return _('Empty repository')
366 372 except (ImproperArchiveTypeError, KeyError):
367 373 return _('Unknown archive type')
368 374
369 375 fd, archive = tempfile.mkstemp()
370 376 t = open(archive, 'wb')
371 377 cs.fill_archive(stream=t, kind=fileformat, subrepos=subrepos)
372 378 t.close()
373 379
374 380 def get_chunked_archive(archive):
375 381 stream = open(archive, 'rb')
376 382 while True:
377 383 data = stream.read(16 * 1024)
378 384 if not data:
379 385 stream.close()
380 386 os.close(fd)
381 387 os.remove(archive)
382 388 break
383 389 yield data
384 390
385 391 response.content_disposition = str('attachment; filename=%s-%s%s' \
386 392 % (repo_name, revision[:12], ext))
387 393 response.content_type = str(content_type)
388 394 return get_chunked_archive(archive)
389 395
396 @LoginRequired()
390 397 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
391 398 'repository.admin')
392 399 def diff(self, repo_name, f_path):
393 400 ignore_whitespace = request.GET.get('ignorews') == '1'
394 401 line_context = request.GET.get('context', 3)
395 402 diff1 = request.GET.get('diff1', '')
396 403 diff2 = request.GET.get('diff2', '')
397 404 c.action = request.GET.get('diff')
398 405 c.no_changes = diff1 == diff2
399 406 c.f_path = f_path
400 407 c.big_diff = False
401 408 c.anchor_url = anchor_url
402 409 c.ignorews_url = _ignorews_url
403 410 c.context_url = _context_url
404 411 c.changes = OrderedDict()
405 412 c.changes[diff2] = []
406 413 try:
407 414 if diff1 not in ['', None, 'None', '0' * 12, '0' * 40]:
408 415 c.changeset_1 = c.rhodecode_repo.get_changeset(diff1)
409 416 node1 = c.changeset_1.get_node(f_path)
410 417 else:
411 418 c.changeset_1 = EmptyChangeset(repo=c.rhodecode_repo)
412 419 node1 = FileNode('.', '', changeset=c.changeset_1)
413 420
414 421 if diff2 not in ['', None, 'None', '0' * 12, '0' * 40]:
415 422 c.changeset_2 = c.rhodecode_repo.get_changeset(diff2)
416 423 node2 = c.changeset_2.get_node(f_path)
417 424 else:
418 425 c.changeset_2 = EmptyChangeset(repo=c.rhodecode_repo)
419 426 node2 = FileNode('.', '', changeset=c.changeset_2)
420 427 except RepositoryError:
421 428 return redirect(url('files_home', repo_name=c.repo_name,
422 429 f_path=f_path))
423 430
424 431 if c.action == 'download':
425 432 _diff = diffs.get_gitdiff(node1, node2,
426 433 ignore_whitespace=ignore_whitespace,
427 434 context=line_context)
428 435 diff = diffs.DiffProcessor(_diff, format='gitdiff')
429 436
430 437 diff_name = '%s_vs_%s.diff' % (diff1, diff2)
431 438 response.content_type = 'text/plain'
432 439 response.content_disposition = (
433 440 'attachment; filename=%s' % diff_name
434 441 )
435 442 return diff.raw_diff()
436 443
437 444 elif c.action == 'raw':
438 445 _diff = diffs.get_gitdiff(node1, node2,
439 446 ignore_whitespace=ignore_whitespace,
440 447 context=line_context)
441 448 diff = diffs.DiffProcessor(_diff, format='gitdiff')
442 449 response.content_type = 'text/plain'
443 450 return diff.raw_diff()
444 451
445 452 else:
446 453 fid = h.FID(diff2, node2.path)
447 454 line_context_lcl = get_line_ctx(fid, request.GET)
448 455 ign_whitespace_lcl = get_ignore_ws(fid, request.GET)
449 456
450 457 lim = request.GET.get('fulldiff') or self.cut_off_limit
451 458 _, cs1, cs2, diff, st = diffs.wrapped_diff(filenode_old=node1,
452 459 filenode_new=node2,
453 460 cut_off_limit=lim,
454 461 ignore_whitespace=ign_whitespace_lcl,
455 462 line_context=line_context_lcl,
456 463 enable_comments=False)
457 464
458 465 c.changes = [('', node2, diff, cs1, cs2, st,)]
459 466
460 467 return render('files/file_diff.html')
461 468
462 469 def _get_node_history(self, cs, f_path, changesets=None):
463 470 if changesets is None:
464 471 changesets = cs.get_file_history(f_path)
465 472 hist_l = []
466 473
467 474 changesets_group = ([], _("Changesets"))
468 475 branches_group = ([], _("Branches"))
469 476 tags_group = ([], _("Tags"))
470 477 _hg = cs.repository.alias == 'hg'
471 478 for chs in changesets:
472 479 _branch = '(%s)' % chs.branch if _hg else ''
473 480 n_desc = 'r%s:%s %s' % (chs.revision, chs.short_id, _branch)
474 481 changesets_group[0].append((chs.raw_id, n_desc,))
475 482
476 483 hist_l.append(changesets_group)
477 484
478 485 for name, chs in c.rhodecode_repo.branches.items():
479 486 branches_group[0].append((chs, name),)
480 487 hist_l.append(branches_group)
481 488
482 489 for name, chs in c.rhodecode_repo.tags.items():
483 490 tags_group[0].append((chs, name),)
484 491 hist_l.append(tags_group)
485 492
486 493 return hist_l
487 494
488 @jsonify
495 @LoginRequired()
489 496 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
490 497 'repository.admin')
498 @jsonify
491 499 def nodelist(self, repo_name, revision, f_path):
492 500 if request.environ.get('HTTP_X_PARTIAL_XHR'):
493 501 cs = self.__get_cs_or_redirect(revision, repo_name)
494 502 _d, _f = ScmModel().get_nodes(repo_name, cs.raw_id, f_path,
495 503 flat=False)
496 504 return {'nodes': _d + _f}
General Comments 0
You need to be logged in to leave comments. Login now