##// END OF EJS Templates
cache: turn off caches if expiration_time is 0
marcink -
r2848:daea8b54 default
parent child Browse files
Show More
@@ -1,1278 +1,1297 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2011-2018 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 import itertools
22 22 import logging
23 23 import os
24 24 import shutil
25 25 import tempfile
26 26 import collections
27 27
28 28 from pyramid.httpexceptions import HTTPNotFound, HTTPBadRequest, HTTPFound
29 29 from pyramid.view import view_config
30 30 from pyramid.renderers import render
31 31 from pyramid.response import Response
32 32
33 import rhodecode
33 34 from rhodecode.apps._base import RepoAppView
34 35
35 36 from rhodecode.controllers.utils import parse_path_ref
36 37 from rhodecode.lib import diffs, helpers as h, caches, rc_cache
37 38 from rhodecode.lib import audit_logger
38 39 from rhodecode.lib.exceptions import NonRelativePathError
39 40 from rhodecode.lib.codeblocks import (
40 41 filenode_as_lines_tokens, filenode_as_annotated_lines_tokens)
41 42 from rhodecode.lib.utils2 import (
42 convert_line_endings, detect_mode, safe_str, str2bool)
43 convert_line_endings, detect_mode, safe_str, str2bool, safe_int)
43 44 from rhodecode.lib.auth import (
44 45 LoginRequired, HasRepoPermissionAnyDecorator, CSRFRequired)
45 46 from rhodecode.lib.vcs import path as vcspath
46 47 from rhodecode.lib.vcs.backends.base import EmptyCommit
47 48 from rhodecode.lib.vcs.conf import settings
48 49 from rhodecode.lib.vcs.nodes import FileNode
49 50 from rhodecode.lib.vcs.exceptions import (
50 51 RepositoryError, CommitDoesNotExistError, EmptyRepositoryError,
51 52 ImproperArchiveTypeError, VCSError, NodeAlreadyExistsError,
52 53 NodeDoesNotExistError, CommitError, NodeError)
53 54
54 55 from rhodecode.model.scm import ScmModel
55 56 from rhodecode.model.db import Repository
56 57
57 58 log = logging.getLogger(__name__)
58 59
59 60
60 61 class RepoFilesView(RepoAppView):
61 62
62 63 @staticmethod
63 64 def adjust_file_path_for_svn(f_path, repo):
64 65 """
65 66 Computes the relative path of `f_path`.
66 67
67 68 This is mainly based on prefix matching of the recognized tags and
68 69 branches in the underlying repository.
69 70 """
70 71 tags_and_branches = itertools.chain(
71 72 repo.branches.iterkeys(),
72 73 repo.tags.iterkeys())
73 74 tags_and_branches = sorted(tags_and_branches, key=len, reverse=True)
74 75
75 76 for name in tags_and_branches:
76 77 if f_path.startswith('{}/'.format(name)):
77 78 f_path = vcspath.relpath(f_path, name)
78 79 break
79 80 return f_path
80 81
81 82 def load_default_context(self):
82 83 c = self._get_local_tmpl_context(include_app_defaults=True)
83 84 c.rhodecode_repo = self.rhodecode_vcs_repo
84 85 return c
85 86
86 87 def _ensure_not_locked(self):
87 88 _ = self.request.translate
88 89
89 90 repo = self.db_repo
90 91 if repo.enable_locking and repo.locked[0]:
91 92 h.flash(_('This repository has been locked by %s on %s')
92 93 % (h.person_by_id(repo.locked[0]),
93 94 h.format_date(h.time_to_datetime(repo.locked[1]))),
94 95 'warning')
95 96 files_url = h.route_path(
96 97 'repo_files:default_path',
97 98 repo_name=self.db_repo_name, commit_id='tip')
98 99 raise HTTPFound(files_url)
99 100
100 101 def _get_commit_and_path(self):
101 102 default_commit_id = self.db_repo.landing_rev[1]
102 103 default_f_path = '/'
103 104
104 105 commit_id = self.request.matchdict.get(
105 106 'commit_id', default_commit_id)
106 107 f_path = self._get_f_path(self.request.matchdict, default_f_path)
107 108 return commit_id, f_path
108 109
109 110 def _get_default_encoding(self, c):
110 111 enc_list = getattr(c, 'default_encodings', [])
111 112 return enc_list[0] if enc_list else 'UTF-8'
112 113
113 114 def _get_commit_or_redirect(self, commit_id, redirect_after=True):
114 115 """
115 116 This is a safe way to get commit. If an error occurs it redirects to
116 117 tip with proper message
117 118
118 119 :param commit_id: id of commit to fetch
119 120 :param redirect_after: toggle redirection
120 121 """
121 122 _ = self.request.translate
122 123
123 124 try:
124 125 return self.rhodecode_vcs_repo.get_commit(commit_id)
125 126 except EmptyRepositoryError:
126 127 if not redirect_after:
127 128 return None
128 129
129 130 _url = h.route_path(
130 131 'repo_files_add_file',
131 132 repo_name=self.db_repo_name, commit_id=0, f_path='',
132 133 _anchor='edit')
133 134
134 135 if h.HasRepoPermissionAny(
135 136 'repository.write', 'repository.admin')(self.db_repo_name):
136 137 add_new = h.link_to(
137 138 _('Click here to add a new file.'), _url, class_="alert-link")
138 139 else:
139 140 add_new = ""
140 141
141 142 h.flash(h.literal(
142 143 _('There are no files yet. %s') % add_new), category='warning')
143 144 raise HTTPFound(
144 145 h.route_path('repo_summary', repo_name=self.db_repo_name))
145 146
146 147 except (CommitDoesNotExistError, LookupError):
147 148 msg = _('No such commit exists for this repository')
148 149 h.flash(msg, category='error')
149 150 raise HTTPNotFound()
150 151 except RepositoryError as e:
151 152 h.flash(safe_str(h.escape(e)), category='error')
152 153 raise HTTPNotFound()
153 154
154 155 def _get_filenode_or_redirect(self, commit_obj, path):
155 156 """
156 157 Returns file_node, if error occurs or given path is directory,
157 158 it'll redirect to top level path
158 159 """
159 160 _ = self.request.translate
160 161
161 162 try:
162 163 file_node = commit_obj.get_node(path)
163 164 if file_node.is_dir():
164 165 raise RepositoryError('The given path is a directory')
165 166 except CommitDoesNotExistError:
166 167 log.exception('No such commit exists for this repository')
167 168 h.flash(_('No such commit exists for this repository'), category='error')
168 169 raise HTTPNotFound()
169 170 except RepositoryError as e:
170 171 log.warning('Repository error while fetching '
171 172 'filenode `%s`. Err:%s', path, e)
172 173 h.flash(safe_str(h.escape(e)), category='error')
173 174 raise HTTPNotFound()
174 175
175 176 return file_node
176 177
177 178 def _is_valid_head(self, commit_id, repo):
178 179 # check if commit is a branch identifier- basically we cannot
179 180 # create multiple heads via file editing
180 181 valid_heads = repo.branches.keys() + repo.branches.values()
181 182
182 183 if h.is_svn(repo) and not repo.is_empty():
183 184 # Note: Subversion only has one head, we add it here in case there
184 185 # is no branch matched.
185 186 valid_heads.append(repo.get_commit(commit_idx=-1).raw_id)
186 187
187 188 # check if commit is a branch name or branch hash
188 189 return commit_id in valid_heads
189 190
190 191 def _get_tree_at_commit(
191 192 self, c, commit_id, f_path, full_load=False):
192 193
193 194 repo_id = self.db_repo.repo_id
194 195
196 cache_seconds = safe_int(
197 rhodecode.CONFIG.get('rc_cache.cache_repo.expiration_time'))
198 cache_on = cache_seconds > 0
199 log.debug(
200 'Computing FILE TREE for repo_id %s commit_id `%s` and path `%s`'
201 'with caching: %s[TTL: %ss]' % (
202 repo_id, commit_id, f_path, cache_on, cache_seconds or 0))
203
195 204 cache_namespace_uid = 'cache_repo.{}'.format(repo_id)
196 205 region = rc_cache.get_or_create_region('cache_repo', cache_namespace_uid)
197 206
198 @region.cache_on_arguments(namespace=cache_namespace_uid)
207 @region.cache_on_arguments(namespace=cache_namespace_uid,
208 should_cache_fn=lambda v: cache_on)
199 209 def compute_file_tree(repo_id, commit_id, f_path, full_load):
200 210 log.debug('Generating cached file tree for repo_id: %s, %s, %s',
201 211 repo_id, commit_id, f_path)
202 212
203 213 c.full_load = full_load
204 214 return render(
205 215 'rhodecode:templates/files/files_browser_tree.mako',
206 216 self._get_template_context(c), self.request)
207 217
208 218 return compute_file_tree(self.db_repo.repo_id, commit_id, f_path, full_load)
209 219
210 220 def _get_archive_spec(self, fname):
211 221 log.debug('Detecting archive spec for: `%s`', fname)
212 222
213 223 fileformat = None
214 224 ext = None
215 225 content_type = None
216 226 for a_type, ext_data in settings.ARCHIVE_SPECS.items():
217 227 content_type, extension = ext_data
218 228
219 229 if fname.endswith(extension):
220 230 fileformat = a_type
221 231 log.debug('archive is of type: %s', fileformat)
222 232 ext = extension
223 233 break
224 234
225 235 if not fileformat:
226 236 raise ValueError()
227 237
228 238 # left over part of whole fname is the commit
229 239 commit_id = fname[:-len(ext)]
230 240
231 241 return commit_id, ext, fileformat, content_type
232 242
233 243 @LoginRequired()
234 244 @HasRepoPermissionAnyDecorator(
235 245 'repository.read', 'repository.write', 'repository.admin')
236 246 @view_config(
237 247 route_name='repo_archivefile', request_method='GET',
238 248 renderer=None)
239 249 def repo_archivefile(self):
240 250 # archive cache config
241 251 from rhodecode import CONFIG
242 252 _ = self.request.translate
243 253 self.load_default_context()
244 254
245 255 fname = self.request.matchdict['fname']
246 256 subrepos = self.request.GET.get('subrepos') == 'true'
247 257
248 258 if not self.db_repo.enable_downloads:
249 259 return Response(_('Downloads disabled'))
250 260
251 261 try:
252 262 commit_id, ext, fileformat, content_type = \
253 263 self._get_archive_spec(fname)
254 264 except ValueError:
255 265 return Response(_('Unknown archive type for: `{}`').format(
256 266 h.escape(fname)))
257 267
258 268 try:
259 269 commit = self.rhodecode_vcs_repo.get_commit(commit_id)
260 270 except CommitDoesNotExistError:
261 271 return Response(_('Unknown commit_id {}').format(
262 272 h.escape(commit_id)))
263 273 except EmptyRepositoryError:
264 274 return Response(_('Empty repository'))
265 275
266 276 archive_name = '%s-%s%s%s' % (
267 277 safe_str(self.db_repo_name.replace('/', '_')),
268 278 '-sub' if subrepos else '',
269 279 safe_str(commit.short_id), ext)
270 280
271 281 use_cached_archive = False
272 282 archive_cache_enabled = CONFIG.get(
273 283 'archive_cache_dir') and not self.request.GET.get('no_cache')
274 284
275 285 if archive_cache_enabled:
276 286 # check if we it's ok to write
277 287 if not os.path.isdir(CONFIG['archive_cache_dir']):
278 288 os.makedirs(CONFIG['archive_cache_dir'])
279 289 cached_archive_path = os.path.join(
280 290 CONFIG['archive_cache_dir'], archive_name)
281 291 if os.path.isfile(cached_archive_path):
282 292 log.debug('Found cached archive in %s', cached_archive_path)
283 293 fd, archive = None, cached_archive_path
284 294 use_cached_archive = True
285 295 else:
286 296 log.debug('Archive %s is not yet cached', archive_name)
287 297
288 298 if not use_cached_archive:
289 299 # generate new archive
290 300 fd, archive = tempfile.mkstemp()
291 301 log.debug('Creating new temp archive in %s', archive)
292 302 try:
293 303 commit.archive_repo(archive, kind=fileformat, subrepos=subrepos)
294 304 except ImproperArchiveTypeError:
295 305 return _('Unknown archive type')
296 306 if archive_cache_enabled:
297 307 # if we generated the archive and we have cache enabled
298 308 # let's use this for future
299 309 log.debug('Storing new archive in %s', cached_archive_path)
300 310 shutil.move(archive, cached_archive_path)
301 311 archive = cached_archive_path
302 312
303 313 # store download action
304 314 audit_logger.store_web(
305 315 'repo.archive.download', action_data={
306 316 'user_agent': self.request.user_agent,
307 317 'archive_name': archive_name,
308 318 'archive_spec': fname,
309 319 'archive_cached': use_cached_archive},
310 320 user=self._rhodecode_user,
311 321 repo=self.db_repo,
312 322 commit=True
313 323 )
314 324
315 325 def get_chunked_archive(archive):
316 326 with open(archive, 'rb') as stream:
317 327 while True:
318 328 data = stream.read(16 * 1024)
319 329 if not data:
320 330 if fd: # fd means we used temporary file
321 331 os.close(fd)
322 332 if not archive_cache_enabled:
323 333 log.debug('Destroying temp archive %s', archive)
324 334 os.remove(archive)
325 335 break
326 336 yield data
327 337
328 338 response = Response(app_iter=get_chunked_archive(archive))
329 339 response.content_disposition = str(
330 340 'attachment; filename=%s' % archive_name)
331 341 response.content_type = str(content_type)
332 342
333 343 return response
334 344
335 345 def _get_file_node(self, commit_id, f_path):
336 346 if commit_id not in ['', None, 'None', '0' * 12, '0' * 40]:
337 347 commit = self.rhodecode_vcs_repo.get_commit(commit_id=commit_id)
338 348 try:
339 349 node = commit.get_node(f_path)
340 350 if node.is_dir():
341 351 raise NodeError('%s path is a %s not a file'
342 352 % (node, type(node)))
343 353 except NodeDoesNotExistError:
344 354 commit = EmptyCommit(
345 355 commit_id=commit_id,
346 356 idx=commit.idx,
347 357 repo=commit.repository,
348 358 alias=commit.repository.alias,
349 359 message=commit.message,
350 360 author=commit.author,
351 361 date=commit.date)
352 362 node = FileNode(f_path, '', commit=commit)
353 363 else:
354 364 commit = EmptyCommit(
355 365 repo=self.rhodecode_vcs_repo,
356 366 alias=self.rhodecode_vcs_repo.alias)
357 367 node = FileNode(f_path, '', commit=commit)
358 368 return node
359 369
360 370 @LoginRequired()
361 371 @HasRepoPermissionAnyDecorator(
362 372 'repository.read', 'repository.write', 'repository.admin')
363 373 @view_config(
364 374 route_name='repo_files_diff', request_method='GET',
365 375 renderer=None)
366 376 def repo_files_diff(self):
367 377 c = self.load_default_context()
368 378 f_path = self._get_f_path(self.request.matchdict)
369 379 diff1 = self.request.GET.get('diff1', '')
370 380 diff2 = self.request.GET.get('diff2', '')
371 381
372 382 path1, diff1 = parse_path_ref(diff1, default_path=f_path)
373 383
374 384 ignore_whitespace = str2bool(self.request.GET.get('ignorews'))
375 385 line_context = self.request.GET.get('context', 3)
376 386
377 387 if not any((diff1, diff2)):
378 388 h.flash(
379 389 'Need query parameter "diff1" or "diff2" to generate a diff.',
380 390 category='error')
381 391 raise HTTPBadRequest()
382 392
383 393 c.action = self.request.GET.get('diff')
384 394 if c.action not in ['download', 'raw']:
385 395 compare_url = h.route_path(
386 396 'repo_compare',
387 397 repo_name=self.db_repo_name,
388 398 source_ref_type='rev',
389 399 source_ref=diff1,
390 400 target_repo=self.db_repo_name,
391 401 target_ref_type='rev',
392 402 target_ref=diff2,
393 403 _query=dict(f_path=f_path))
394 404 # redirect to new view if we render diff
395 405 raise HTTPFound(compare_url)
396 406
397 407 try:
398 408 node1 = self._get_file_node(diff1, path1)
399 409 node2 = self._get_file_node(diff2, f_path)
400 410 except (RepositoryError, NodeError):
401 411 log.exception("Exception while trying to get node from repository")
402 412 raise HTTPFound(
403 413 h.route_path('repo_files', repo_name=self.db_repo_name,
404 414 commit_id='tip', f_path=f_path))
405 415
406 416 if all(isinstance(node.commit, EmptyCommit)
407 417 for node in (node1, node2)):
408 418 raise HTTPNotFound()
409 419
410 420 c.commit_1 = node1.commit
411 421 c.commit_2 = node2.commit
412 422
413 423 if c.action == 'download':
414 424 _diff = diffs.get_gitdiff(node1, node2,
415 425 ignore_whitespace=ignore_whitespace,
416 426 context=line_context)
417 427 diff = diffs.DiffProcessor(_diff, format='gitdiff')
418 428
419 429 response = Response(self.path_filter.get_raw_patch(diff))
420 430 response.content_type = 'text/plain'
421 431 response.content_disposition = (
422 432 'attachment; filename=%s_%s_vs_%s.diff' % (f_path, diff1, diff2)
423 433 )
424 434 charset = self._get_default_encoding(c)
425 435 if charset:
426 436 response.charset = charset
427 437 return response
428 438
429 439 elif c.action == 'raw':
430 440 _diff = diffs.get_gitdiff(node1, node2,
431 441 ignore_whitespace=ignore_whitespace,
432 442 context=line_context)
433 443 diff = diffs.DiffProcessor(_diff, format='gitdiff')
434 444
435 445 response = Response(self.path_filter.get_raw_patch(diff))
436 446 response.content_type = 'text/plain'
437 447 charset = self._get_default_encoding(c)
438 448 if charset:
439 449 response.charset = charset
440 450 return response
441 451
442 452 # in case we ever end up here
443 453 raise HTTPNotFound()
444 454
445 455 @LoginRequired()
446 456 @HasRepoPermissionAnyDecorator(
447 457 'repository.read', 'repository.write', 'repository.admin')
448 458 @view_config(
449 459 route_name='repo_files_diff_2way_redirect', request_method='GET',
450 460 renderer=None)
451 461 def repo_files_diff_2way_redirect(self):
452 462 """
453 463 Kept only to make OLD links work
454 464 """
455 465 f_path = self._get_f_path_unchecked(self.request.matchdict)
456 466 diff1 = self.request.GET.get('diff1', '')
457 467 diff2 = self.request.GET.get('diff2', '')
458 468
459 469 if not any((diff1, diff2)):
460 470 h.flash(
461 471 'Need query parameter "diff1" or "diff2" to generate a diff.',
462 472 category='error')
463 473 raise HTTPBadRequest()
464 474
465 475 compare_url = h.route_path(
466 476 'repo_compare',
467 477 repo_name=self.db_repo_name,
468 478 source_ref_type='rev',
469 479 source_ref=diff1,
470 480 target_ref_type='rev',
471 481 target_ref=diff2,
472 482 _query=dict(f_path=f_path, diffmode='sideside',
473 483 target_repo=self.db_repo_name,))
474 484 raise HTTPFound(compare_url)
475 485
476 486 @LoginRequired()
477 487 @HasRepoPermissionAnyDecorator(
478 488 'repository.read', 'repository.write', 'repository.admin')
479 489 @view_config(
480 490 route_name='repo_files', request_method='GET',
481 491 renderer=None)
482 492 @view_config(
483 493 route_name='repo_files:default_path', request_method='GET',
484 494 renderer=None)
485 495 @view_config(
486 496 route_name='repo_files:default_commit', request_method='GET',
487 497 renderer=None)
488 498 @view_config(
489 499 route_name='repo_files:rendered', request_method='GET',
490 500 renderer=None)
491 501 @view_config(
492 502 route_name='repo_files:annotated', request_method='GET',
493 503 renderer=None)
494 504 def repo_files(self):
495 505 c = self.load_default_context()
496 506
497 507 view_name = getattr(self.request.matched_route, 'name', None)
498 508
499 509 c.annotate = view_name == 'repo_files:annotated'
500 510 # default is false, but .rst/.md files later are auto rendered, we can
501 511 # overwrite auto rendering by setting this GET flag
502 512 c.renderer = view_name == 'repo_files:rendered' or \
503 513 not self.request.GET.get('no-render', False)
504 514
505 515 # redirect to given commit_id from form if given
506 516 get_commit_id = self.request.GET.get('at_rev', None)
507 517 if get_commit_id:
508 518 self._get_commit_or_redirect(get_commit_id)
509 519
510 520 commit_id, f_path = self._get_commit_and_path()
511 521 c.commit = self._get_commit_or_redirect(commit_id)
512 522 c.branch = self.request.GET.get('branch', None)
513 523 c.f_path = f_path
514 524
515 525 # prev link
516 526 try:
517 527 prev_commit = c.commit.prev(c.branch)
518 528 c.prev_commit = prev_commit
519 529 c.url_prev = h.route_path(
520 530 'repo_files', repo_name=self.db_repo_name,
521 531 commit_id=prev_commit.raw_id, f_path=f_path)
522 532 if c.branch:
523 533 c.url_prev += '?branch=%s' % c.branch
524 534 except (CommitDoesNotExistError, VCSError):
525 535 c.url_prev = '#'
526 536 c.prev_commit = EmptyCommit()
527 537
528 538 # next link
529 539 try:
530 540 next_commit = c.commit.next(c.branch)
531 541 c.next_commit = next_commit
532 542 c.url_next = h.route_path(
533 543 'repo_files', repo_name=self.db_repo_name,
534 544 commit_id=next_commit.raw_id, f_path=f_path)
535 545 if c.branch:
536 546 c.url_next += '?branch=%s' % c.branch
537 547 except (CommitDoesNotExistError, VCSError):
538 548 c.url_next = '#'
539 549 c.next_commit = EmptyCommit()
540 550
541 551 # files or dirs
542 552 try:
543 553 c.file = c.commit.get_node(f_path)
544 554 c.file_author = True
545 555 c.file_tree = ''
546 556
547 557 # load file content
548 558 if c.file.is_file():
549 559 c.lf_node = c.file.get_largefile_node()
550 560
551 561 c.file_source_page = 'true'
552 562 c.file_last_commit = c.file.last_commit
553 563 if c.file.size < c.visual.cut_off_limit_diff:
554 564 if c.annotate: # annotation has precedence over renderer
555 565 c.annotated_lines = filenode_as_annotated_lines_tokens(
556 566 c.file
557 567 )
558 568 else:
559 569 c.renderer = (
560 570 c.renderer and h.renderer_from_filename(c.file.path)
561 571 )
562 572 if not c.renderer:
563 573 c.lines = filenode_as_lines_tokens(c.file)
564 574
565 575 c.on_branch_head = self._is_valid_head(
566 576 commit_id, self.rhodecode_vcs_repo)
567 577
568 578 branch = c.commit.branch if (
569 579 c.commit.branch and '/' not in c.commit.branch) else None
570 580 c.branch_or_raw_id = branch or c.commit.raw_id
571 581 c.branch_name = c.commit.branch or h.short_id(c.commit.raw_id)
572 582
573 583 author = c.file_last_commit.author
574 584 c.authors = [[
575 585 h.email(author),
576 586 h.person(author, 'username_or_name_or_email'),
577 587 1
578 588 ]]
579 589
580 590 else: # load tree content at path
581 591 c.file_source_page = 'false'
582 592 c.authors = []
583 593 # this loads a simple tree without metadata to speed things up
584 594 # later via ajax we call repo_nodetree_full and fetch whole
585 595 c.file_tree = self._get_tree_at_commit(
586 596 c, c.commit.raw_id, f_path)
587 597
588 598 except RepositoryError as e:
589 599 h.flash(safe_str(h.escape(e)), category='error')
590 600 raise HTTPNotFound()
591 601
592 602 if self.request.environ.get('HTTP_X_PJAX'):
593 603 html = render('rhodecode:templates/files/files_pjax.mako',
594 604 self._get_template_context(c), self.request)
595 605 else:
596 606 html = render('rhodecode:templates/files/files.mako',
597 607 self._get_template_context(c), self.request)
598 608 return Response(html)
599 609
600 610 @HasRepoPermissionAnyDecorator(
601 611 'repository.read', 'repository.write', 'repository.admin')
602 612 @view_config(
603 613 route_name='repo_files:annotated_previous', request_method='GET',
604 614 renderer=None)
605 615 def repo_files_annotated_previous(self):
606 616 self.load_default_context()
607 617
608 618 commit_id, f_path = self._get_commit_and_path()
609 619 commit = self._get_commit_or_redirect(commit_id)
610 620 prev_commit_id = commit.raw_id
611 621 line_anchor = self.request.GET.get('line_anchor')
612 622 is_file = False
613 623 try:
614 624 _file = commit.get_node(f_path)
615 625 is_file = _file.is_file()
616 626 except (NodeDoesNotExistError, CommitDoesNotExistError, VCSError):
617 627 pass
618 628
619 629 if is_file:
620 630 history = commit.get_file_history(f_path)
621 631 prev_commit_id = history[1].raw_id \
622 632 if len(history) > 1 else prev_commit_id
623 633 prev_url = h.route_path(
624 634 'repo_files:annotated', repo_name=self.db_repo_name,
625 635 commit_id=prev_commit_id, f_path=f_path,
626 636 _anchor='L{}'.format(line_anchor))
627 637
628 638 raise HTTPFound(prev_url)
629 639
630 640 @LoginRequired()
631 641 @HasRepoPermissionAnyDecorator(
632 642 'repository.read', 'repository.write', 'repository.admin')
633 643 @view_config(
634 644 route_name='repo_nodetree_full', request_method='GET',
635 645 renderer=None, xhr=True)
636 646 @view_config(
637 647 route_name='repo_nodetree_full:default_path', request_method='GET',
638 648 renderer=None, xhr=True)
639 649 def repo_nodetree_full(self):
640 650 """
641 651 Returns rendered html of file tree that contains commit date,
642 652 author, commit_id for the specified combination of
643 653 repo, commit_id and file path
644 654 """
645 655 c = self.load_default_context()
646 656
647 657 commit_id, f_path = self._get_commit_and_path()
648 658 commit = self._get_commit_or_redirect(commit_id)
649 659 try:
650 660 dir_node = commit.get_node(f_path)
651 661 except RepositoryError as e:
652 662 return Response('error: {}'.format(h.escape(safe_str(e))))
653 663
654 664 if dir_node.is_file():
655 665 return Response('')
656 666
657 667 c.file = dir_node
658 668 c.commit = commit
659 669
660 670 html = self._get_tree_at_commit(
661 671 c, commit.raw_id, dir_node.path, full_load=True)
662 672
663 673 return Response(html)
664 674
665 675 def _get_attachement_disposition(self, f_path):
666 676 return 'attachment; filename=%s' % \
667 677 safe_str(f_path.split(Repository.NAME_SEP)[-1])
668 678
669 679 @LoginRequired()
670 680 @HasRepoPermissionAnyDecorator(
671 681 'repository.read', 'repository.write', 'repository.admin')
672 682 @view_config(
673 683 route_name='repo_file_raw', request_method='GET',
674 684 renderer=None)
675 685 def repo_file_raw(self):
676 686 """
677 687 Action for show as raw, some mimetypes are "rendered",
678 688 those include images, icons.
679 689 """
680 690 c = self.load_default_context()
681 691
682 692 commit_id, f_path = self._get_commit_and_path()
683 693 commit = self._get_commit_or_redirect(commit_id)
684 694 file_node = self._get_filenode_or_redirect(commit, f_path)
685 695
686 696 raw_mimetype_mapping = {
687 697 # map original mimetype to a mimetype used for "show as raw"
688 698 # you can also provide a content-disposition to override the
689 699 # default "attachment" disposition.
690 700 # orig_type: (new_type, new_dispo)
691 701
692 702 # show images inline:
693 703 # Do not re-add SVG: it is unsafe and permits XSS attacks. One can
694 704 # for example render an SVG with javascript inside or even render
695 705 # HTML.
696 706 'image/x-icon': ('image/x-icon', 'inline'),
697 707 'image/png': ('image/png', 'inline'),
698 708 'image/gif': ('image/gif', 'inline'),
699 709 'image/jpeg': ('image/jpeg', 'inline'),
700 710 'application/pdf': ('application/pdf', 'inline'),
701 711 }
702 712
703 713 mimetype = file_node.mimetype
704 714 try:
705 715 mimetype, disposition = raw_mimetype_mapping[mimetype]
706 716 except KeyError:
707 717 # we don't know anything special about this, handle it safely
708 718 if file_node.is_binary:
709 719 # do same as download raw for binary files
710 720 mimetype, disposition = 'application/octet-stream', 'attachment'
711 721 else:
712 722 # do not just use the original mimetype, but force text/plain,
713 723 # otherwise it would serve text/html and that might be unsafe.
714 724 # Note: underlying vcs library fakes text/plain mimetype if the
715 725 # mimetype can not be determined and it thinks it is not
716 726 # binary.This might lead to erroneous text display in some
717 727 # cases, but helps in other cases, like with text files
718 728 # without extension.
719 729 mimetype, disposition = 'text/plain', 'inline'
720 730
721 731 if disposition == 'attachment':
722 732 disposition = self._get_attachement_disposition(f_path)
723 733
724 734 def stream_node():
725 735 yield file_node.raw_bytes
726 736
727 737 response = Response(app_iter=stream_node())
728 738 response.content_disposition = disposition
729 739 response.content_type = mimetype
730 740
731 741 charset = self._get_default_encoding(c)
732 742 if charset:
733 743 response.charset = charset
734 744
735 745 return response
736 746
737 747 @LoginRequired()
738 748 @HasRepoPermissionAnyDecorator(
739 749 'repository.read', 'repository.write', 'repository.admin')
740 750 @view_config(
741 751 route_name='repo_file_download', request_method='GET',
742 752 renderer=None)
743 753 @view_config(
744 754 route_name='repo_file_download:legacy', request_method='GET',
745 755 renderer=None)
746 756 def repo_file_download(self):
747 757 c = self.load_default_context()
748 758
749 759 commit_id, f_path = self._get_commit_and_path()
750 760 commit = self._get_commit_or_redirect(commit_id)
751 761 file_node = self._get_filenode_or_redirect(commit, f_path)
752 762
753 763 if self.request.GET.get('lf'):
754 764 # only if lf get flag is passed, we download this file
755 765 # as LFS/Largefile
756 766 lf_node = file_node.get_largefile_node()
757 767 if lf_node:
758 768 # overwrite our pointer with the REAL large-file
759 769 file_node = lf_node
760 770
761 771 disposition = self._get_attachement_disposition(f_path)
762 772
763 773 def stream_node():
764 774 yield file_node.raw_bytes
765 775
766 776 response = Response(app_iter=stream_node())
767 777 response.content_disposition = disposition
768 778 response.content_type = file_node.mimetype
769 779
770 780 charset = self._get_default_encoding(c)
771 781 if charset:
772 782 response.charset = charset
773 783
774 784 return response
775 785
776 786 def _get_nodelist_at_commit(self, repo_name, repo_id, commit_id, f_path):
777 787
788 cache_seconds = safe_int(
789 rhodecode.CONFIG.get('rc_cache.cache_repo.expiration_time'))
790 cache_on = cache_seconds > 0
791 log.debug(
792 'Computing FILE SEARCH for repo_id %s commit_id `%s` and path `%s`'
793 'with caching: %s[TTL: %ss]' % (
794 repo_id, commit_id, f_path, cache_on, cache_seconds or 0))
795
778 796 cache_namespace_uid = 'cache_repo.{}'.format(repo_id)
779 797 region = rc_cache.get_or_create_region('cache_repo', cache_namespace_uid)
780 798
781 @region.cache_on_arguments(namespace=cache_namespace_uid)
799 @region.cache_on_arguments(namespace=cache_namespace_uid,
800 should_cache_fn=lambda v: cache_on)
782 801 def compute_file_search(repo_id, commit_id, f_path):
783 802 log.debug('Generating cached nodelist for repo_id:%s, %s, %s',
784 803 repo_id, commit_id, f_path)
785 804 try:
786 805 _d, _f = ScmModel().get_nodes(
787 806 repo_name, commit_id, f_path, flat=False)
788 807 except (RepositoryError, CommitDoesNotExistError, Exception) as e:
789 808 log.exception(safe_str(e))
790 809 h.flash(safe_str(h.escape(e)), category='error')
791 810 raise HTTPFound(h.route_path(
792 811 'repo_files', repo_name=self.db_repo_name,
793 812 commit_id='tip', f_path='/'))
794 813 return _d + _f
795 814
796 815 return compute_file_search(self.db_repo.repo_id, commit_id, f_path)
797 816
798 817 @LoginRequired()
799 818 @HasRepoPermissionAnyDecorator(
800 819 'repository.read', 'repository.write', 'repository.admin')
801 820 @view_config(
802 821 route_name='repo_files_nodelist', request_method='GET',
803 822 renderer='json_ext', xhr=True)
804 823 def repo_nodelist(self):
805 824 self.load_default_context()
806 825
807 826 commit_id, f_path = self._get_commit_and_path()
808 827 commit = self._get_commit_or_redirect(commit_id)
809 828
810 829 metadata = self._get_nodelist_at_commit(
811 830 self.db_repo_name, self.db_repo.repo_id, commit.raw_id, f_path)
812 831 return {'nodes': metadata}
813 832
814 833 def _create_references(
815 834 self, branches_or_tags, symbolic_reference, f_path):
816 835 items = []
817 836 for name, commit_id in branches_or_tags.items():
818 837 sym_ref = symbolic_reference(commit_id, name, f_path)
819 838 items.append((sym_ref, name))
820 839 return items
821 840
822 841 def _symbolic_reference(self, commit_id, name, f_path):
823 842 return commit_id
824 843
825 844 def _symbolic_reference_svn(self, commit_id, name, f_path):
826 845 new_f_path = vcspath.join(name, f_path)
827 846 return u'%s@%s' % (new_f_path, commit_id)
828 847
829 848 def _get_node_history(self, commit_obj, f_path, commits=None):
830 849 """
831 850 get commit history for given node
832 851
833 852 :param commit_obj: commit to calculate history
834 853 :param f_path: path for node to calculate history for
835 854 :param commits: if passed don't calculate history and take
836 855 commits defined in this list
837 856 """
838 857 _ = self.request.translate
839 858
840 859 # calculate history based on tip
841 860 tip = self.rhodecode_vcs_repo.get_commit()
842 861 if commits is None:
843 862 pre_load = ["author", "branch"]
844 863 try:
845 864 commits = tip.get_file_history(f_path, pre_load=pre_load)
846 865 except (NodeDoesNotExistError, CommitError):
847 866 # this node is not present at tip!
848 867 commits = commit_obj.get_file_history(f_path, pre_load=pre_load)
849 868
850 869 history = []
851 870 commits_group = ([], _("Changesets"))
852 871 for commit in commits:
853 872 branch = ' (%s)' % commit.branch if commit.branch else ''
854 873 n_desc = 'r%s:%s%s' % (commit.idx, commit.short_id, branch)
855 874 commits_group[0].append((commit.raw_id, n_desc,))
856 875 history.append(commits_group)
857 876
858 877 symbolic_reference = self._symbolic_reference
859 878
860 879 if self.rhodecode_vcs_repo.alias == 'svn':
861 880 adjusted_f_path = RepoFilesView.adjust_file_path_for_svn(
862 881 f_path, self.rhodecode_vcs_repo)
863 882 if adjusted_f_path != f_path:
864 883 log.debug(
865 884 'Recognized svn tag or branch in file "%s", using svn '
866 885 'specific symbolic references', f_path)
867 886 f_path = adjusted_f_path
868 887 symbolic_reference = self._symbolic_reference_svn
869 888
870 889 branches = self._create_references(
871 890 self.rhodecode_vcs_repo.branches, symbolic_reference, f_path)
872 891 branches_group = (branches, _("Branches"))
873 892
874 893 tags = self._create_references(
875 894 self.rhodecode_vcs_repo.tags, symbolic_reference, f_path)
876 895 tags_group = (tags, _("Tags"))
877 896
878 897 history.append(branches_group)
879 898 history.append(tags_group)
880 899
881 900 return history, commits
882 901
883 902 @LoginRequired()
884 903 @HasRepoPermissionAnyDecorator(
885 904 'repository.read', 'repository.write', 'repository.admin')
886 905 @view_config(
887 906 route_name='repo_file_history', request_method='GET',
888 907 renderer='json_ext')
889 908 def repo_file_history(self):
890 909 self.load_default_context()
891 910
892 911 commit_id, f_path = self._get_commit_and_path()
893 912 commit = self._get_commit_or_redirect(commit_id)
894 913 file_node = self._get_filenode_or_redirect(commit, f_path)
895 914
896 915 if file_node.is_file():
897 916 file_history, _hist = self._get_node_history(commit, f_path)
898 917
899 918 res = []
900 919 for obj in file_history:
901 920 res.append({
902 921 'text': obj[1],
903 922 'children': [{'id': o[0], 'text': o[1]} for o in obj[0]]
904 923 })
905 924
906 925 data = {
907 926 'more': False,
908 927 'results': res
909 928 }
910 929 return data
911 930
912 931 log.warning('Cannot fetch history for directory')
913 932 raise HTTPBadRequest()
914 933
915 934 @LoginRequired()
916 935 @HasRepoPermissionAnyDecorator(
917 936 'repository.read', 'repository.write', 'repository.admin')
918 937 @view_config(
919 938 route_name='repo_file_authors', request_method='GET',
920 939 renderer='rhodecode:templates/files/file_authors_box.mako')
921 940 def repo_file_authors(self):
922 941 c = self.load_default_context()
923 942
924 943 commit_id, f_path = self._get_commit_and_path()
925 944 commit = self._get_commit_or_redirect(commit_id)
926 945 file_node = self._get_filenode_or_redirect(commit, f_path)
927 946
928 947 if not file_node.is_file():
929 948 raise HTTPBadRequest()
930 949
931 950 c.file_last_commit = file_node.last_commit
932 951 if self.request.GET.get('annotate') == '1':
933 952 # use _hist from annotation if annotation mode is on
934 953 commit_ids = set(x[1] for x in file_node.annotate)
935 954 _hist = (
936 955 self.rhodecode_vcs_repo.get_commit(commit_id)
937 956 for commit_id in commit_ids)
938 957 else:
939 958 _f_history, _hist = self._get_node_history(commit, f_path)
940 959 c.file_author = False
941 960
942 961 unique = collections.OrderedDict()
943 962 for commit in _hist:
944 963 author = commit.author
945 964 if author not in unique:
946 965 unique[commit.author] = [
947 966 h.email(author),
948 967 h.person(author, 'username_or_name_or_email'),
949 968 1 # counter
950 969 ]
951 970
952 971 else:
953 972 # increase counter
954 973 unique[commit.author][2] += 1
955 974
956 975 c.authors = [val for val in unique.values()]
957 976
958 977 return self._get_template_context(c)
959 978
960 979 @LoginRequired()
961 980 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
962 981 @view_config(
963 982 route_name='repo_files_remove_file', request_method='GET',
964 983 renderer='rhodecode:templates/files/files_delete.mako')
965 984 def repo_files_remove_file(self):
966 985 _ = self.request.translate
967 986 c = self.load_default_context()
968 987 commit_id, f_path = self._get_commit_and_path()
969 988
970 989 self._ensure_not_locked()
971 990
972 991 if not self._is_valid_head(commit_id, self.rhodecode_vcs_repo):
973 992 h.flash(_('You can only delete files with commit '
974 993 'being a valid branch '), category='warning')
975 994 raise HTTPFound(
976 995 h.route_path('repo_files',
977 996 repo_name=self.db_repo_name, commit_id='tip',
978 997 f_path=f_path))
979 998
980 999 c.commit = self._get_commit_or_redirect(commit_id)
981 1000 c.file = self._get_filenode_or_redirect(c.commit, f_path)
982 1001
983 1002 c.default_message = _(
984 1003 'Deleted file {} via RhodeCode Enterprise').format(f_path)
985 1004 c.f_path = f_path
986 1005
987 1006 return self._get_template_context(c)
988 1007
989 1008 @LoginRequired()
990 1009 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
991 1010 @CSRFRequired()
992 1011 @view_config(
993 1012 route_name='repo_files_delete_file', request_method='POST',
994 1013 renderer=None)
995 1014 def repo_files_delete_file(self):
996 1015 _ = self.request.translate
997 1016
998 1017 c = self.load_default_context()
999 1018 commit_id, f_path = self._get_commit_and_path()
1000 1019
1001 1020 self._ensure_not_locked()
1002 1021
1003 1022 if not self._is_valid_head(commit_id, self.rhodecode_vcs_repo):
1004 1023 h.flash(_('You can only delete files with commit '
1005 1024 'being a valid branch '), category='warning')
1006 1025 raise HTTPFound(
1007 1026 h.route_path('repo_files',
1008 1027 repo_name=self.db_repo_name, commit_id='tip',
1009 1028 f_path=f_path))
1010 1029
1011 1030 c.commit = self._get_commit_or_redirect(commit_id)
1012 1031 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1013 1032
1014 1033 c.default_message = _(
1015 1034 'Deleted file {} via RhodeCode Enterprise').format(f_path)
1016 1035 c.f_path = f_path
1017 1036 node_path = f_path
1018 1037 author = self._rhodecode_db_user.full_contact
1019 1038 message = self.request.POST.get('message') or c.default_message
1020 1039 try:
1021 1040 nodes = {
1022 1041 node_path: {
1023 1042 'content': ''
1024 1043 }
1025 1044 }
1026 1045 ScmModel().delete_nodes(
1027 1046 user=self._rhodecode_db_user.user_id, repo=self.db_repo,
1028 1047 message=message,
1029 1048 nodes=nodes,
1030 1049 parent_commit=c.commit,
1031 1050 author=author,
1032 1051 )
1033 1052
1034 1053 h.flash(
1035 1054 _('Successfully deleted file `{}`').format(
1036 1055 h.escape(f_path)), category='success')
1037 1056 except Exception:
1038 1057 log.exception('Error during commit operation')
1039 1058 h.flash(_('Error occurred during commit'), category='error')
1040 1059 raise HTTPFound(
1041 1060 h.route_path('repo_commit', repo_name=self.db_repo_name,
1042 1061 commit_id='tip'))
1043 1062
1044 1063 @LoginRequired()
1045 1064 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1046 1065 @view_config(
1047 1066 route_name='repo_files_edit_file', request_method='GET',
1048 1067 renderer='rhodecode:templates/files/files_edit.mako')
1049 1068 def repo_files_edit_file(self):
1050 1069 _ = self.request.translate
1051 1070 c = self.load_default_context()
1052 1071 commit_id, f_path = self._get_commit_and_path()
1053 1072
1054 1073 self._ensure_not_locked()
1055 1074
1056 1075 if not self._is_valid_head(commit_id, self.rhodecode_vcs_repo):
1057 1076 h.flash(_('You can only edit files with commit '
1058 1077 'being a valid branch '), category='warning')
1059 1078 raise HTTPFound(
1060 1079 h.route_path('repo_files',
1061 1080 repo_name=self.db_repo_name, commit_id='tip',
1062 1081 f_path=f_path))
1063 1082
1064 1083 c.commit = self._get_commit_or_redirect(commit_id)
1065 1084 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1066 1085
1067 1086 if c.file.is_binary:
1068 1087 files_url = h.route_path(
1069 1088 'repo_files',
1070 1089 repo_name=self.db_repo_name,
1071 1090 commit_id=c.commit.raw_id, f_path=f_path)
1072 1091 raise HTTPFound(files_url)
1073 1092
1074 1093 c.default_message = _(
1075 1094 'Edited file {} via RhodeCode Enterprise').format(f_path)
1076 1095 c.f_path = f_path
1077 1096
1078 1097 return self._get_template_context(c)
1079 1098
1080 1099 @LoginRequired()
1081 1100 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1082 1101 @CSRFRequired()
1083 1102 @view_config(
1084 1103 route_name='repo_files_update_file', request_method='POST',
1085 1104 renderer=None)
1086 1105 def repo_files_update_file(self):
1087 1106 _ = self.request.translate
1088 1107 c = self.load_default_context()
1089 1108 commit_id, f_path = self._get_commit_and_path()
1090 1109
1091 1110 self._ensure_not_locked()
1092 1111
1093 1112 if not self._is_valid_head(commit_id, self.rhodecode_vcs_repo):
1094 1113 h.flash(_('You can only edit files with commit '
1095 1114 'being a valid branch '), category='warning')
1096 1115 raise HTTPFound(
1097 1116 h.route_path('repo_files',
1098 1117 repo_name=self.db_repo_name, commit_id='tip',
1099 1118 f_path=f_path))
1100 1119
1101 1120 c.commit = self._get_commit_or_redirect(commit_id)
1102 1121 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1103 1122
1104 1123 if c.file.is_binary:
1105 1124 raise HTTPFound(
1106 1125 h.route_path('repo_files',
1107 1126 repo_name=self.db_repo_name,
1108 1127 commit_id=c.commit.raw_id,
1109 1128 f_path=f_path))
1110 1129
1111 1130 c.default_message = _(
1112 1131 'Edited file {} via RhodeCode Enterprise').format(f_path)
1113 1132 c.f_path = f_path
1114 1133 old_content = c.file.content
1115 1134 sl = old_content.splitlines(1)
1116 1135 first_line = sl[0] if sl else ''
1117 1136
1118 1137 r_post = self.request.POST
1119 1138 # modes: 0 - Unix, 1 - Mac, 2 - DOS
1120 1139 mode = detect_mode(first_line, 0)
1121 1140 content = convert_line_endings(r_post.get('content', ''), mode)
1122 1141
1123 1142 message = r_post.get('message') or c.default_message
1124 1143 org_f_path = c.file.unicode_path
1125 1144 filename = r_post['filename']
1126 1145 org_filename = c.file.name
1127 1146
1128 1147 if content == old_content and filename == org_filename:
1129 1148 h.flash(_('No changes'), category='warning')
1130 1149 raise HTTPFound(
1131 1150 h.route_path('repo_commit', repo_name=self.db_repo_name,
1132 1151 commit_id='tip'))
1133 1152 try:
1134 1153 mapping = {
1135 1154 org_f_path: {
1136 1155 'org_filename': org_f_path,
1137 1156 'filename': os.path.join(c.file.dir_path, filename),
1138 1157 'content': content,
1139 1158 'lexer': '',
1140 1159 'op': 'mod',
1141 1160 }
1142 1161 }
1143 1162
1144 1163 ScmModel().update_nodes(
1145 1164 user=self._rhodecode_db_user.user_id,
1146 1165 repo=self.db_repo,
1147 1166 message=message,
1148 1167 nodes=mapping,
1149 1168 parent_commit=c.commit,
1150 1169 )
1151 1170
1152 1171 h.flash(
1153 1172 _('Successfully committed changes to file `{}`').format(
1154 1173 h.escape(f_path)), category='success')
1155 1174 except Exception:
1156 1175 log.exception('Error occurred during commit')
1157 1176 h.flash(_('Error occurred during commit'), category='error')
1158 1177 raise HTTPFound(
1159 1178 h.route_path('repo_commit', repo_name=self.db_repo_name,
1160 1179 commit_id='tip'))
1161 1180
1162 1181 @LoginRequired()
1163 1182 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1164 1183 @view_config(
1165 1184 route_name='repo_files_add_file', request_method='GET',
1166 1185 renderer='rhodecode:templates/files/files_add.mako')
1167 1186 def repo_files_add_file(self):
1168 1187 _ = self.request.translate
1169 1188 c = self.load_default_context()
1170 1189 commit_id, f_path = self._get_commit_and_path()
1171 1190
1172 1191 self._ensure_not_locked()
1173 1192
1174 1193 c.commit = self._get_commit_or_redirect(commit_id, redirect_after=False)
1175 1194 if c.commit is None:
1176 1195 c.commit = EmptyCommit(alias=self.rhodecode_vcs_repo.alias)
1177 1196 c.default_message = (_('Added file via RhodeCode Enterprise'))
1178 1197 c.f_path = f_path.lstrip('/') # ensure not relative path
1179 1198
1180 1199 return self._get_template_context(c)
1181 1200
1182 1201 @LoginRequired()
1183 1202 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1184 1203 @CSRFRequired()
1185 1204 @view_config(
1186 1205 route_name='repo_files_create_file', request_method='POST',
1187 1206 renderer=None)
1188 1207 def repo_files_create_file(self):
1189 1208 _ = self.request.translate
1190 1209 c = self.load_default_context()
1191 1210 commit_id, f_path = self._get_commit_and_path()
1192 1211
1193 1212 self._ensure_not_locked()
1194 1213
1195 1214 r_post = self.request.POST
1196 1215
1197 1216 c.commit = self._get_commit_or_redirect(
1198 1217 commit_id, redirect_after=False)
1199 1218 if c.commit is None:
1200 1219 c.commit = EmptyCommit(alias=self.rhodecode_vcs_repo.alias)
1201 1220 c.default_message = (_('Added file via RhodeCode Enterprise'))
1202 1221 c.f_path = f_path
1203 1222 unix_mode = 0
1204 1223 content = convert_line_endings(r_post.get('content', ''), unix_mode)
1205 1224
1206 1225 message = r_post.get('message') or c.default_message
1207 1226 filename = r_post.get('filename')
1208 1227 location = r_post.get('location', '') # dir location
1209 1228 file_obj = r_post.get('upload_file', None)
1210 1229
1211 1230 if file_obj is not None and hasattr(file_obj, 'filename'):
1212 1231 filename = r_post.get('filename_upload')
1213 1232 content = file_obj.file
1214 1233
1215 1234 if hasattr(content, 'file'):
1216 1235 # non posix systems store real file under file attr
1217 1236 content = content.file
1218 1237
1219 1238 if self.rhodecode_vcs_repo.is_empty:
1220 1239 default_redirect_url = h.route_path(
1221 1240 'repo_summary', repo_name=self.db_repo_name)
1222 1241 else:
1223 1242 default_redirect_url = h.route_path(
1224 1243 'repo_commit', repo_name=self.db_repo_name, commit_id='tip')
1225 1244
1226 1245 # If there's no commit, redirect to repo summary
1227 1246 if type(c.commit) is EmptyCommit:
1228 1247 redirect_url = h.route_path(
1229 1248 'repo_summary', repo_name=self.db_repo_name)
1230 1249 else:
1231 1250 redirect_url = default_redirect_url
1232 1251
1233 1252 if not filename:
1234 1253 h.flash(_('No filename'), category='warning')
1235 1254 raise HTTPFound(redirect_url)
1236 1255
1237 1256 # extract the location from filename,
1238 1257 # allows using foo/bar.txt syntax to create subdirectories
1239 1258 subdir_loc = filename.rsplit('/', 1)
1240 1259 if len(subdir_loc) == 2:
1241 1260 location = os.path.join(location, subdir_loc[0])
1242 1261
1243 1262 # strip all crap out of file, just leave the basename
1244 1263 filename = os.path.basename(filename)
1245 1264 node_path = os.path.join(location, filename)
1246 1265 author = self._rhodecode_db_user.full_contact
1247 1266
1248 1267 try:
1249 1268 nodes = {
1250 1269 node_path: {
1251 1270 'content': content
1252 1271 }
1253 1272 }
1254 1273 ScmModel().create_nodes(
1255 1274 user=self._rhodecode_db_user.user_id,
1256 1275 repo=self.db_repo,
1257 1276 message=message,
1258 1277 nodes=nodes,
1259 1278 parent_commit=c.commit,
1260 1279 author=author,
1261 1280 )
1262 1281
1263 1282 h.flash(
1264 1283 _('Successfully committed new file `{}`').format(
1265 1284 h.escape(node_path)), category='success')
1266 1285 except NonRelativePathError:
1267 1286 log.exception('Non Relative path found')
1268 1287 h.flash(_(
1269 1288 'The location specified must be a relative path and must not '
1270 1289 'contain .. in the path'), category='warning')
1271 1290 raise HTTPFound(default_redirect_url)
1272 1291 except (NodeError, NodeAlreadyExistsError) as e:
1273 1292 h.flash(_(h.escape(e)), category='error')
1274 1293 except Exception:
1275 1294 log.exception('Error occurred during commit')
1276 1295 h.flash(_('Error occurred during commit'), category='error')
1277 1296
1278 1297 raise HTTPFound(default_redirect_url)
@@ -1,369 +1,379 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2011-2018 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 import logging
22 22 import string
23 import rhodecode
23 24
24 25 from pyramid.view import view_config
25 26 from beaker.cache import cache_region
26 27
27 28 from rhodecode.controllers import utils
28 29 from rhodecode.apps._base import RepoAppView
29 30 from rhodecode.config.conf import (LANGUAGES_EXTENSIONS_MAP)
30 31 from rhodecode.lib import helpers as h, rc_cache
31 32 from rhodecode.lib.utils2 import safe_str, safe_int
32 33 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
33 34 from rhodecode.lib.markup_renderer import MarkupRenderer, relative_links
34 35 from rhodecode.lib.ext_json import json
35 36 from rhodecode.lib.vcs.backends.base import EmptyCommit
36 37 from rhodecode.lib.vcs.exceptions import (
37 38 CommitError, EmptyRepositoryError, CommitDoesNotExistError)
38 39 from rhodecode.model.db import Statistics, CacheKey, User
39 40 from rhodecode.model.meta import Session
40 41 from rhodecode.model.repo import ReadmeFinder
41 42 from rhodecode.model.scm import ScmModel
42 43
43 44 log = logging.getLogger(__name__)
44 45
45 46
46 47 class RepoSummaryView(RepoAppView):
47 48
48 49 def load_default_context(self):
49 50 c = self._get_local_tmpl_context(include_app_defaults=True)
50 51 c.rhodecode_repo = None
51 52 if not c.repository_requirements_missing:
52 53 c.rhodecode_repo = self.rhodecode_vcs_repo
53 54 return c
54 55
55 56 def _get_readme_data(self, db_repo, default_renderer):
56 57 repo_name = db_repo.repo_name
57 58 log.debug('Looking for README file')
58 59
59 60 @cache_region('long_term')
60 61 def _generate_readme(cache_key):
61 62 readme_data = None
62 63 readme_node = None
63 64 readme_filename = None
64 65 commit = self._get_landing_commit_or_none(db_repo)
65 66 if commit:
66 67 log.debug("Searching for a README file.")
67 68 readme_node = ReadmeFinder(default_renderer).search(commit)
68 69 if readme_node:
69 70 relative_urls = {
70 71 'raw': h.route_path(
71 72 'repo_file_raw', repo_name=repo_name,
72 73 commit_id=commit.raw_id, f_path=readme_node.path),
73 74 'standard': h.route_path(
74 75 'repo_files', repo_name=repo_name,
75 76 commit_id=commit.raw_id, f_path=readme_node.path),
76 77 }
77 78 readme_data = self._render_readme_or_none(
78 79 commit, readme_node, relative_urls)
79 80 readme_filename = readme_node.path
80 81 return readme_data, readme_filename
81 82
82 83 invalidator_context = CacheKey.repo_context_cache(
83 84 _generate_readme, repo_name, CacheKey.CACHE_TYPE_README)
84 85
85 86 with invalidator_context as context:
86 87 context.invalidate()
87 88 computed = context.compute()
88 89
89 90 return computed
90 91
91 92 def _get_landing_commit_or_none(self, db_repo):
92 93 log.debug("Getting the landing commit.")
93 94 try:
94 95 commit = db_repo.get_landing_commit()
95 96 if not isinstance(commit, EmptyCommit):
96 97 return commit
97 98 else:
98 99 log.debug("Repository is empty, no README to render.")
99 100 except CommitError:
100 101 log.exception(
101 102 "Problem getting commit when trying to render the README.")
102 103
103 104 def _render_readme_or_none(self, commit, readme_node, relative_urls):
104 105 log.debug(
105 106 'Found README file `%s` rendering...', readme_node.path)
106 107 renderer = MarkupRenderer()
107 108 try:
108 109 html_source = renderer.render(
109 110 readme_node.content, filename=readme_node.path)
110 111 if relative_urls:
111 112 return relative_links(html_source, relative_urls)
112 113 return html_source
113 114 except Exception:
114 115 log.exception(
115 116 "Exception while trying to render the README")
116 117
117 118 def _load_commits_context(self, c):
118 119 p = safe_int(self.request.GET.get('page'), 1)
119 120 size = safe_int(self.request.GET.get('size'), 10)
120 121
121 122 def url_generator(**kw):
122 123 query_params = {
123 124 'size': size
124 125 }
125 126 query_params.update(kw)
126 127 return h.route_path(
127 128 'repo_summary_commits',
128 129 repo_name=c.rhodecode_db_repo.repo_name, _query=query_params)
129 130
130 131 pre_load = ['author', 'branch', 'date', 'message']
131 132 try:
132 133 collection = self.rhodecode_vcs_repo.get_commits(pre_load=pre_load)
133 134 except EmptyRepositoryError:
134 135 collection = self.rhodecode_vcs_repo
135 136
136 137 c.repo_commits = h.RepoPage(
137 138 collection, page=p, items_per_page=size, url=url_generator)
138 139 page_ids = [x.raw_id for x in c.repo_commits]
139 140 c.comments = self.db_repo.get_comments(page_ids)
140 141 c.statuses = self.db_repo.statuses(page_ids)
141 142
142 143 @LoginRequired()
143 144 @HasRepoPermissionAnyDecorator(
144 145 'repository.read', 'repository.write', 'repository.admin')
145 146 @view_config(
146 147 route_name='repo_summary_commits', request_method='GET',
147 148 renderer='rhodecode:templates/summary/summary_commits.mako')
148 149 def summary_commits(self):
149 150 c = self.load_default_context()
150 151 self._load_commits_context(c)
151 152 return self._get_template_context(c)
152 153
153 154 @LoginRequired()
154 155 @HasRepoPermissionAnyDecorator(
155 156 'repository.read', 'repository.write', 'repository.admin')
156 157 @view_config(
157 158 route_name='repo_summary', request_method='GET',
158 159 renderer='rhodecode:templates/summary/summary.mako')
159 160 @view_config(
160 161 route_name='repo_summary_slash', request_method='GET',
161 162 renderer='rhodecode:templates/summary/summary.mako')
162 163 @view_config(
163 164 route_name='repo_summary_explicit', request_method='GET',
164 165 renderer='rhodecode:templates/summary/summary.mako')
165 166 def summary(self):
166 167 c = self.load_default_context()
167 168
168 169 # Prepare the clone URL
169 170 username = ''
170 171 if self._rhodecode_user.username != User.DEFAULT_USER:
171 172 username = safe_str(self._rhodecode_user.username)
172 173
173 174 _def_clone_uri = _def_clone_uri_id = c.clone_uri_tmpl
174 175 _def_clone_uri_ssh = c.clone_uri_ssh_tmpl
175 176
176 177 if '{repo}' in _def_clone_uri:
177 178 _def_clone_uri_id = _def_clone_uri.replace(
178 179 '{repo}', '_{repoid}')
179 180 elif '{repoid}' in _def_clone_uri:
180 181 _def_clone_uri_id = _def_clone_uri.replace(
181 182 '_{repoid}', '{repo}')
182 183
183 184 c.clone_repo_url = self.db_repo.clone_url(
184 185 user=username, uri_tmpl=_def_clone_uri)
185 186 c.clone_repo_url_id = self.db_repo.clone_url(
186 187 user=username, uri_tmpl=_def_clone_uri_id)
187 188 c.clone_repo_url_ssh = self.db_repo.clone_url(
188 189 uri_tmpl=_def_clone_uri_ssh, ssh=True)
189 190
190 191 # If enabled, get statistics data
191 192
192 193 c.show_stats = bool(self.db_repo.enable_statistics)
193 194
194 195 stats = Session().query(Statistics) \
195 196 .filter(Statistics.repository == self.db_repo) \
196 197 .scalar()
197 198
198 199 c.stats_percentage = 0
199 200
200 201 if stats and stats.languages:
201 202 c.no_data = False is self.db_repo.enable_statistics
202 203 lang_stats_d = json.loads(stats.languages)
203 204
204 205 # Sort first by decreasing count and second by the file extension,
205 206 # so we have a consistent output.
206 207 lang_stats_items = sorted(lang_stats_d.iteritems(),
207 208 key=lambda k: (-k[1], k[0]))[:10]
208 209 lang_stats = [(x, {"count": y,
209 210 "desc": LANGUAGES_EXTENSIONS_MAP.get(x)})
210 211 for x, y in lang_stats_items]
211 212
212 213 c.trending_languages = json.dumps(lang_stats)
213 214 else:
214 215 c.no_data = True
215 216 c.trending_languages = json.dumps({})
216 217
217 218 scm_model = ScmModel()
218 219 c.enable_downloads = self.db_repo.enable_downloads
219 220 c.repository_followers = scm_model.get_followers(self.db_repo)
220 221 c.repository_forks = scm_model.get_forks(self.db_repo)
221 222 c.repository_is_user_following = scm_model.is_following_repo(
222 223 self.db_repo_name, self._rhodecode_user.user_id)
223 224
224 225 # first interaction with the VCS instance after here...
225 226 if c.repository_requirements_missing:
226 227 self.request.override_renderer = \
227 228 'rhodecode:templates/summary/missing_requirements.mako'
228 229 return self._get_template_context(c)
229 230
230 231 c.readme_data, c.readme_file = \
231 232 self._get_readme_data(self.db_repo, c.visual.default_renderer)
232 233
233 234 # loads the summary commits template context
234 235 self._load_commits_context(c)
235 236
236 237 return self._get_template_context(c)
237 238
238 239 def get_request_commit_id(self):
239 240 return self.request.matchdict['commit_id']
240 241
241 242 @LoginRequired()
242 243 @HasRepoPermissionAnyDecorator(
243 244 'repository.read', 'repository.write', 'repository.admin')
244 245 @view_config(
245 246 route_name='repo_stats', request_method='GET',
246 247 renderer='json_ext')
247 248 def repo_stats(self):
248 249 commit_id = self.get_request_commit_id()
249 250 show_stats = bool(self.db_repo.enable_statistics)
250 251 repo_id = self.db_repo.repo_id
251 252
253 cache_seconds = safe_int(
254 rhodecode.CONFIG.get('rc_cache.cache_repo.expiration_time'))
255 cache_on = cache_seconds > 0
256 log.debug(
257 'Computing REPO TREE for repo_id %s commit_id `%s` '
258 'with caching: %s[TTL: %ss]' % (
259 repo_id, commit_id, cache_on, cache_seconds or 0))
260
252 261 cache_namespace_uid = 'cache_repo.{}'.format(repo_id)
253 262 region = rc_cache.get_or_create_region('cache_repo', cache_namespace_uid)
254 263
255 @region.cache_on_arguments(namespace=cache_namespace_uid)
264 @region.cache_on_arguments(namespace=cache_namespace_uid,
265 should_cache_fn=lambda v: cache_on)
256 266 def compute_stats(repo_id, commit_id, show_stats):
257 267 code_stats = {}
258 268 size = 0
259 269 try:
260 270 scm_instance = self.db_repo.scm_instance()
261 271 commit = scm_instance.get_commit(commit_id)
262 272
263 273 for node in commit.get_filenodes_generator():
264 274 size += node.size
265 275 if not show_stats:
266 276 continue
267 277 ext = string.lower(node.extension)
268 278 ext_info = LANGUAGES_EXTENSIONS_MAP.get(ext)
269 279 if ext_info:
270 280 if ext in code_stats:
271 281 code_stats[ext]['count'] += 1
272 282 else:
273 283 code_stats[ext] = {"count": 1, "desc": ext_info}
274 284 except (EmptyRepositoryError, CommitDoesNotExistError):
275 285 pass
276 286 return {'size': h.format_byte_size_binary(size),
277 287 'code_stats': code_stats}
278 288
279 289 stats = compute_stats(self.db_repo.repo_id, commit_id, show_stats)
280 290 return stats
281 291
282 292 @LoginRequired()
283 293 @HasRepoPermissionAnyDecorator(
284 294 'repository.read', 'repository.write', 'repository.admin')
285 295 @view_config(
286 296 route_name='repo_refs_data', request_method='GET',
287 297 renderer='json_ext')
288 298 def repo_refs_data(self):
289 299 _ = self.request.translate
290 300 self.load_default_context()
291 301
292 302 repo = self.rhodecode_vcs_repo
293 303 refs_to_create = [
294 304 (_("Branch"), repo.branches, 'branch'),
295 305 (_("Tag"), repo.tags, 'tag'),
296 306 (_("Bookmark"), repo.bookmarks, 'book'),
297 307 ]
298 308 res = self._create_reference_data(
299 309 repo, self.db_repo_name, refs_to_create)
300 310 data = {
301 311 'more': False,
302 312 'results': res
303 313 }
304 314 return data
305 315
306 316 @LoginRequired()
307 317 @HasRepoPermissionAnyDecorator(
308 318 'repository.read', 'repository.write', 'repository.admin')
309 319 @view_config(
310 320 route_name='repo_refs_changelog_data', request_method='GET',
311 321 renderer='json_ext')
312 322 def repo_refs_changelog_data(self):
313 323 _ = self.request.translate
314 324 self.load_default_context()
315 325
316 326 repo = self.rhodecode_vcs_repo
317 327
318 328 refs_to_create = [
319 329 (_("Branches"), repo.branches, 'branch'),
320 330 (_("Closed branches"), repo.branches_closed, 'branch_closed'),
321 331 # TODO: enable when vcs can handle bookmarks filters
322 332 # (_("Bookmarks"), repo.bookmarks, "book"),
323 333 ]
324 334 res = self._create_reference_data(
325 335 repo, self.db_repo_name, refs_to_create)
326 336 data = {
327 337 'more': False,
328 338 'results': res
329 339 }
330 340 return data
331 341
332 342 def _create_reference_data(self, repo, full_repo_name, refs_to_create):
333 343 format_ref_id = utils.get_format_ref_id(repo)
334 344
335 345 result = []
336 346 for title, refs, ref_type in refs_to_create:
337 347 if refs:
338 348 result.append({
339 349 'text': title,
340 350 'children': self._create_reference_items(
341 351 repo, full_repo_name, refs, ref_type,
342 352 format_ref_id),
343 353 })
344 354 return result
345 355
346 356 def _create_reference_items(self, repo, full_repo_name, refs, ref_type,
347 357 format_ref_id):
348 358 result = []
349 359 is_svn = h.is_svn(repo)
350 360 for ref_name, raw_id in refs.iteritems():
351 361 files_url = self._create_files_url(
352 362 repo, full_repo_name, ref_name, raw_id, is_svn)
353 363 result.append({
354 364 'text': ref_name,
355 365 'id': format_ref_id(ref_name, raw_id),
356 366 'raw_id': raw_id,
357 367 'type': ref_type,
358 368 'files_url': files_url,
359 369 })
360 370 return result
361 371
362 372 def _create_files_url(self, repo, full_repo_name, ref_name, raw_id, is_svn):
363 373 use_commit_id = '/' in ref_name or is_svn
364 374 return h.route_path(
365 375 'repo_files',
366 376 repo_name=full_repo_name,
367 377 f_path=ref_name if is_svn else '',
368 378 commit_id=raw_id if use_commit_id else ref_name,
369 379 _query=dict(at=ref_name))
General Comments 0
You need to be logged in to leave comments. Login now