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