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