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