##// END OF EJS Templates
files: drop usage of pathlib2, it's now in core python
super-admin -
r5025:9f64def3 default
parent child Browse files
Show More
@@ -1,1581 +1,1583 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.request, urllib.parse, urllib.error
28 import pathlib2
28 import pathlib
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 convert_line_endings, detect_mode, safe_str, str2bool, safe_int, sha1, safe_unicode)
46 convert_line_endings, detect_mode, safe_str, str2bool, safe_int, sha1)
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(h.escape(safe_str(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(h.escape(safe_str(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 x for x in pathlib2.PurePath(*parts).parts
307 x for x in pathlib.PurePath(*parts).parts
308 308 if x not in ['.', '..']]
309 309
310 pure_path = pathlib2.PurePath(*sanitized_path)
310 pure_path = pathlib.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 376 # we used a ref, or a shorter version, lets redirect client ot use explicit hash
377 377 if commit_id != commit.raw_id:
378 378 fname='{}{}'.format(commit.raw_id, ext)
379 379 raise HTTPFound(self.request.current_route_path(fname=fname))
380 380
381 381 try:
382 382 at_path = commit.get_node(at_path).path or default_at_path
383 383 except Exception:
384 384 return Response(_('No node at path {} for this repository').format(h.escape(at_path)))
385 385
386 386 # path sha is part of subdir
387 387 path_sha = ''
388 388 if at_path != default_at_path:
389 389 path_sha = sha1(at_path)[:8]
390 390 short_sha = '-{}'.format(safe_str(commit.short_id))
391 391 # used for cache etc
392 392 archive_name = self._get_archive_name(
393 393 self.db_repo_name, commit_sha=short_sha, ext=ext, subrepos=subrepos,
394 394 path_sha=path_sha, with_hash=with_hash)
395 395
396 396 if not with_hash:
397 397 short_sha = ''
398 398 path_sha = ''
399 399
400 400 # what end client gets served
401 401 response_archive_name = self._get_archive_name(
402 402 self.db_repo_name, commit_sha=short_sha, ext=ext, subrepos=subrepos,
403 403 path_sha=path_sha, with_hash=with_hash)
404 404 # remove extension from our archive directory name
405 405 archive_dir_name = response_archive_name[:-len(ext)]
406 406
407 407 use_cached_archive = False
408 408 archive_cache_dir = CONFIG.get('archive_cache_dir')
409 409 archive_cache_enabled = archive_cache_dir and not self.request.GET.get('no_cache')
410 410 cached_archive_path = None
411 411
412 412 if archive_cache_enabled:
413 413 # check if we it's ok to write, and re-create the archive cache
414 414 if not os.path.isdir(CONFIG['archive_cache_dir']):
415 415 os.makedirs(CONFIG['archive_cache_dir'])
416 416
417 417 cached_archive_path = os.path.join(
418 418 CONFIG['archive_cache_dir'], archive_name)
419 419 if os.path.isfile(cached_archive_path):
420 420 log.debug('Found cached archive in %s', cached_archive_path)
421 421 fd, archive = None, cached_archive_path
422 422 use_cached_archive = True
423 423 else:
424 424 log.debug('Archive %s is not yet cached', archive_name)
425 425
426 426 # generate new archive, as previous was not found in the cache
427 427 if not use_cached_archive:
428 428 _dir = os.path.abspath(archive_cache_dir) if archive_cache_dir else None
429 429 fd, archive = tempfile.mkstemp(dir=_dir)
430 430 log.debug('Creating new temp archive in %s', archive)
431 431 try:
432 432 commit.archive_repo(archive, archive_dir_name=archive_dir_name,
433 433 kind=fileformat, subrepos=subrepos,
434 434 archive_at_path=at_path)
435 435 except ImproperArchiveTypeError:
436 436 return _('Unknown archive type')
437 437 if archive_cache_enabled:
438 438 # if we generated the archive and we have cache enabled
439 439 # let's use this for future
440 440 log.debug('Storing new archive in %s', cached_archive_path)
441 441 shutil.move(archive, cached_archive_path)
442 442 archive = cached_archive_path
443 443
444 444 # store download action
445 445 audit_logger.store_web(
446 446 'repo.archive.download', action_data={
447 447 'user_agent': self.request.user_agent,
448 448 'archive_name': archive_name,
449 449 'archive_spec': fname,
450 450 'archive_cached': use_cached_archive},
451 451 user=self._rhodecode_user,
452 452 repo=self.db_repo,
453 453 commit=True
454 454 )
455 455
456 456 def get_chunked_archive(archive_path):
457 457 with open(archive_path, 'rb') as stream:
458 458 while True:
459 459 data = stream.read(16 * 1024)
460 460 if not data:
461 461 if fd: # fd means we used temporary file
462 462 os.close(fd)
463 463 if not archive_cache_enabled:
464 464 log.debug('Destroying temp archive %s', archive_path)
465 465 os.remove(archive_path)
466 466 break
467 467 yield data
468 468
469 469 response = Response(app_iter=get_chunked_archive(archive))
470 470 response.content_disposition = str('attachment; filename=%s' % response_archive_name)
471 471 response.content_type = str(content_type)
472 472
473 473 return response
474 474
475 475 def _get_file_node(self, commit_id, f_path):
476 476 if commit_id not in ['', None, 'None', '0' * 12, '0' * 40]:
477 477 commit = self.rhodecode_vcs_repo.get_commit(commit_id=commit_id)
478 478 try:
479 479 node = commit.get_node(f_path)
480 480 if node.is_dir():
481 481 raise NodeError('%s path is a %s not a file'
482 482 % (node, type(node)))
483 483 except NodeDoesNotExistError:
484 484 commit = EmptyCommit(
485 485 commit_id=commit_id,
486 486 idx=commit.idx,
487 487 repo=commit.repository,
488 488 alias=commit.repository.alias,
489 489 message=commit.message,
490 490 author=commit.author,
491 491 date=commit.date)
492 492 node = FileNode(f_path, '', commit=commit)
493 493 else:
494 494 commit = EmptyCommit(
495 495 repo=self.rhodecode_vcs_repo,
496 496 alias=self.rhodecode_vcs_repo.alias)
497 497 node = FileNode(f_path, '', commit=commit)
498 498 return node
499 499
500 500 @LoginRequired()
501 501 @HasRepoPermissionAnyDecorator(
502 502 'repository.read', 'repository.write', 'repository.admin')
503 503 def repo_files_diff(self):
504 504 c = self.load_default_context()
505 505 f_path = self._get_f_path(self.request.matchdict)
506 506 diff1 = self.request.GET.get('diff1', '')
507 507 diff2 = self.request.GET.get('diff2', '')
508 508
509 509 path1, diff1 = parse_path_ref(diff1, default_path=f_path)
510 510
511 511 ignore_whitespace = str2bool(self.request.GET.get('ignorews'))
512 512 line_context = self.request.GET.get('context', 3)
513 513
514 514 if not any((diff1, diff2)):
515 515 h.flash(
516 516 'Need query parameter "diff1" or "diff2" to generate a diff.',
517 517 category='error')
518 518 raise HTTPBadRequest()
519 519
520 520 c.action = self.request.GET.get('diff')
521 521 if c.action not in ['download', 'raw']:
522 522 compare_url = h.route_path(
523 523 'repo_compare',
524 524 repo_name=self.db_repo_name,
525 525 source_ref_type='rev',
526 526 source_ref=diff1,
527 527 target_repo=self.db_repo_name,
528 528 target_ref_type='rev',
529 529 target_ref=diff2,
530 530 _query=dict(f_path=f_path))
531 531 # redirect to new view if we render diff
532 532 raise HTTPFound(compare_url)
533 533
534 534 try:
535 535 node1 = self._get_file_node(diff1, path1)
536 536 node2 = self._get_file_node(diff2, f_path)
537 537 except (RepositoryError, NodeError):
538 538 log.exception("Exception while trying to get node from repository")
539 539 raise HTTPFound(
540 540 h.route_path('repo_files', repo_name=self.db_repo_name,
541 541 commit_id='tip', f_path=f_path))
542 542
543 543 if all(isinstance(node.commit, EmptyCommit)
544 544 for node in (node1, node2)):
545 545 raise HTTPNotFound()
546 546
547 547 c.commit_1 = node1.commit
548 548 c.commit_2 = node2.commit
549 549
550 550 if c.action == 'download':
551 551 _diff = diffs.get_gitdiff(node1, node2,
552 552 ignore_whitespace=ignore_whitespace,
553 553 context=line_context)
554 554 diff = diffs.DiffProcessor(_diff, format='gitdiff')
555 555
556 556 response = Response(self.path_filter.get_raw_patch(diff))
557 557 response.content_type = 'text/plain'
558 558 response.content_disposition = (
559 559 'attachment; filename=%s_%s_vs_%s.diff' % (f_path, diff1, diff2)
560 560 )
561 561 charset = self._get_default_encoding(c)
562 562 if charset:
563 563 response.charset = charset
564 564 return response
565 565
566 566 elif c.action == 'raw':
567 567 _diff = diffs.get_gitdiff(node1, node2,
568 568 ignore_whitespace=ignore_whitespace,
569 569 context=line_context)
570 570 diff = diffs.DiffProcessor(_diff, format='gitdiff')
571 571
572 572 response = Response(self.path_filter.get_raw_patch(diff))
573 573 response.content_type = 'text/plain'
574 574 charset = self._get_default_encoding(c)
575 575 if charset:
576 576 response.charset = charset
577 577 return response
578 578
579 579 # in case we ever end up here
580 580 raise HTTPNotFound()
581 581
582 582 @LoginRequired()
583 583 @HasRepoPermissionAnyDecorator(
584 584 'repository.read', 'repository.write', 'repository.admin')
585 585 def repo_files_diff_2way_redirect(self):
586 586 """
587 587 Kept only to make OLD links work
588 588 """
589 589 f_path = self._get_f_path_unchecked(self.request.matchdict)
590 590 diff1 = self.request.GET.get('diff1', '')
591 591 diff2 = self.request.GET.get('diff2', '')
592 592
593 593 if not any((diff1, diff2)):
594 594 h.flash(
595 595 'Need query parameter "diff1" or "diff2" to generate a diff.',
596 596 category='error')
597 597 raise HTTPBadRequest()
598 598
599 599 compare_url = h.route_path(
600 600 'repo_compare',
601 601 repo_name=self.db_repo_name,
602 602 source_ref_type='rev',
603 603 source_ref=diff1,
604 604 target_ref_type='rev',
605 605 target_ref=diff2,
606 606 _query=dict(f_path=f_path, diffmode='sideside',
607 607 target_repo=self.db_repo_name,))
608 608 raise HTTPFound(compare_url)
609 609
610 610 @LoginRequired()
611 611 def repo_files_default_commit_redirect(self):
612 612 """
613 613 Special page that redirects to the landing page of files based on the default
614 614 commit for repository
615 615 """
616 616 c = self.load_default_context()
617 617 ref_name = c.rhodecode_db_repo.landing_ref_name
618 618 landing_url = h.repo_files_by_ref_url(
619 619 c.rhodecode_db_repo.repo_name,
620 620 c.rhodecode_db_repo.repo_type,
621 621 f_path='',
622 622 ref_name=ref_name,
623 623 commit_id='tip',
624 624 query=dict(at=ref_name)
625 625 )
626 626
627 627 raise HTTPFound(landing_url)
628 628
629 629 @LoginRequired()
630 630 @HasRepoPermissionAnyDecorator(
631 631 'repository.read', 'repository.write', 'repository.admin')
632 632 def repo_files(self):
633 633 c = self.load_default_context()
634 634
635 635 view_name = getattr(self.request.matched_route, 'name', None)
636 636
637 637 c.annotate = view_name == 'repo_files:annotated'
638 638 # default is false, but .rst/.md files later are auto rendered, we can
639 639 # overwrite auto rendering by setting this GET flag
640 640 c.renderer = view_name == 'repo_files:rendered' or \
641 641 not self.request.GET.get('no-render', False)
642 642
643 643 commit_id, f_path = self._get_commit_and_path()
644 644
645 645 c.commit = self._get_commit_or_redirect(commit_id)
646 646 c.branch = self.request.GET.get('branch', None)
647 647 c.f_path = f_path
648 648 at_rev = self.request.GET.get('at')
649 649
650 650 # prev link
651 651 try:
652 652 prev_commit = c.commit.prev(c.branch)
653 653 c.prev_commit = prev_commit
654 654 c.url_prev = h.route_path(
655 655 'repo_files', repo_name=self.db_repo_name,
656 656 commit_id=prev_commit.raw_id, f_path=f_path)
657 657 if c.branch:
658 658 c.url_prev += '?branch=%s' % c.branch
659 659 except (CommitDoesNotExistError, VCSError):
660 660 c.url_prev = '#'
661 661 c.prev_commit = EmptyCommit()
662 662
663 663 # next link
664 664 try:
665 665 next_commit = c.commit.next(c.branch)
666 666 c.next_commit = next_commit
667 667 c.url_next = h.route_path(
668 668 'repo_files', repo_name=self.db_repo_name,
669 669 commit_id=next_commit.raw_id, f_path=f_path)
670 670 if c.branch:
671 671 c.url_next += '?branch=%s' % c.branch
672 672 except (CommitDoesNotExistError, VCSError):
673 673 c.url_next = '#'
674 674 c.next_commit = EmptyCommit()
675 675
676 676 # files or dirs
677 677 try:
678 678 c.file = c.commit.get_node(f_path)
679
679 680 c.file_author = True
680 681 c.file_tree = ''
681 682
682 683 # load file content
683 684 if c.file.is_file():
684 685 c.lf_node = {}
685 686
686 687 has_lf_enabled = self._is_lf_enabled(self.db_repo)
687 688 if has_lf_enabled:
688 689 c.lf_node = c.file.get_largefile_node()
689 690
690 691 c.file_source_page = 'true'
691 692 c.file_last_commit = c.file.last_commit
692 693
693 694 c.file_size_too_big = c.file.size > c.visual.cut_off_limit_file
694 695
695 696 if not (c.file_size_too_big or c.file.is_binary):
696 697 if c.annotate: # annotation has precedence over renderer
697 698 c.annotated_lines = filenode_as_annotated_lines_tokens(
698 699 c.file
699 700 )
700 701 else:
701 702 c.renderer = (
702 703 c.renderer and h.renderer_from_filename(c.file.path)
703 704 )
704 705 if not c.renderer:
705 706 c.lines = filenode_as_lines_tokens(c.file)
706 707
707 708 _branch_name, _sha_commit_id, is_head = \
708 709 self._is_valid_head(commit_id, self.rhodecode_vcs_repo,
709 710 landing_ref=self.db_repo.landing_ref_name)
710 711 c.on_branch_head = is_head
711 712
712 713 branch = c.commit.branch if (
713 714 c.commit.branch and '/' not in c.commit.branch) else None
714 715 c.branch_or_raw_id = branch or c.commit.raw_id
715 716 c.branch_name = c.commit.branch or h.short_id(c.commit.raw_id)
716 717
717 718 author = c.file_last_commit.author
718 719 c.authors = [[
719 720 h.email(author),
720 721 h.person(author, 'username_or_name_or_email'),
721 722 1
722 723 ]]
723 724
724 725 else: # load tree content at path
725 726 c.file_source_page = 'false'
726 727 c.authors = []
727 728 # this loads a simple tree without metadata to speed things up
728 729 # later via ajax we call repo_nodetree_full and fetch whole
729 730 c.file_tree = self._get_tree_at_commit(c, c.commit.raw_id, f_path, at_rev=at_rev)
730 731
731 732 c.readme_data, c.readme_file = \
732 733 self._get_readme_data(self.db_repo, c.visual.default_renderer,
733 734 c.commit.raw_id, f_path)
734 735
735 736 except RepositoryError as e:
736 737 h.flash(h.escape(safe_str(e)), category='error')
737 738 raise HTTPNotFound()
738 739
739 740 if self.request.environ.get('HTTP_X_PJAX'):
740 741 html = render('rhodecode:templates/files/files_pjax.mako',
741 742 self._get_template_context(c), self.request)
742 743 else:
743 744 html = render('rhodecode:templates/files/files.mako',
744 745 self._get_template_context(c), self.request)
745 746 return Response(html)
746 747
747 748 @HasRepoPermissionAnyDecorator(
748 749 'repository.read', 'repository.write', 'repository.admin')
749 750 def repo_files_annotated_previous(self):
750 751 self.load_default_context()
751 752
752 753 commit_id, f_path = self._get_commit_and_path()
753 754 commit = self._get_commit_or_redirect(commit_id)
754 755 prev_commit_id = commit.raw_id
755 756 line_anchor = self.request.GET.get('line_anchor')
756 757 is_file = False
757 758 try:
758 759 _file = commit.get_node(f_path)
759 760 is_file = _file.is_file()
760 761 except (NodeDoesNotExistError, CommitDoesNotExistError, VCSError):
761 762 pass
762 763
763 764 if is_file:
764 765 history = commit.get_path_history(f_path)
765 766 prev_commit_id = history[1].raw_id \
766 767 if len(history) > 1 else prev_commit_id
767 768 prev_url = h.route_path(
768 769 'repo_files:annotated', repo_name=self.db_repo_name,
769 770 commit_id=prev_commit_id, f_path=f_path,
770 771 _anchor='L{}'.format(line_anchor))
771 772
772 773 raise HTTPFound(prev_url)
773 774
774 775 @LoginRequired()
775 776 @HasRepoPermissionAnyDecorator(
776 777 'repository.read', 'repository.write', 'repository.admin')
777 778 def repo_nodetree_full(self):
778 779 """
779 780 Returns rendered html of file tree that contains commit date,
780 781 author, commit_id for the specified combination of
781 782 repo, commit_id and file path
782 783 """
783 784 c = self.load_default_context()
784 785
785 786 commit_id, f_path = self._get_commit_and_path()
786 787 commit = self._get_commit_or_redirect(commit_id)
787 788 try:
788 789 dir_node = commit.get_node(f_path)
789 790 except RepositoryError as e:
790 791 return Response('error: {}'.format(h.escape(safe_str(e))))
791 792
792 793 if dir_node.is_file():
793 794 return Response('')
794 795
795 796 c.file = dir_node
796 797 c.commit = commit
797 798 at_rev = self.request.GET.get('at')
798 799
799 800 html = self._get_tree_at_commit(
800 801 c, commit.raw_id, dir_node.path, full_load=True, at_rev=at_rev)
801 802
802 803 return Response(html)
803 804
804 805 def _get_attachement_headers(self, f_path):
805 806 f_name = safe_str(f_path.split(Repository.NAME_SEP)[-1])
806 807 safe_path = f_name.replace('"', '\\"')
807 808 encoded_path = urllib.parse.quote(f_name)
808 809
809 810 return "attachment; " \
810 811 "filename=\"{}\"; " \
811 812 "filename*=UTF-8\'\'{}".format(safe_path, encoded_path)
812 813
813 814 @LoginRequired()
814 815 @HasRepoPermissionAnyDecorator(
815 816 'repository.read', 'repository.write', 'repository.admin')
816 817 def repo_file_raw(self):
817 818 """
818 819 Action for show as raw, some mimetypes are "rendered",
819 820 those include images, icons.
820 821 """
821 822 c = self.load_default_context()
822 823
823 824 commit_id, f_path = self._get_commit_and_path()
824 825 commit = self._get_commit_or_redirect(commit_id)
825 826 file_node = self._get_filenode_or_redirect(commit, f_path)
826 827
827 828 raw_mimetype_mapping = {
828 829 # map original mimetype to a mimetype used for "show as raw"
829 830 # you can also provide a content-disposition to override the
830 831 # default "attachment" disposition.
831 832 # orig_type: (new_type, new_dispo)
832 833
833 834 # show images inline:
834 835 # Do not re-add SVG: it is unsafe and permits XSS attacks. One can
835 836 # for example render an SVG with javascript inside or even render
836 837 # HTML.
837 838 'image/x-icon': ('image/x-icon', 'inline'),
838 839 'image/png': ('image/png', 'inline'),
839 840 'image/gif': ('image/gif', 'inline'),
840 841 'image/jpeg': ('image/jpeg', 'inline'),
841 842 'application/pdf': ('application/pdf', 'inline'),
842 843 }
843 844
844 845 mimetype = file_node.mimetype
845 846 try:
846 847 mimetype, disposition = raw_mimetype_mapping[mimetype]
847 848 except KeyError:
848 849 # we don't know anything special about this, handle it safely
849 850 if file_node.is_binary:
850 851 # do same as download raw for binary files
851 852 mimetype, disposition = 'application/octet-stream', 'attachment'
852 853 else:
853 854 # do not just use the original mimetype, but force text/plain,
854 855 # otherwise it would serve text/html and that might be unsafe.
855 856 # Note: underlying vcs library fakes text/plain mimetype if the
856 857 # mimetype can not be determined and it thinks it is not
857 858 # binary.This might lead to erroneous text display in some
858 859 # cases, but helps in other cases, like with text files
859 860 # without extension.
860 861 mimetype, disposition = 'text/plain', 'inline'
861 862
862 863 if disposition == 'attachment':
863 864 disposition = self._get_attachement_headers(f_path)
864 865
865 866 stream_content = file_node.stream_bytes()
866 867
867 868 response = Response(app_iter=stream_content)
868 869 response.content_disposition = disposition
869 870 response.content_type = mimetype
870 871
871 872 charset = self._get_default_encoding(c)
872 873 if charset:
873 874 response.charset = charset
874 875
875 876 return response
876 877
877 878 @LoginRequired()
878 879 @HasRepoPermissionAnyDecorator(
879 880 'repository.read', 'repository.write', 'repository.admin')
880 881 def repo_file_download(self):
881 882 c = self.load_default_context()
882 883
883 884 commit_id, f_path = self._get_commit_and_path()
884 885 commit = self._get_commit_or_redirect(commit_id)
885 886 file_node = self._get_filenode_or_redirect(commit, f_path)
886 887
887 888 if self.request.GET.get('lf'):
888 889 # only if lf get flag is passed, we download this file
889 890 # as LFS/Largefile
890 891 lf_node = file_node.get_largefile_node()
891 892 if lf_node:
892 893 # overwrite our pointer with the REAL large-file
893 894 file_node = lf_node
894 895
895 896 disposition = self._get_attachement_headers(f_path)
896 897
897 898 stream_content = file_node.stream_bytes()
898 899
899 900 response = Response(app_iter=stream_content)
900 901 response.content_disposition = disposition
901 902 response.content_type = file_node.mimetype
902 903
903 904 charset = self._get_default_encoding(c)
904 905 if charset:
905 906 response.charset = charset
906 907
907 908 return response
908 909
909 910 def _get_nodelist_at_commit(self, repo_name, repo_id, commit_id, f_path):
910 911
911 912 cache_seconds = safe_int(
912 913 rhodecode.CONFIG.get('rc_cache.cache_repo.expiration_time'))
913 914 cache_on = cache_seconds > 0
914 915 log.debug(
915 916 'Computing FILE SEARCH for repo_id %s commit_id `%s` and path `%s`'
916 917 'with caching: %s[TTL: %ss]' % (
917 918 repo_id, commit_id, f_path, cache_on, cache_seconds or 0))
918 919
919 920 cache_namespace_uid = 'cache_repo.{}'.format(repo_id)
920 921 region = rc_cache.get_or_create_region('cache_repo', cache_namespace_uid)
921 922
922 923 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid, condition=cache_on)
923 924 def compute_file_search(_name_hash, _repo_id, _commit_id, _f_path):
924 925 log.debug('Generating cached nodelist for repo_id:%s, %s, %s',
925 926 _repo_id, commit_id, f_path)
926 927 try:
927 928 _d, _f = ScmModel().get_quick_filter_nodes(repo_name, _commit_id, _f_path)
928 929 except (RepositoryError, CommitDoesNotExistError, Exception) as e:
929 930 log.exception(safe_str(e))
930 931 h.flash(h.escape(safe_str(e)), category='error')
931 932 raise HTTPFound(h.route_path(
932 933 'repo_files', repo_name=self.db_repo_name,
933 934 commit_id='tip', f_path='/'))
934 935
935 936 return _d + _f
936 937
937 938 result = compute_file_search(self.db_repo.repo_name_hash, self.db_repo.repo_id,
938 939 commit_id, f_path)
939 940 return filter(lambda n: self.path_filter.path_access_allowed(n['name']), result)
940 941
941 942 @LoginRequired()
942 943 @HasRepoPermissionAnyDecorator(
943 944 'repository.read', 'repository.write', 'repository.admin')
944 945 def repo_nodelist(self):
945 946 self.load_default_context()
946 947
947 948 commit_id, f_path = self._get_commit_and_path()
948 949 commit = self._get_commit_or_redirect(commit_id)
949 950
950 951 metadata = self._get_nodelist_at_commit(
951 952 self.db_repo_name, self.db_repo.repo_id, commit.raw_id, f_path)
952 953 return {'nodes': metadata}
953 954
954 955 def _create_references(self, branches_or_tags, symbolic_reference, f_path, ref_type):
955 956 items = []
956 957 for name, commit_id in branches_or_tags.items():
957 958 sym_ref = symbolic_reference(commit_id, name, f_path, ref_type)
958 959 items.append((sym_ref, name, ref_type))
959 960 return items
960 961
961 962 def _symbolic_reference(self, commit_id, name, f_path, ref_type):
962 963 return commit_id
963 964
964 965 def _symbolic_reference_svn(self, commit_id, name, f_path, ref_type):
965 966 return commit_id
966 967
967 968 # NOTE(dan): old code we used in "diff" mode compare
968 969 new_f_path = vcspath.join(name, f_path)
969 970 return u'%s@%s' % (new_f_path, commit_id)
970 971
971 972 def _get_node_history(self, commit_obj, f_path, commits=None):
972 973 """
973 974 get commit history for given node
974 975
975 976 :param commit_obj: commit to calculate history
976 977 :param f_path: path for node to calculate history for
977 978 :param commits: if passed don't calculate history and take
978 979 commits defined in this list
979 980 """
980 981 _ = self.request.translate
981 982
982 983 # calculate history based on tip
983 984 tip = self.rhodecode_vcs_repo.get_commit()
984 985 if commits is None:
985 986 pre_load = ["author", "branch"]
986 987 try:
987 988 commits = tip.get_path_history(f_path, pre_load=pre_load)
988 989 except (NodeDoesNotExistError, CommitError):
989 990 # this node is not present at tip!
990 991 commits = commit_obj.get_path_history(f_path, pre_load=pre_load)
991 992
992 993 history = []
993 994 commits_group = ([], _("Changesets"))
994 995 for commit in commits:
995 996 branch = ' (%s)' % commit.branch if commit.branch else ''
996 997 n_desc = 'r%s:%s%s' % (commit.idx, commit.short_id, branch)
997 998 commits_group[0].append((commit.raw_id, n_desc, 'sha'))
998 999 history.append(commits_group)
999 1000
1000 1001 symbolic_reference = self._symbolic_reference
1001 1002
1002 1003 if self.rhodecode_vcs_repo.alias == 'svn':
1003 1004 adjusted_f_path = RepoFilesView.adjust_file_path_for_svn(
1004 1005 f_path, self.rhodecode_vcs_repo)
1005 1006 if adjusted_f_path != f_path:
1006 1007 log.debug(
1007 1008 'Recognized svn tag or branch in file "%s", using svn '
1008 1009 'specific symbolic references', f_path)
1009 1010 f_path = adjusted_f_path
1010 1011 symbolic_reference = self._symbolic_reference_svn
1011 1012
1012 1013 branches = self._create_references(
1013 1014 self.rhodecode_vcs_repo.branches, symbolic_reference, f_path, 'branch')
1014 1015 branches_group = (branches, _("Branches"))
1015 1016
1016 1017 tags = self._create_references(
1017 1018 self.rhodecode_vcs_repo.tags, symbolic_reference, f_path, 'tag')
1018 1019 tags_group = (tags, _("Tags"))
1019 1020
1020 1021 history.append(branches_group)
1021 1022 history.append(tags_group)
1022 1023
1023 1024 return history, commits
1024 1025
1025 1026 @LoginRequired()
1026 1027 @HasRepoPermissionAnyDecorator(
1027 1028 'repository.read', 'repository.write', 'repository.admin')
1028 1029 def repo_file_history(self):
1029 1030 self.load_default_context()
1030 1031
1031 1032 commit_id, f_path = self._get_commit_and_path()
1032 1033 commit = self._get_commit_or_redirect(commit_id)
1033 1034 file_node = self._get_filenode_or_redirect(commit, f_path)
1034 1035
1035 1036 if file_node.is_file():
1036 1037 file_history, _hist = self._get_node_history(commit, f_path)
1037 1038
1038 1039 res = []
1039 1040 for section_items, section in file_history:
1040 1041 items = []
1041 1042 for obj_id, obj_text, obj_type in section_items:
1042 1043 at_rev = ''
1043 1044 if obj_type in ['branch', 'bookmark', 'tag']:
1044 1045 at_rev = obj_text
1045 1046 entry = {
1046 1047 'id': obj_id,
1047 1048 'text': obj_text,
1048 1049 'type': obj_type,
1049 1050 'at_rev': at_rev
1050 1051 }
1051 1052
1052 1053 items.append(entry)
1053 1054
1054 1055 res.append({
1055 1056 'text': section,
1056 1057 'children': items
1057 1058 })
1058 1059
1059 1060 data = {
1060 1061 'more': False,
1061 1062 'results': res
1062 1063 }
1063 1064 return data
1064 1065
1065 1066 log.warning('Cannot fetch history for directory')
1066 1067 raise HTTPBadRequest()
1067 1068
1068 1069 @LoginRequired()
1069 1070 @HasRepoPermissionAnyDecorator(
1070 1071 'repository.read', 'repository.write', 'repository.admin')
1071 1072 def repo_file_authors(self):
1072 1073 c = self.load_default_context()
1073 1074
1074 1075 commit_id, f_path = self._get_commit_and_path()
1075 1076 commit = self._get_commit_or_redirect(commit_id)
1076 1077 file_node = self._get_filenode_or_redirect(commit, f_path)
1077 1078
1078 1079 if not file_node.is_file():
1079 1080 raise HTTPBadRequest()
1080 1081
1081 1082 c.file_last_commit = file_node.last_commit
1082 1083 if self.request.GET.get('annotate') == '1':
1083 1084 # use _hist from annotation if annotation mode is on
1084 1085 commit_ids = set(x[1] for x in file_node.annotate)
1085 1086 _hist = (
1086 1087 self.rhodecode_vcs_repo.get_commit(commit_id)
1087 1088 for commit_id in commit_ids)
1088 1089 else:
1089 1090 _f_history, _hist = self._get_node_history(commit, f_path)
1090 1091 c.file_author = False
1091 1092
1092 1093 unique = collections.OrderedDict()
1093 1094 for commit in _hist:
1094 1095 author = commit.author
1095 1096 if author not in unique:
1096 1097 unique[commit.author] = [
1097 1098 h.email(author),
1098 1099 h.person(author, 'username_or_name_or_email'),
1099 1100 1 # counter
1100 1101 ]
1101 1102
1102 1103 else:
1103 1104 # increase counter
1104 1105 unique[commit.author][2] += 1
1105 1106
1106 1107 c.authors = [val for val in unique.values()]
1107 1108
1108 1109 return self._get_template_context(c)
1109 1110
1110 1111 @LoginRequired()
1111 1112 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1112 1113 def repo_files_check_head(self):
1113 1114 self.load_default_context()
1114 1115
1115 1116 commit_id, f_path = self._get_commit_and_path()
1116 1117 _branch_name, _sha_commit_id, is_head = \
1117 1118 self._is_valid_head(commit_id, self.rhodecode_vcs_repo,
1118 1119 landing_ref=self.db_repo.landing_ref_name)
1119 1120
1120 1121 new_path = self.request.POST.get('path')
1121 1122 operation = self.request.POST.get('operation')
1122 1123 path_exist = ''
1123 1124
1124 1125 if new_path and operation in ['create', 'upload']:
1125 1126 new_f_path = os.path.join(f_path.lstrip('/'), new_path)
1126 1127 try:
1127 1128 commit_obj = self.rhodecode_vcs_repo.get_commit(commit_id)
1128 1129 # NOTE(dan): construct whole path without leading /
1129 1130 file_node = commit_obj.get_node(new_f_path)
1130 1131 if file_node is not None:
1131 1132 path_exist = new_f_path
1132 1133 except EmptyRepositoryError:
1133 1134 pass
1134 1135 except Exception:
1135 1136 pass
1136 1137
1137 1138 return {
1138 1139 'branch': _branch_name,
1139 1140 'sha': _sha_commit_id,
1140 1141 'is_head': is_head,
1141 1142 'path_exists': path_exist
1142 1143 }
1143 1144
1144 1145 @LoginRequired()
1145 1146 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1146 1147 def repo_files_remove_file(self):
1147 1148 _ = self.request.translate
1148 1149 c = self.load_default_context()
1149 1150 commit_id, f_path = self._get_commit_and_path()
1150 1151
1151 1152 self._ensure_not_locked()
1152 1153 _branch_name, _sha_commit_id, is_head = \
1153 1154 self._is_valid_head(commit_id, self.rhodecode_vcs_repo,
1154 1155 landing_ref=self.db_repo.landing_ref_name)
1155 1156
1156 1157 self.forbid_non_head(is_head, f_path)
1157 1158 self.check_branch_permission(_branch_name)
1158 1159
1159 1160 c.commit = self._get_commit_or_redirect(commit_id)
1160 1161 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1161 1162
1162 1163 c.default_message = _(
1163 1164 'Deleted file {} via RhodeCode Enterprise').format(f_path)
1164 1165 c.f_path = f_path
1165 1166
1166 1167 return self._get_template_context(c)
1167 1168
1168 1169 @LoginRequired()
1169 1170 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1170 1171 @CSRFRequired()
1171 1172 def repo_files_delete_file(self):
1172 1173 _ = self.request.translate
1173 1174
1174 1175 c = self.load_default_context()
1175 1176 commit_id, f_path = self._get_commit_and_path()
1176 1177
1177 1178 self._ensure_not_locked()
1178 1179 _branch_name, _sha_commit_id, is_head = \
1179 1180 self._is_valid_head(commit_id, self.rhodecode_vcs_repo,
1180 1181 landing_ref=self.db_repo.landing_ref_name)
1181 1182
1182 1183 self.forbid_non_head(is_head, f_path)
1183 1184 self.check_branch_permission(_branch_name)
1184 1185
1185 1186 c.commit = self._get_commit_or_redirect(commit_id)
1186 1187 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1187 1188
1188 1189 c.default_message = _(
1189 1190 'Deleted file {} via RhodeCode Enterprise').format(f_path)
1190 1191 c.f_path = f_path
1191 1192 node_path = f_path
1192 1193 author = self._rhodecode_db_user.full_contact
1193 1194 message = self.request.POST.get('message') or c.default_message
1194 1195 try:
1195 1196 nodes = {
1196 1197 node_path: {
1197 1198 'content': ''
1198 1199 }
1199 1200 }
1200 1201 ScmModel().delete_nodes(
1201 1202 user=self._rhodecode_db_user.user_id, repo=self.db_repo,
1202 1203 message=message,
1203 1204 nodes=nodes,
1204 1205 parent_commit=c.commit,
1205 1206 author=author,
1206 1207 )
1207 1208
1208 1209 h.flash(
1209 1210 _('Successfully deleted file `{}`').format(
1210 1211 h.escape(f_path)), category='success')
1211 1212 except Exception:
1212 1213 log.exception('Error during commit operation')
1213 1214 h.flash(_('Error occurred during commit'), category='error')
1214 1215 raise HTTPFound(
1215 1216 h.route_path('repo_commit', repo_name=self.db_repo_name,
1216 1217 commit_id='tip'))
1217 1218
1218 1219 @LoginRequired()
1219 1220 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1220 1221 def repo_files_edit_file(self):
1221 1222 _ = self.request.translate
1222 1223 c = self.load_default_context()
1223 1224 commit_id, f_path = self._get_commit_and_path()
1224 1225
1225 1226 self._ensure_not_locked()
1226 1227 _branch_name, _sha_commit_id, is_head = \
1227 1228 self._is_valid_head(commit_id, self.rhodecode_vcs_repo,
1228 1229 landing_ref=self.db_repo.landing_ref_name)
1229 1230
1230 1231 self.forbid_non_head(is_head, f_path, commit_id=commit_id)
1231 1232 self.check_branch_permission(_branch_name, commit_id=commit_id)
1232 1233
1233 1234 c.commit = self._get_commit_or_redirect(commit_id)
1234 1235 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1235 1236
1236 1237 if c.file.is_binary:
1237 1238 files_url = h.route_path(
1238 1239 'repo_files',
1239 1240 repo_name=self.db_repo_name,
1240 1241 commit_id=c.commit.raw_id, f_path=f_path)
1241 1242 raise HTTPFound(files_url)
1242 1243
1243 1244 c.default_message = _('Edited file {} via RhodeCode Enterprise').format(f_path)
1244 1245 c.f_path = f_path
1245 1246
1246 1247 return self._get_template_context(c)
1247 1248
1248 1249 @LoginRequired()
1249 1250 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1250 1251 @CSRFRequired()
1251 1252 def repo_files_update_file(self):
1252 1253 _ = self.request.translate
1253 1254 c = self.load_default_context()
1254 1255 commit_id, f_path = self._get_commit_and_path()
1255 1256
1256 1257 self._ensure_not_locked()
1257 1258
1258 1259 c.commit = self._get_commit_or_redirect(commit_id)
1259 1260 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1260 1261
1261 1262 if c.file.is_binary:
1262 1263 raise HTTPFound(h.route_path('repo_files', repo_name=self.db_repo_name,
1263 1264 commit_id=c.commit.raw_id, f_path=f_path))
1264 1265
1265 1266 _branch_name, _sha_commit_id, is_head = \
1266 1267 self._is_valid_head(commit_id, self.rhodecode_vcs_repo,
1267 1268 landing_ref=self.db_repo.landing_ref_name)
1268 1269
1269 1270 self.forbid_non_head(is_head, f_path, commit_id=commit_id)
1270 1271 self.check_branch_permission(_branch_name, commit_id=commit_id)
1271 1272
1272 1273 c.default_message = _('Edited file {} via RhodeCode Enterprise').format(f_path)
1273 1274 c.f_path = f_path
1274 1275
1275 1276 old_content = c.file.content
1276 1277 sl = old_content.splitlines(1)
1277 1278 first_line = sl[0] if sl else ''
1278 1279
1279 1280 r_post = self.request.POST
1280 1281 # line endings: 0 - Unix, 1 - Mac, 2 - DOS
1281 1282 line_ending_mode = detect_mode(first_line, 0)
1282 1283 content = convert_line_endings(r_post.get('content', ''), line_ending_mode)
1283 1284
1284 1285 message = r_post.get('message') or c.default_message
1285 1286 org_node_path = c.file.unicode_path
1286 1287 filename = r_post['filename']
1287 1288
1288 1289 root_path = c.file.dir_path
1289 1290 pure_path = self.create_pure_path(root_path, filename)
1290 node_path = safe_unicode(bytes(pure_path))
1291 node_path = pure_path.as_posix()
1291 1292
1292 1293 default_redirect_url = h.route_path('repo_commit', repo_name=self.db_repo_name,
1293 1294 commit_id=commit_id)
1294 1295 if content == old_content and node_path == org_node_path:
1295 1296 h.flash(_('No changes detected on {}').format(h.escape(org_node_path)),
1296 1297 category='warning')
1297 1298 raise HTTPFound(default_redirect_url)
1298 1299
1299 1300 try:
1300 1301 mapping = {
1301 1302 org_node_path: {
1302 1303 'org_filename': org_node_path,
1303 1304 'filename': node_path,
1304 1305 'content': content,
1305 1306 'lexer': '',
1306 1307 'op': 'mod',
1307 1308 'mode': c.file.mode
1308 1309 }
1309 1310 }
1310 1311
1311 1312 commit = ScmModel().update_nodes(
1312 1313 user=self._rhodecode_db_user.user_id,
1313 1314 repo=self.db_repo,
1314 1315 message=message,
1315 1316 nodes=mapping,
1316 1317 parent_commit=c.commit,
1317 1318 )
1318 1319
1319 1320 h.flash(_('Successfully committed changes to file `{}`').format(
1320 1321 h.escape(f_path)), category='success')
1321 1322 default_redirect_url = h.route_path(
1322 1323 'repo_commit', repo_name=self.db_repo_name, commit_id=commit.raw_id)
1323 1324
1324 1325 except Exception:
1325 1326 log.exception('Error occurred during commit')
1326 1327 h.flash(_('Error occurred during commit'), category='error')
1327 1328
1328 1329 raise HTTPFound(default_redirect_url)
1329 1330
1330 1331 @LoginRequired()
1331 1332 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1332 1333 def repo_files_add_file(self):
1333 1334 _ = self.request.translate
1334 1335 c = self.load_default_context()
1335 1336 commit_id, f_path = self._get_commit_and_path()
1336 1337
1337 1338 self._ensure_not_locked()
1338 1339
1339 1340 c.commit = self._get_commit_or_redirect(commit_id, redirect_after=False)
1340 1341 if c.commit is None:
1341 1342 c.commit = EmptyCommit(alias=self.rhodecode_vcs_repo.alias)
1342 1343
1343 1344 if self.rhodecode_vcs_repo.is_empty():
1344 1345 # for empty repository we cannot check for current branch, we rely on
1345 1346 # c.commit.branch instead
1346 1347 _branch_name, _sha_commit_id, is_head = c.commit.branch, '', True
1347 1348 else:
1348 1349 _branch_name, _sha_commit_id, is_head = \
1349 1350 self._is_valid_head(commit_id, self.rhodecode_vcs_repo,
1350 1351 landing_ref=self.db_repo.landing_ref_name)
1351 1352
1352 1353 self.forbid_non_head(is_head, f_path, commit_id=commit_id)
1353 1354 self.check_branch_permission(_branch_name, commit_id=commit_id)
1354 1355
1355 1356 c.default_message = (_('Added file via RhodeCode Enterprise'))
1356 1357 c.f_path = f_path.lstrip('/') # ensure not relative path
1357 1358
1358 1359 return self._get_template_context(c)
1359 1360
1360 1361 @LoginRequired()
1361 1362 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1362 1363 @CSRFRequired()
1363 1364 def repo_files_create_file(self):
1364 1365 _ = self.request.translate
1365 1366 c = self.load_default_context()
1366 1367 commit_id, f_path = self._get_commit_and_path()
1367 1368
1368 1369 self._ensure_not_locked()
1369 1370
1370 1371 c.commit = self._get_commit_or_redirect(commit_id, redirect_after=False)
1371 1372 if c.commit is None:
1372 1373 c.commit = EmptyCommit(alias=self.rhodecode_vcs_repo.alias)
1373 1374
1374 1375 # calculate redirect URL
1375 1376 if self.rhodecode_vcs_repo.is_empty():
1376 1377 default_redirect_url = h.route_path(
1377 1378 'repo_summary', repo_name=self.db_repo_name)
1378 1379 else:
1379 1380 default_redirect_url = h.route_path(
1380 1381 'repo_commit', repo_name=self.db_repo_name, commit_id='tip')
1381 1382
1382 1383 if self.rhodecode_vcs_repo.is_empty():
1383 1384 # for empty repository we cannot check for current branch, we rely on
1384 1385 # c.commit.branch instead
1385 1386 _branch_name, _sha_commit_id, is_head = c.commit.branch, '', True
1386 1387 else:
1387 1388 _branch_name, _sha_commit_id, is_head = \
1388 1389 self._is_valid_head(commit_id, self.rhodecode_vcs_repo,
1389 1390 landing_ref=self.db_repo.landing_ref_name)
1390 1391
1391 1392 self.forbid_non_head(is_head, f_path, commit_id=commit_id)
1392 1393 self.check_branch_permission(_branch_name, commit_id=commit_id)
1393 1394
1394 1395 c.default_message = (_('Added file via RhodeCode Enterprise'))
1395 1396 c.f_path = f_path
1396 1397
1397 1398 r_post = self.request.POST
1398 1399 message = r_post.get('message') or c.default_message
1399 1400 filename = r_post.get('filename')
1400 1401 unix_mode = 0
1402
1401 1403 content = convert_line_endings(r_post.get('content', ''), unix_mode)
1402 1404
1403 1405 if not filename:
1404 1406 # If there's no commit, redirect to repo summary
1405 1407 if type(c.commit) is EmptyCommit:
1406 1408 redirect_url = h.route_path(
1407 1409 'repo_summary', repo_name=self.db_repo_name)
1408 1410 else:
1409 1411 redirect_url = default_redirect_url
1410 1412 h.flash(_('No filename specified'), category='warning')
1411 1413 raise HTTPFound(redirect_url)
1412 1414
1413 1415 root_path = f_path
1414 1416 pure_path = self.create_pure_path(root_path, filename)
1415 node_path = safe_unicode(bytes(pure_path).lstrip('/'))
1417 node_path = pure_path.as_posix().lstrip('/')
1416 1418
1417 1419 author = self._rhodecode_db_user.full_contact
1418 1420 nodes = {
1419 1421 node_path: {
1420 1422 'content': content
1421 1423 }
1422 1424 }
1423 1425
1424 1426 try:
1425 1427
1426 1428 commit = ScmModel().create_nodes(
1427 1429 user=self._rhodecode_db_user.user_id,
1428 1430 repo=self.db_repo,
1429 1431 message=message,
1430 1432 nodes=nodes,
1431 1433 parent_commit=c.commit,
1432 1434 author=author,
1433 1435 )
1434 1436
1435 1437 h.flash(_('Successfully committed new file `{}`').format(
1436 1438 h.escape(node_path)), category='success')
1437 1439
1438 1440 default_redirect_url = h.route_path(
1439 1441 'repo_commit', repo_name=self.db_repo_name, commit_id=commit.raw_id)
1440 1442
1441 1443 except NonRelativePathError:
1442 1444 log.exception('Non Relative path found')
1443 1445 h.flash(_('The location specified must be a relative path and must not '
1444 1446 'contain .. in the path'), category='warning')
1445 1447 raise HTTPFound(default_redirect_url)
1446 1448 except (NodeError, NodeAlreadyExistsError) as e:
1447 1449 h.flash(h.escape(safe_str(e)), category='error')
1448 1450 except Exception:
1449 1451 log.exception('Error occurred during commit')
1450 1452 h.flash(_('Error occurred during commit'), category='error')
1451 1453
1452 1454 raise HTTPFound(default_redirect_url)
1453 1455
1454 1456 @LoginRequired()
1455 1457 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1456 1458 @CSRFRequired()
1457 1459 def repo_files_upload_file(self):
1458 1460 _ = self.request.translate
1459 1461 c = self.load_default_context()
1460 1462 commit_id, f_path = self._get_commit_and_path()
1461 1463
1462 1464 self._ensure_not_locked()
1463 1465
1464 1466 c.commit = self._get_commit_or_redirect(commit_id, redirect_after=False)
1465 1467 if c.commit is None:
1466 1468 c.commit = EmptyCommit(alias=self.rhodecode_vcs_repo.alias)
1467 1469
1468 1470 # calculate redirect URL
1469 1471 if self.rhodecode_vcs_repo.is_empty():
1470 1472 default_redirect_url = h.route_path(
1471 1473 'repo_summary', repo_name=self.db_repo_name)
1472 1474 else:
1473 1475 default_redirect_url = h.route_path(
1474 1476 'repo_commit', repo_name=self.db_repo_name, commit_id='tip')
1475 1477
1476 1478 if self.rhodecode_vcs_repo.is_empty():
1477 1479 # for empty repository we cannot check for current branch, we rely on
1478 1480 # c.commit.branch instead
1479 1481 _branch_name, _sha_commit_id, is_head = c.commit.branch, '', True
1480 1482 else:
1481 1483 _branch_name, _sha_commit_id, is_head = \
1482 1484 self._is_valid_head(commit_id, self.rhodecode_vcs_repo,
1483 1485 landing_ref=self.db_repo.landing_ref_name)
1484 1486
1485 1487 error = self.forbid_non_head(is_head, f_path, json_mode=True)
1486 1488 if error:
1487 1489 return {
1488 1490 'error': error,
1489 1491 'redirect_url': default_redirect_url
1490 1492 }
1491 1493 error = self.check_branch_permission(_branch_name, json_mode=True)
1492 1494 if error:
1493 1495 return {
1494 1496 'error': error,
1495 1497 'redirect_url': default_redirect_url
1496 1498 }
1497 1499
1498 1500 c.default_message = (_('Uploaded file via RhodeCode Enterprise'))
1499 1501 c.f_path = f_path
1500 1502
1501 1503 r_post = self.request.POST
1502 1504
1503 1505 message = c.default_message
1504 1506 user_message = r_post.getall('message')
1505 1507 if isinstance(user_message, list) and user_message:
1506 1508 # we take the first from duplicated results if it's not empty
1507 1509 message = user_message[0] if user_message[0] else message
1508 1510
1509 1511 nodes = {}
1510 1512
1511 1513 for file_obj in r_post.getall('files_upload') or []:
1512 1514 content = file_obj.file
1513 1515 filename = file_obj.filename
1514 1516
1515 1517 root_path = f_path
1516 1518 pure_path = self.create_pure_path(root_path, filename)
1517 node_path = safe_unicode(bytes(pure_path).lstrip('/'))
1519 node_path = pure_path.as_posix().lstrip('/')
1518 1520
1519 1521 nodes[node_path] = {
1520 1522 'content': content
1521 1523 }
1522 1524
1523 1525 if not nodes:
1524 1526 error = 'missing files'
1525 1527 return {
1526 1528 'error': error,
1527 1529 'redirect_url': default_redirect_url
1528 1530 }
1529 1531
1530 1532 author = self._rhodecode_db_user.full_contact
1531 1533
1532 1534 try:
1533 1535 commit = ScmModel().create_nodes(
1534 1536 user=self._rhodecode_db_user.user_id,
1535 1537 repo=self.db_repo,
1536 1538 message=message,
1537 1539 nodes=nodes,
1538 1540 parent_commit=c.commit,
1539 1541 author=author,
1540 1542 )
1541 1543 if len(nodes) == 1:
1542 1544 flash_message = _('Successfully committed {} new files').format(len(nodes))
1543 1545 else:
1544 1546 flash_message = _('Successfully committed 1 new file')
1545 1547
1546 1548 h.flash(flash_message, category='success')
1547 1549
1548 1550 default_redirect_url = h.route_path(
1549 1551 'repo_commit', repo_name=self.db_repo_name, commit_id=commit.raw_id)
1550 1552
1551 1553 except NonRelativePathError:
1552 1554 log.exception('Non Relative path found')
1553 1555 error = _('The location specified must be a relative path and must not '
1554 1556 'contain .. in the path')
1555 1557 h.flash(error, category='warning')
1556 1558
1557 1559 return {
1558 1560 'error': error,
1559 1561 'redirect_url': default_redirect_url
1560 1562 }
1561 1563 except (NodeError, NodeAlreadyExistsError) as e:
1562 1564 error = h.escape(e)
1563 1565 h.flash(error, category='error')
1564 1566
1565 1567 return {
1566 1568 'error': error,
1567 1569 'redirect_url': default_redirect_url
1568 1570 }
1569 1571 except Exception:
1570 1572 log.exception('Error occurred during commit')
1571 1573 error = _('Error occurred during commit')
1572 1574 h.flash(error, category='error')
1573 1575 return {
1574 1576 'error': error,
1575 1577 'redirect_url': default_redirect_url
1576 1578 }
1577 1579
1578 1580 return {
1579 1581 'error': None,
1580 1582 'redirect_url': default_redirect_url
1581 1583 }
General Comments 0
You need to be logged in to leave comments. Login now