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