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