##// END OF EJS Templates
files: don't load the file if it is over the size limit since it...
dan -
r1029:518666ba default
parent child Browse files
Show More
@@ -1,1122 +1,1125 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2016 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.html')
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.file_last_commit = c.file.last_commit
227 if c.annotate: # annotation has precedence over renderer
228 c.annotated_lines = filenode_as_annotated_lines_tokens(
229 c.file)
230 else:
231 c.renderer = (
232 c.renderer and h.renderer_from_filename(c.file.path))
233 if not c.renderer:
234 c.lines = filenode_as_lines_tokens(c.file)
227 if c.file.size < self.cut_off_limit_file:
228 if c.annotate: # annotation has precedence over renderer
229 c.annotated_lines = filenode_as_annotated_lines_tokens(
230 c.file
231 )
232 else:
233 c.renderer = (
234 c.renderer and h.renderer_from_filename(c.file.path)
235 )
236 if not c.renderer:
237 c.lines = filenode_as_lines_tokens(c.file)
235 238
236 239 c.on_branch_head = self._is_valid_head(
237 240 commit_id, c.rhodecode_repo)
238 241 c.branch_or_raw_id = c.commit.branch or c.commit.raw_id
239 242
240 243 author = c.file_last_commit.author
241 244 c.authors = [(h.email(author),
242 245 h.person(author, 'username_or_name_or_email'))]
243 246 else:
244 247 c.authors = []
245 248 c.file_tree = self._get_tree_at_commit(
246 249 repo_name, c.commit.raw_id, f_path)
247 250
248 251 except RepositoryError as e:
249 252 h.flash(safe_str(e), category='error')
250 253 raise HTTPNotFound()
251 254
252 255 if request.environ.get('HTTP_X_PJAX'):
253 256 return render('files/files_pjax.html')
254 257
255 258 return render('files/files.html')
256 259
257 260 @LoginRequired()
258 261 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
259 262 'repository.admin')
260 263 @jsonify
261 264 def history(self, repo_name, revision, f_path):
262 265 commit = self.__get_commit_or_redirect(revision, repo_name)
263 266 f_path = f_path
264 267 _file = commit.get_node(f_path)
265 268 if _file.is_file():
266 269 file_history, _hist = self._get_node_history(commit, f_path)
267 270
268 271 res = []
269 272 for obj in file_history:
270 273 res.append({
271 274 'text': obj[1],
272 275 'children': [{'id': o[0], 'text': o[1]} for o in obj[0]]
273 276 })
274 277
275 278 data = {
276 279 'more': False,
277 280 'results': res
278 281 }
279 282 return data
280 283
281 284 @LoginRequired()
282 285 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
283 286 'repository.admin')
284 287 def authors(self, repo_name, revision, f_path):
285 288 commit = self.__get_commit_or_redirect(revision, repo_name)
286 289 file_node = commit.get_node(f_path)
287 290 if file_node.is_file():
288 291 c.file_last_commit = file_node.last_commit
289 292 if request.GET.get('annotate') == '1':
290 293 # use _hist from annotation if annotation mode is on
291 294 commit_ids = set(x[1] for x in file_node.annotate)
292 295 _hist = (
293 296 c.rhodecode_repo.get_commit(commit_id)
294 297 for commit_id in commit_ids)
295 298 else:
296 299 _f_history, _hist = self._get_node_history(commit, f_path)
297 300 c.file_author = False
298 301 c.authors = []
299 302 for author in set(commit.author for commit in _hist):
300 303 c.authors.append((
301 304 h.email(author),
302 305 h.person(author, 'username_or_name_or_email')))
303 306 return render('files/file_authors_box.html')
304 307
305 308 @LoginRequired()
306 309 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
307 310 'repository.admin')
308 311 def rawfile(self, repo_name, revision, f_path):
309 312 """
310 313 Action for download as raw
311 314 """
312 315 commit = self.__get_commit_or_redirect(revision, repo_name)
313 316 file_node = self.__get_filenode_or_redirect(repo_name, commit, f_path)
314 317
315 318 response.content_disposition = 'attachment; filename=%s' % \
316 319 safe_str(f_path.split(Repository.NAME_SEP)[-1])
317 320
318 321 response.content_type = file_node.mimetype
319 322 charset = self._get_default_encoding()
320 323 if charset:
321 324 response.charset = charset
322 325
323 326 return file_node.content
324 327
325 328 @LoginRequired()
326 329 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
327 330 'repository.admin')
328 331 def raw(self, repo_name, revision, f_path):
329 332 """
330 333 Action for show as raw, some mimetypes are "rendered",
331 334 those include images, icons.
332 335 """
333 336 commit = self.__get_commit_or_redirect(revision, repo_name)
334 337 file_node = self.__get_filenode_or_redirect(repo_name, commit, f_path)
335 338
336 339 raw_mimetype_mapping = {
337 340 # map original mimetype to a mimetype used for "show as raw"
338 341 # you can also provide a content-disposition to override the
339 342 # default "attachment" disposition.
340 343 # orig_type: (new_type, new_dispo)
341 344
342 345 # show images inline:
343 346 # Do not re-add SVG: it is unsafe and permits XSS attacks. One can
344 347 # for example render an SVG with javascript inside or even render
345 348 # HTML.
346 349 'image/x-icon': ('image/x-icon', 'inline'),
347 350 'image/png': ('image/png', 'inline'),
348 351 'image/gif': ('image/gif', 'inline'),
349 352 'image/jpeg': ('image/jpeg', 'inline'),
350 353 }
351 354
352 355 mimetype = file_node.mimetype
353 356 try:
354 357 mimetype, dispo = raw_mimetype_mapping[mimetype]
355 358 except KeyError:
356 359 # we don't know anything special about this, handle it safely
357 360 if file_node.is_binary:
358 361 # do same as download raw for binary files
359 362 mimetype, dispo = 'application/octet-stream', 'attachment'
360 363 else:
361 364 # do not just use the original mimetype, but force text/plain,
362 365 # otherwise it would serve text/html and that might be unsafe.
363 366 # Note: underlying vcs library fakes text/plain mimetype if the
364 367 # mimetype can not be determined and it thinks it is not
365 368 # binary.This might lead to erroneous text display in some
366 369 # cases, but helps in other cases, like with text files
367 370 # without extension.
368 371 mimetype, dispo = 'text/plain', 'inline'
369 372
370 373 if dispo == 'attachment':
371 374 dispo = 'attachment; filename=%s' % safe_str(
372 375 f_path.split(os.sep)[-1])
373 376
374 377 response.content_disposition = dispo
375 378 response.content_type = mimetype
376 379 charset = self._get_default_encoding()
377 380 if charset:
378 381 response.charset = charset
379 382 return file_node.content
380 383
381 384 @CSRFRequired()
382 385 @LoginRequired()
383 386 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
384 387 def delete(self, repo_name, revision, f_path):
385 388 commit_id = revision
386 389
387 390 repo = c.rhodecode_db_repo
388 391 if repo.enable_locking and repo.locked[0]:
389 392 h.flash(_('This repository has been locked by %s on %s')
390 393 % (h.person_by_id(repo.locked[0]),
391 394 h.format_date(h.time_to_datetime(repo.locked[1]))),
392 395 'warning')
393 396 return redirect(h.url('files_home',
394 397 repo_name=repo_name, revision='tip'))
395 398
396 399 if not self._is_valid_head(commit_id, repo.scm_instance()):
397 400 h.flash(_('You can only delete files with revision '
398 401 'being a valid branch '), category='warning')
399 402 return redirect(h.url('files_home',
400 403 repo_name=repo_name, revision='tip',
401 404 f_path=f_path))
402 405
403 406 c.commit = self.__get_commit_or_redirect(commit_id, repo_name)
404 407 c.file = self.__get_filenode_or_redirect(repo_name, c.commit, f_path)
405 408
406 409 c.default_message = _(
407 410 'Deleted file %s via RhodeCode Enterprise') % (f_path)
408 411 c.f_path = f_path
409 412 node_path = f_path
410 413 author = c.rhodecode_user.full_contact
411 414 message = request.POST.get('message') or c.default_message
412 415 try:
413 416 nodes = {
414 417 node_path: {
415 418 'content': ''
416 419 }
417 420 }
418 421 self.scm_model.delete_nodes(
419 422 user=c.rhodecode_user.user_id, repo=c.rhodecode_db_repo,
420 423 message=message,
421 424 nodes=nodes,
422 425 parent_commit=c.commit,
423 426 author=author,
424 427 )
425 428
426 429 h.flash(_('Successfully deleted file %s') % f_path,
427 430 category='success')
428 431 except Exception:
429 432 msg = _('Error occurred during commit')
430 433 log.exception(msg)
431 434 h.flash(msg, category='error')
432 435 return redirect(url('changeset_home',
433 436 repo_name=c.repo_name, revision='tip'))
434 437
435 438 @LoginRequired()
436 439 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
437 440 def delete_home(self, repo_name, revision, f_path):
438 441 commit_id = revision
439 442
440 443 repo = c.rhodecode_db_repo
441 444 if repo.enable_locking and repo.locked[0]:
442 445 h.flash(_('This repository has been locked by %s on %s')
443 446 % (h.person_by_id(repo.locked[0]),
444 447 h.format_date(h.time_to_datetime(repo.locked[1]))),
445 448 'warning')
446 449 return redirect(h.url('files_home',
447 450 repo_name=repo_name, revision='tip'))
448 451
449 452 if not self._is_valid_head(commit_id, repo.scm_instance()):
450 453 h.flash(_('You can only delete files with revision '
451 454 'being a valid branch '), category='warning')
452 455 return redirect(h.url('files_home',
453 456 repo_name=repo_name, revision='tip',
454 457 f_path=f_path))
455 458
456 459 c.commit = self.__get_commit_or_redirect(commit_id, repo_name)
457 460 c.file = self.__get_filenode_or_redirect(repo_name, c.commit, f_path)
458 461
459 462 c.default_message = _(
460 463 'Deleted file %s via RhodeCode Enterprise') % (f_path)
461 464 c.f_path = f_path
462 465
463 466 return render('files/files_delete.html')
464 467
465 468 @CSRFRequired()
466 469 @LoginRequired()
467 470 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
468 471 def edit(self, repo_name, revision, f_path):
469 472 commit_id = revision
470 473
471 474 repo = c.rhodecode_db_repo
472 475 if repo.enable_locking and repo.locked[0]:
473 476 h.flash(_('This repository has been locked by %s on %s')
474 477 % (h.person_by_id(repo.locked[0]),
475 478 h.format_date(h.time_to_datetime(repo.locked[1]))),
476 479 'warning')
477 480 return redirect(h.url('files_home',
478 481 repo_name=repo_name, revision='tip'))
479 482
480 483 if not self._is_valid_head(commit_id, repo.scm_instance()):
481 484 h.flash(_('You can only edit files with revision '
482 485 'being a valid branch '), category='warning')
483 486 return redirect(h.url('files_home',
484 487 repo_name=repo_name, revision='tip',
485 488 f_path=f_path))
486 489
487 490 c.commit = self.__get_commit_or_redirect(commit_id, repo_name)
488 491 c.file = self.__get_filenode_or_redirect(repo_name, c.commit, f_path)
489 492
490 493 if c.file.is_binary:
491 494 return redirect(url('files_home', repo_name=c.repo_name,
492 495 revision=c.commit.raw_id, f_path=f_path))
493 496 c.default_message = _(
494 497 'Edited file %s via RhodeCode Enterprise') % (f_path)
495 498 c.f_path = f_path
496 499 old_content = c.file.content
497 500 sl = old_content.splitlines(1)
498 501 first_line = sl[0] if sl else ''
499 502
500 503 # modes: 0 - Unix, 1 - Mac, 2 - DOS
501 504 mode = detect_mode(first_line, 0)
502 505 content = convert_line_endings(request.POST.get('content', ''), mode)
503 506
504 507 message = request.POST.get('message') or c.default_message
505 508 org_f_path = c.file.unicode_path
506 509 filename = request.POST['filename']
507 510 org_filename = c.file.name
508 511
509 512 if content == old_content and filename == org_filename:
510 513 h.flash(_('No changes'), category='warning')
511 514 return redirect(url('changeset_home', repo_name=c.repo_name,
512 515 revision='tip'))
513 516 try:
514 517 mapping = {
515 518 org_f_path: {
516 519 'org_filename': org_f_path,
517 520 'filename': os.path.join(c.file.dir_path, filename),
518 521 'content': content,
519 522 'lexer': '',
520 523 'op': 'mod',
521 524 }
522 525 }
523 526
524 527 ScmModel().update_nodes(
525 528 user=c.rhodecode_user.user_id,
526 529 repo=c.rhodecode_db_repo,
527 530 message=message,
528 531 nodes=mapping,
529 532 parent_commit=c.commit,
530 533 )
531 534
532 535 h.flash(_('Successfully committed to %s') % f_path,
533 536 category='success')
534 537 except Exception:
535 538 msg = _('Error occurred during commit')
536 539 log.exception(msg)
537 540 h.flash(msg, category='error')
538 541 return redirect(url('changeset_home',
539 542 repo_name=c.repo_name, revision='tip'))
540 543
541 544 @LoginRequired()
542 545 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
543 546 def edit_home(self, repo_name, revision, f_path):
544 547 commit_id = revision
545 548
546 549 repo = c.rhodecode_db_repo
547 550 if repo.enable_locking and repo.locked[0]:
548 551 h.flash(_('This repository has been locked by %s on %s')
549 552 % (h.person_by_id(repo.locked[0]),
550 553 h.format_date(h.time_to_datetime(repo.locked[1]))),
551 554 'warning')
552 555 return redirect(h.url('files_home',
553 556 repo_name=repo_name, revision='tip'))
554 557
555 558 if not self._is_valid_head(commit_id, repo.scm_instance()):
556 559 h.flash(_('You can only edit files with revision '
557 560 'being a valid branch '), category='warning')
558 561 return redirect(h.url('files_home',
559 562 repo_name=repo_name, revision='tip',
560 563 f_path=f_path))
561 564
562 565 c.commit = self.__get_commit_or_redirect(commit_id, repo_name)
563 566 c.file = self.__get_filenode_or_redirect(repo_name, c.commit, f_path)
564 567
565 568 if c.file.is_binary:
566 569 return redirect(url('files_home', repo_name=c.repo_name,
567 570 revision=c.commit.raw_id, f_path=f_path))
568 571 c.default_message = _(
569 572 'Edited file %s via RhodeCode Enterprise') % (f_path)
570 573 c.f_path = f_path
571 574
572 575 return render('files/files_edit.html')
573 576
574 577 def _is_valid_head(self, commit_id, repo):
575 578 # check if commit is a branch identifier- basically we cannot
576 579 # create multiple heads via file editing
577 580 valid_heads = repo.branches.keys() + repo.branches.values()
578 581
579 582 if h.is_svn(repo) and not repo.is_empty():
580 583 # Note: Subversion only has one head, we add it here in case there
581 584 # is no branch matched.
582 585 valid_heads.append(repo.get_commit(commit_idx=-1).raw_id)
583 586
584 587 # check if commit is a branch name or branch hash
585 588 return commit_id in valid_heads
586 589
587 590 @CSRFRequired()
588 591 @LoginRequired()
589 592 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
590 593 def add(self, repo_name, revision, f_path):
591 594 repo = Repository.get_by_repo_name(repo_name)
592 595 if repo.enable_locking and repo.locked[0]:
593 596 h.flash(_('This repository has been locked by %s on %s')
594 597 % (h.person_by_id(repo.locked[0]),
595 598 h.format_date(h.time_to_datetime(repo.locked[1]))),
596 599 'warning')
597 600 return redirect(h.url('files_home',
598 601 repo_name=repo_name, revision='tip'))
599 602
600 603 r_post = request.POST
601 604
602 605 c.commit = self.__get_commit_or_redirect(
603 606 revision, repo_name, redirect_after=False)
604 607 if c.commit is None:
605 608 c.commit = EmptyCommit(alias=c.rhodecode_repo.alias)
606 609 c.default_message = (_('Added file via RhodeCode Enterprise'))
607 610 c.f_path = f_path
608 611 unix_mode = 0
609 612 content = convert_line_endings(r_post.get('content', ''), unix_mode)
610 613
611 614 message = r_post.get('message') or c.default_message
612 615 filename = r_post.get('filename')
613 616 location = r_post.get('location', '') # dir location
614 617 file_obj = r_post.get('upload_file', None)
615 618
616 619 if file_obj is not None and hasattr(file_obj, 'filename'):
617 620 filename = file_obj.filename
618 621 content = file_obj.file
619 622
620 623 if hasattr(content, 'file'):
621 624 # non posix systems store real file under file attr
622 625 content = content.file
623 626
624 627 # If there's no commit, redirect to repo summary
625 628 if type(c.commit) is EmptyCommit:
626 629 redirect_url = "summary_home"
627 630 else:
628 631 redirect_url = "changeset_home"
629 632
630 633 if not filename:
631 634 h.flash(_('No filename'), category='warning')
632 635 return redirect(url(redirect_url, repo_name=c.repo_name,
633 636 revision='tip'))
634 637
635 638 # extract the location from filename,
636 639 # allows using foo/bar.txt syntax to create subdirectories
637 640 subdir_loc = filename.rsplit('/', 1)
638 641 if len(subdir_loc) == 2:
639 642 location = os.path.join(location, subdir_loc[0])
640 643
641 644 # strip all crap out of file, just leave the basename
642 645 filename = os.path.basename(filename)
643 646 node_path = os.path.join(location, filename)
644 647 author = c.rhodecode_user.full_contact
645 648
646 649 try:
647 650 nodes = {
648 651 node_path: {
649 652 'content': content
650 653 }
651 654 }
652 655 self.scm_model.create_nodes(
653 656 user=c.rhodecode_user.user_id,
654 657 repo=c.rhodecode_db_repo,
655 658 message=message,
656 659 nodes=nodes,
657 660 parent_commit=c.commit,
658 661 author=author,
659 662 )
660 663
661 664 h.flash(_('Successfully committed to %s') % node_path,
662 665 category='success')
663 666 except NonRelativePathError as e:
664 667 h.flash(_(
665 668 'The location specified must be a relative path and must not '
666 669 'contain .. in the path'), category='warning')
667 670 return redirect(url('changeset_home', repo_name=c.repo_name,
668 671 revision='tip'))
669 672 except (NodeError, NodeAlreadyExistsError) as e:
670 673 h.flash(_(e), category='error')
671 674 except Exception:
672 675 msg = _('Error occurred during commit')
673 676 log.exception(msg)
674 677 h.flash(msg, category='error')
675 678 return redirect(url('changeset_home',
676 679 repo_name=c.repo_name, revision='tip'))
677 680
678 681 @LoginRequired()
679 682 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
680 683 def add_home(self, repo_name, revision, f_path):
681 684
682 685 repo = Repository.get_by_repo_name(repo_name)
683 686 if repo.enable_locking and repo.locked[0]:
684 687 h.flash(_('This repository has been locked by %s on %s')
685 688 % (h.person_by_id(repo.locked[0]),
686 689 h.format_date(h.time_to_datetime(repo.locked[1]))),
687 690 'warning')
688 691 return redirect(h.url('files_home',
689 692 repo_name=repo_name, revision='tip'))
690 693
691 694 c.commit = self.__get_commit_or_redirect(
692 695 revision, repo_name, redirect_after=False)
693 696 if c.commit is None:
694 697 c.commit = EmptyCommit(alias=c.rhodecode_repo.alias)
695 698 c.default_message = (_('Added file via RhodeCode Enterprise'))
696 699 c.f_path = f_path
697 700
698 701 return render('files/files_add.html')
699 702
700 703 @LoginRequired()
701 704 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
702 705 'repository.admin')
703 706 def archivefile(self, repo_name, fname):
704 707 fileformat = None
705 708 commit_id = None
706 709 ext = None
707 710 subrepos = request.GET.get('subrepos') == 'true'
708 711
709 712 for a_type, ext_data in settings.ARCHIVE_SPECS.items():
710 713 archive_spec = fname.split(ext_data[1])
711 714 if len(archive_spec) == 2 and archive_spec[1] == '':
712 715 fileformat = a_type or ext_data[1]
713 716 commit_id = archive_spec[0]
714 717 ext = ext_data[1]
715 718
716 719 dbrepo = RepoModel().get_by_repo_name(repo_name)
717 720 if not dbrepo.enable_downloads:
718 721 return _('Downloads disabled')
719 722
720 723 try:
721 724 commit = c.rhodecode_repo.get_commit(commit_id)
722 725 content_type = settings.ARCHIVE_SPECS[fileformat][0]
723 726 except CommitDoesNotExistError:
724 727 return _('Unknown revision %s') % commit_id
725 728 except EmptyRepositoryError:
726 729 return _('Empty repository')
727 730 except KeyError:
728 731 return _('Unknown archive type')
729 732
730 733 # archive cache
731 734 from rhodecode import CONFIG
732 735
733 736 archive_name = '%s-%s%s%s' % (
734 737 safe_str(repo_name.replace('/', '_')),
735 738 '-sub' if subrepos else '',
736 739 safe_str(commit.short_id), ext)
737 740
738 741 use_cached_archive = False
739 742 archive_cache_enabled = CONFIG.get(
740 743 'archive_cache_dir') and not request.GET.get('no_cache')
741 744
742 745 if archive_cache_enabled:
743 746 # check if we it's ok to write
744 747 if not os.path.isdir(CONFIG['archive_cache_dir']):
745 748 os.makedirs(CONFIG['archive_cache_dir'])
746 749 cached_archive_path = os.path.join(
747 750 CONFIG['archive_cache_dir'], archive_name)
748 751 if os.path.isfile(cached_archive_path):
749 752 log.debug('Found cached archive in %s', cached_archive_path)
750 753 fd, archive = None, cached_archive_path
751 754 use_cached_archive = True
752 755 else:
753 756 log.debug('Archive %s is not yet cached', archive_name)
754 757
755 758 if not use_cached_archive:
756 759 # generate new archive
757 760 fd, archive = tempfile.mkstemp()
758 761 log.debug('Creating new temp archive in %s' % (archive,))
759 762 try:
760 763 commit.archive_repo(archive, kind=fileformat, subrepos=subrepos)
761 764 except ImproperArchiveTypeError:
762 765 return _('Unknown archive type')
763 766 if archive_cache_enabled:
764 767 # if we generated the archive and we have cache enabled
765 768 # let's use this for future
766 769 log.debug('Storing new archive in %s' % (cached_archive_path,))
767 770 shutil.move(archive, cached_archive_path)
768 771 archive = cached_archive_path
769 772
770 773 def get_chunked_archive(archive):
771 774 with open(archive, 'rb') as stream:
772 775 while True:
773 776 data = stream.read(16 * 1024)
774 777 if not data:
775 778 if fd: # fd means we used temporary file
776 779 os.close(fd)
777 780 if not archive_cache_enabled:
778 781 log.debug('Destroying temp archive %s', archive)
779 782 os.remove(archive)
780 783 break
781 784 yield data
782 785
783 786 # store download action
784 787 action_logger(user=c.rhodecode_user,
785 788 action='user_downloaded_archive:%s' % archive_name,
786 789 repo=repo_name, ipaddr=self.ip_addr, commit=True)
787 790 response.content_disposition = str(
788 791 'attachment; filename=%s' % archive_name)
789 792 response.content_type = str(content_type)
790 793
791 794 return get_chunked_archive(archive)
792 795
793 796 @LoginRequired()
794 797 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
795 798 'repository.admin')
796 799 def diff(self, repo_name, f_path):
797 800 ignore_whitespace = request.GET.get('ignorews') == '1'
798 801 line_context = request.GET.get('context', 3)
799 802 diff1 = request.GET.get('diff1', '')
800 803
801 804 path1, diff1 = parse_path_ref(diff1, default_path=f_path)
802 805
803 806 diff2 = request.GET.get('diff2', '')
804 807 c.action = request.GET.get('diff')
805 808 c.no_changes = diff1 == diff2
806 809 c.f_path = f_path
807 810 c.big_diff = False
808 811 c.ignorews_url = _ignorews_url
809 812 c.context_url = _context_url
810 813 c.changes = OrderedDict()
811 814 c.changes[diff2] = []
812 815
813 816 if not any((diff1, diff2)):
814 817 h.flash(
815 818 'Need query parameter "diff1" or "diff2" to generate a diff.',
816 819 category='error')
817 820 raise HTTPBadRequest()
818 821
819 822 # special case if we want a show commit_id only, it's impl here
820 823 # to reduce JS and callbacks
821 824
822 825 if request.GET.get('show_rev') and diff1:
823 826 if str2bool(request.GET.get('annotate', 'False')):
824 827 _url = url('files_annotate_home', repo_name=c.repo_name,
825 828 revision=diff1, f_path=path1)
826 829 else:
827 830 _url = url('files_home', repo_name=c.repo_name,
828 831 revision=diff1, f_path=path1)
829 832
830 833 return redirect(_url)
831 834
832 835 try:
833 836 node1 = self._get_file_node(diff1, path1)
834 837 node2 = self._get_file_node(diff2, f_path)
835 838 except (RepositoryError, NodeError):
836 839 log.exception("Exception while trying to get node from repository")
837 840 return redirect(url(
838 841 'files_home', repo_name=c.repo_name, f_path=f_path))
839 842
840 843 if all(isinstance(node.commit, EmptyCommit)
841 844 for node in (node1, node2)):
842 845 raise HTTPNotFound
843 846
844 847 c.commit_1 = node1.commit
845 848 c.commit_2 = node2.commit
846 849
847 850 if c.action == 'download':
848 851 _diff = diffs.get_gitdiff(node1, node2,
849 852 ignore_whitespace=ignore_whitespace,
850 853 context=line_context)
851 854 diff = diffs.DiffProcessor(_diff, format='gitdiff')
852 855
853 856 diff_name = '%s_vs_%s.diff' % (diff1, diff2)
854 857 response.content_type = 'text/plain'
855 858 response.content_disposition = (
856 859 'attachment; filename=%s' % (diff_name,)
857 860 )
858 861 charset = self._get_default_encoding()
859 862 if charset:
860 863 response.charset = charset
861 864 return diff.as_raw()
862 865
863 866 elif c.action == 'raw':
864 867 _diff = diffs.get_gitdiff(node1, node2,
865 868 ignore_whitespace=ignore_whitespace,
866 869 context=line_context)
867 870 diff = diffs.DiffProcessor(_diff, format='gitdiff')
868 871 response.content_type = 'text/plain'
869 872 charset = self._get_default_encoding()
870 873 if charset:
871 874 response.charset = charset
872 875 return diff.as_raw()
873 876
874 877 else:
875 878 fid = h.FID(diff2, node2.path)
876 879 line_context_lcl = get_line_ctx(fid, request.GET)
877 880 ign_whitespace_lcl = get_ignore_ws(fid, request.GET)
878 881
879 882 __, commit1, commit2, diff, st, data = diffs.wrapped_diff(
880 883 filenode_old=node1,
881 884 filenode_new=node2,
882 885 diff_limit=self.cut_off_limit_diff,
883 886 file_limit=self.cut_off_limit_file,
884 887 show_full_diff=request.GET.get('fulldiff'),
885 888 ignore_whitespace=ign_whitespace_lcl,
886 889 line_context=line_context_lcl,)
887 890
888 891 c.lines_added = data['stats']['added'] if data else 0
889 892 c.lines_deleted = data['stats']['deleted'] if data else 0
890 893 c.files = [data]
891 894 c.commit_ranges = [c.commit_1, c.commit_2]
892 895 c.ancestor = None
893 896 c.statuses = []
894 897 c.target_repo = c.rhodecode_db_repo
895 898 c.filename1 = node1.path
896 899 c.filename = node2.path
897 900 c.binary_file = node1.is_binary or node2.is_binary
898 901 operation = data['operation'] if data else ''
899 902
900 903 commit_changes = {
901 904 # TODO: it's passing the old file to the diff to keep the
902 905 # standard but this is not being used for this template,
903 906 # but might need both files in the future or a more standard
904 907 # way to work with that
905 908 'fid': [commit1, commit2, operation,
906 909 c.filename, diff, st, data]
907 910 }
908 911
909 912 c.changes = commit_changes
910 913
911 914 return render('files/file_diff.html')
912 915
913 916 @LoginRequired()
914 917 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
915 918 'repository.admin')
916 919 def diff_2way(self, repo_name, f_path):
917 920 diff1 = request.GET.get('diff1', '')
918 921 diff2 = request.GET.get('diff2', '')
919 922
920 923 nodes = []
921 924 unknown_commits = []
922 925 for commit in [diff1, diff2]:
923 926 try:
924 927 nodes.append(self._get_file_node(commit, f_path))
925 928 except (RepositoryError, NodeError):
926 929 log.exception('%(commit)s does not exist' % {'commit': commit})
927 930 unknown_commits.append(commit)
928 931 h.flash(h.literal(
929 932 _('Commit %(commit)s does not exist.') % {'commit': commit}
930 933 ), category='error')
931 934
932 935 if unknown_commits:
933 936 return redirect(url('files_home', repo_name=c.repo_name,
934 937 f_path=f_path))
935 938
936 939 if all(isinstance(node.commit, EmptyCommit) for node in nodes):
937 940 raise HTTPNotFound
938 941
939 942 node1, node2 = nodes
940 943
941 944 f_gitdiff = diffs.get_gitdiff(node1, node2, ignore_whitespace=False)
942 945 diff_processor = diffs.DiffProcessor(f_gitdiff, format='gitdiff')
943 946 diff_data = diff_processor.prepare()
944 947
945 948 if not diff_data or diff_data[0]['raw_diff'] == '':
946 949 h.flash(h.literal(_('%(file_path)s has not changed '
947 950 'between %(commit_1)s and %(commit_2)s.') % {
948 951 'file_path': f_path,
949 952 'commit_1': node1.commit.id,
950 953 'commit_2': node2.commit.id
951 954 }), category='error')
952 955 return redirect(url('files_home', repo_name=c.repo_name,
953 956 f_path=f_path))
954 957
955 958 c.diff_data = diff_data[0]
956 959 c.FID = h.FID(diff2, node2.path)
957 960 # cleanup some unneeded data
958 961 del c.diff_data['raw_diff']
959 962 del c.diff_data['chunks']
960 963
961 964 c.node1 = node1
962 965 c.commit_1 = node1.commit
963 966 c.node2 = node2
964 967 c.commit_2 = node2.commit
965 968
966 969 return render('files/diff_2way.html')
967 970
968 971 def _get_file_node(self, commit_id, f_path):
969 972 if commit_id not in ['', None, 'None', '0' * 12, '0' * 40]:
970 973 commit = c.rhodecode_repo.get_commit(commit_id=commit_id)
971 974 try:
972 975 node = commit.get_node(f_path)
973 976 if node.is_dir():
974 977 raise NodeError('%s path is a %s not a file'
975 978 % (node, type(node)))
976 979 except NodeDoesNotExistError:
977 980 commit = EmptyCommit(
978 981 commit_id=commit_id,
979 982 idx=commit.idx,
980 983 repo=commit.repository,
981 984 alias=commit.repository.alias,
982 985 message=commit.message,
983 986 author=commit.author,
984 987 date=commit.date)
985 988 node = FileNode(f_path, '', commit=commit)
986 989 else:
987 990 commit = EmptyCommit(
988 991 repo=c.rhodecode_repo,
989 992 alias=c.rhodecode_repo.alias)
990 993 node = FileNode(f_path, '', commit=commit)
991 994 return node
992 995
993 996 def _get_node_history(self, commit, f_path, commits=None):
994 997 """
995 998 get commit history for given node
996 999
997 1000 :param commit: commit to calculate history
998 1001 :param f_path: path for node to calculate history for
999 1002 :param commits: if passed don't calculate history and take
1000 1003 commits defined in this list
1001 1004 """
1002 1005 # calculate history based on tip
1003 1006 tip = c.rhodecode_repo.get_commit()
1004 1007 if commits is None:
1005 1008 pre_load = ["author", "branch"]
1006 1009 try:
1007 1010 commits = tip.get_file_history(f_path, pre_load=pre_load)
1008 1011 except (NodeDoesNotExistError, CommitError):
1009 1012 # this node is not present at tip!
1010 1013 commits = commit.get_file_history(f_path, pre_load=pre_load)
1011 1014
1012 1015 history = []
1013 1016 commits_group = ([], _("Changesets"))
1014 1017 for commit in commits:
1015 1018 branch = ' (%s)' % commit.branch if commit.branch else ''
1016 1019 n_desc = 'r%s:%s%s' % (commit.idx, commit.short_id, branch)
1017 1020 commits_group[0].append((commit.raw_id, n_desc,))
1018 1021 history.append(commits_group)
1019 1022
1020 1023 symbolic_reference = self._symbolic_reference
1021 1024
1022 1025 if c.rhodecode_repo.alias == 'svn':
1023 1026 adjusted_f_path = self._adjust_file_path_for_svn(
1024 1027 f_path, c.rhodecode_repo)
1025 1028 if adjusted_f_path != f_path:
1026 1029 log.debug(
1027 1030 'Recognized svn tag or branch in file "%s", using svn '
1028 1031 'specific symbolic references', f_path)
1029 1032 f_path = adjusted_f_path
1030 1033 symbolic_reference = self._symbolic_reference_svn
1031 1034
1032 1035 branches = self._create_references(
1033 1036 c.rhodecode_repo.branches, symbolic_reference, f_path)
1034 1037 branches_group = (branches, _("Branches"))
1035 1038
1036 1039 tags = self._create_references(
1037 1040 c.rhodecode_repo.tags, symbolic_reference, f_path)
1038 1041 tags_group = (tags, _("Tags"))
1039 1042
1040 1043 history.append(branches_group)
1041 1044 history.append(tags_group)
1042 1045
1043 1046 return history, commits
1044 1047
1045 1048 def _adjust_file_path_for_svn(self, f_path, repo):
1046 1049 """
1047 1050 Computes the relative path of `f_path`.
1048 1051
1049 1052 This is mainly based on prefix matching of the recognized tags and
1050 1053 branches in the underlying repository.
1051 1054 """
1052 1055 tags_and_branches = itertools.chain(
1053 1056 repo.branches.iterkeys(),
1054 1057 repo.tags.iterkeys())
1055 1058 tags_and_branches = sorted(tags_and_branches, key=len, reverse=True)
1056 1059
1057 1060 for name in tags_and_branches:
1058 1061 if f_path.startswith(name + '/'):
1059 1062 f_path = vcspath.relpath(f_path, name)
1060 1063 break
1061 1064 return f_path
1062 1065
1063 1066 def _create_references(
1064 1067 self, branches_or_tags, symbolic_reference, f_path):
1065 1068 items = []
1066 1069 for name, commit_id in branches_or_tags.items():
1067 1070 sym_ref = symbolic_reference(commit_id, name, f_path)
1068 1071 items.append((sym_ref, name))
1069 1072 return items
1070 1073
1071 1074 def _symbolic_reference(self, commit_id, name, f_path):
1072 1075 return commit_id
1073 1076
1074 1077 def _symbolic_reference_svn(self, commit_id, name, f_path):
1075 1078 new_f_path = vcspath.join(name, f_path)
1076 1079 return u'%s@%s' % (new_f_path, commit_id)
1077 1080
1078 1081 @LoginRequired()
1079 1082 @XHRRequired()
1080 1083 @HasRepoPermissionAnyDecorator(
1081 1084 'repository.read', 'repository.write', 'repository.admin')
1082 1085 @jsonify
1083 1086 def nodelist(self, repo_name, revision, f_path):
1084 1087 commit = self.__get_commit_or_redirect(revision, repo_name)
1085 1088
1086 1089 metadata = self._get_nodelist_at_commit(
1087 1090 repo_name, commit.raw_id, f_path)
1088 1091 return {'nodes': metadata}
1089 1092
1090 1093 @LoginRequired()
1091 1094 @XHRRequired()
1092 1095 @HasRepoPermissionAnyDecorator(
1093 1096 'repository.read', 'repository.write', 'repository.admin')
1094 1097 def nodetree_full(self, repo_name, commit_id, f_path):
1095 1098 """
1096 1099 Returns rendered html of file tree that contains commit date,
1097 1100 author, revision for the specified combination of
1098 1101 repo, commit_id and file path
1099 1102
1100 1103 :param repo_name: name of the repository
1101 1104 :param commit_id: commit_id of file tree
1102 1105 :param f_path: file path of the requested directory
1103 1106 """
1104 1107
1105 1108 commit = self.__get_commit_or_redirect(commit_id, repo_name)
1106 1109 try:
1107 1110 dir_node = commit.get_node(f_path)
1108 1111 except RepositoryError as e:
1109 1112 return 'error {}'.format(safe_str(e))
1110 1113
1111 1114 if dir_node.is_file():
1112 1115 return ''
1113 1116
1114 1117 c.file = dir_node
1115 1118 c.commit = commit
1116 1119
1117 1120 # using force=True here, make a little trick. We flush the cache and
1118 1121 # compute it using the same key as without full_load, so the fully
1119 1122 # loaded cached tree is now returned instead of partial
1120 1123 return self._get_tree_at_commit(
1121 1124 repo_name, commit.raw_id, dir_node.path, full_load=True,
1122 1125 force=True)
General Comments 0
You need to be logged in to leave comments. Login now