##// END OF EJS Templates
path-permissions: Introduced a _get_f_path_unchecked method, which can be used by redirects, which don't have to create a template context
idlsoft -
r2620:807566df default
parent child Browse files
Show More
@@ -1,616 +1,622 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2016-2018 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 time
22 22 import logging
23 23 import operator
24 24
25 25 from pyramid.httpexceptions import HTTPFound, HTTPForbidden
26 26
27 27 from rhodecode.lib import helpers as h, diffs
28 28 from rhodecode.lib.utils2 import StrictAttributeDict, safe_int, datetime_to_time
29 29 from rhodecode.lib.vcs.exceptions import RepositoryRequirementError
30 30 from rhodecode.model import repo
31 31 from rhodecode.model import repo_group
32 32 from rhodecode.model import user_group
33 33 from rhodecode.model import user
34 34 from rhodecode.model.db import User
35 35 from rhodecode.model.scm import ScmModel
36 36
37 37 log = logging.getLogger(__name__)
38 38
39 39
40 40 ADMIN_PREFIX = '/_admin'
41 41 STATIC_FILE_PREFIX = '/_static'
42 42
43 43 URL_NAME_REQUIREMENTS = {
44 44 # group name can have a slash in them, but they must not end with a slash
45 45 'group_name': r'.*?[^/]',
46 46 'repo_group_name': r'.*?[^/]',
47 47 # repo names can have a slash in them, but they must not end with a slash
48 48 'repo_name': r'.*?[^/]',
49 49 # file path eats up everything at the end
50 50 'f_path': r'.*',
51 51 # reference types
52 52 'source_ref_type': '(branch|book|tag|rev|\%\(source_ref_type\)s)',
53 53 'target_ref_type': '(branch|book|tag|rev|\%\(target_ref_type\)s)',
54 54 }
55 55
56 56
57 57 def add_route_with_slash(config,name, pattern, **kw):
58 58 config.add_route(name, pattern, **kw)
59 59 if not pattern.endswith('/'):
60 60 config.add_route(name + '_slash', pattern + '/', **kw)
61 61
62 62
63 63 def add_route_requirements(route_path, requirements=URL_NAME_REQUIREMENTS):
64 64 """
65 65 Adds regex requirements to pyramid routes using a mapping dict
66 66 e.g::
67 67 add_route_requirements('{repo_name}/settings')
68 68 """
69 69 for key, regex in requirements.items():
70 70 route_path = route_path.replace('{%s}' % key, '{%s:%s}' % (key, regex))
71 71 return route_path
72 72
73 73
74 74 def get_format_ref_id(repo):
75 75 """Returns a `repo` specific reference formatter function"""
76 76 if h.is_svn(repo):
77 77 return _format_ref_id_svn
78 78 else:
79 79 return _format_ref_id
80 80
81 81
82 82 def _format_ref_id(name, raw_id):
83 83 """Default formatting of a given reference `name`"""
84 84 return name
85 85
86 86
87 87 def _format_ref_id_svn(name, raw_id):
88 88 """Special way of formatting a reference for Subversion including path"""
89 89 return '%s@%s' % (name, raw_id)
90 90
91 91
92 92 class TemplateArgs(StrictAttributeDict):
93 93 pass
94 94
95 95
96 96 class BaseAppView(object):
97 97
98 98 def __init__(self, context, request):
99 99 self.request = request
100 100 self.context = context
101 101 self.session = request.session
102 102 self._rhodecode_user = request.user # auth user
103 103 self._rhodecode_db_user = self._rhodecode_user.get_instance()
104 104 self._maybe_needs_password_change(
105 105 request.matched_route.name, self._rhodecode_db_user)
106 106
107 107 def _maybe_needs_password_change(self, view_name, user_obj):
108 108 log.debug('Checking if user %s needs password change on view %s',
109 109 user_obj, view_name)
110 110 skip_user_views = [
111 111 'logout', 'login',
112 112 'my_account_password', 'my_account_password_update'
113 113 ]
114 114
115 115 if not user_obj:
116 116 return
117 117
118 118 if user_obj.username == User.DEFAULT_USER:
119 119 return
120 120
121 121 now = time.time()
122 122 should_change = user_obj.user_data.get('force_password_change')
123 123 change_after = safe_int(should_change) or 0
124 124 if should_change and now > change_after:
125 125 log.debug('User %s requires password change', user_obj)
126 126 h.flash('You are required to change your password', 'warning',
127 127 ignore_duplicate=True)
128 128
129 129 if view_name not in skip_user_views:
130 130 raise HTTPFound(
131 131 self.request.route_path('my_account_password'))
132 132
133 133 def _log_creation_exception(self, e, repo_name):
134 134 _ = self.request.translate
135 135 reason = None
136 136 if len(e.args) == 2:
137 137 reason = e.args[1]
138 138
139 139 if reason == 'INVALID_CERTIFICATE':
140 140 log.exception(
141 141 'Exception creating a repository: invalid certificate')
142 142 msg = (_('Error creating repository %s: invalid certificate')
143 143 % repo_name)
144 144 else:
145 145 log.exception("Exception creating a repository")
146 146 msg = (_('Error creating repository %s')
147 147 % repo_name)
148 148 return msg
149 149
150 150 def _get_local_tmpl_context(self, include_app_defaults=True):
151 151 c = TemplateArgs()
152 152 c.auth_user = self.request.user
153 153 # TODO(marcink): migrate the usage of c.rhodecode_user to c.auth_user
154 154 c.rhodecode_user = self.request.user
155 155
156 156 if include_app_defaults:
157 157 from rhodecode.lib.base import attach_context_attributes
158 158 attach_context_attributes(c, self.request, self.request.user.user_id)
159 159
160 160 return c
161 161
162 162 def _get_template_context(self, tmpl_args, **kwargs):
163 163
164 164 local_tmpl_args = {
165 165 'defaults': {},
166 166 'errors': {},
167 167 'c': tmpl_args
168 168 }
169 169 local_tmpl_args.update(kwargs)
170 170 return local_tmpl_args
171 171
172 172 def load_default_context(self):
173 173 """
174 174 example:
175 175
176 176 def load_default_context(self):
177 177 c = self._get_local_tmpl_context()
178 178 c.custom_var = 'foobar'
179 179
180 180 return c
181 181 """
182 182 raise NotImplementedError('Needs implementation in view class')
183 183
184 184
185 185 class RepoAppView(BaseAppView):
186 186
187 187 def __init__(self, context, request):
188 188 super(RepoAppView, self).__init__(context, request)
189 189 self.db_repo = request.db_repo
190 190 self.db_repo_name = self.db_repo.repo_name
191 191 self.db_repo_pull_requests = ScmModel().get_pull_requests(self.db_repo)
192 192
193 193 def _handle_missing_requirements(self, error):
194 194 log.error(
195 195 'Requirements are missing for repository %s: %s',
196 196 self.db_repo_name, error.message)
197 197
198 198 def _get_local_tmpl_context(self, include_app_defaults=True):
199 199 _ = self.request.translate
200 200 c = super(RepoAppView, self)._get_local_tmpl_context(
201 201 include_app_defaults=include_app_defaults)
202 202
203 203 # register common vars for this type of view
204 204 c.rhodecode_db_repo = self.db_repo
205 205 c.repo_name = self.db_repo_name
206 206 c.repository_pull_requests = self.db_repo_pull_requests
207 207
208 208 c.repository_requirements_missing = False
209 209 try:
210 210 self.rhodecode_vcs_repo = self.db_repo.scm_instance()
211 211 self.path_filter = PathFilter(self.rhodecode_vcs_repo.get_path_permissions(c.auth_user.username))
212 212 except RepositoryRequirementError as e:
213 213 c.repository_requirements_missing = True
214 214 self._handle_missing_requirements(e)
215 215 self.rhodecode_vcs_repo = None
216 216 self.path_filter = None
217 217
218 218 c.path_filter = self.path_filter # used by atom_feed_entry.mako
219 219
220 220 if (not c.repository_requirements_missing
221 221 and self.rhodecode_vcs_repo is None):
222 222 # unable to fetch this repo as vcs instance, report back to user
223 223 h.flash(_(
224 224 "The repository `%(repo_name)s` cannot be loaded in filesystem. "
225 225 "Please check if it exist, or is not damaged.") %
226 226 {'repo_name': c.repo_name},
227 227 category='error', ignore_duplicate=True)
228 228 raise HTTPFound(h.route_path('home'))
229 229
230 230 return c
231 231
232 def _get_f_path(self, matchdict, default=None):
232 def _get_f_path_unchecked(self, matchdict, default=None):
233 """
234 Should only be used by redirects, everything else should call _get_f_path
235 """
233 236 f_path = matchdict.get('f_path')
234 237 if f_path:
235 238 # fix for multiple initial slashes that causes errors for GIT
236 return self.path_filter.assert_path_permissions(f_path.lstrip('/'))
239 return f_path.lstrip('/')
237 240
238 return self.path_filter.assert_path_permissions(default)
241 return default
242
243 def _get_f_path(self, matchdict, default=None):
244 return self.path_filter.assert_path_permissions(self._get_f_path_unchecked(matchdict, default))
239 245
240 246
241 247 class PathFilter(object):
242 248
243 249 # Expects and instance of BasePathPermissionChecker or None
244 250 def __init__(self, permission_checker):
245 251 self.permission_checker = permission_checker
246 252
247 253 def assert_path_permissions(self, path):
248 254 if path and self.permission_checker and not self.permission_checker.has_access(path):
249 255 raise HTTPForbidden()
250 256 return path
251 257
252 258 def filter_patchset(self, patchset):
253 259 if not self.permission_checker or not patchset:
254 260 return patchset, False
255 261 had_filtered = False
256 262 filtered_patchset = []
257 263 for patch in patchset:
258 264 filename = patch.get('filename', None)
259 265 if not filename or self.permission_checker.has_access(filename):
260 266 filtered_patchset.append(patch)
261 267 else:
262 268 had_filtered = True
263 269 if had_filtered:
264 270 if isinstance(patchset, diffs.LimitedDiffContainer):
265 271 filtered_patchset = diffs.LimitedDiffContainer(patchset.diff_limit, patchset.cur_diff_size, filtered_patchset)
266 272 return filtered_patchset, True
267 273 else:
268 274 return patchset, False
269 275
270 276 def render_patchset_filtered(self, diffset, patchset, source_ref=None, target_ref=None):
271 277 filtered_patchset, has_hidden_changes = self.filter_patchset(patchset)
272 278 result = diffset.render_patchset(filtered_patchset, source_ref=source_ref, target_ref=target_ref)
273 279 result.has_hidden_changes = has_hidden_changes
274 280 return result
275 281
276 282 def get_raw_patch(self, diff_processor):
277 283 if self.permission_checker is None:
278 284 return diff_processor.as_raw()
279 285 elif self.permission_checker.has_full_access:
280 286 return diff_processor.as_raw()
281 287 else:
282 288 return '# Repository has user-specific filters, raw patch generation is disabled.'
283 289
284 290 @property
285 291 def is_enabled(self):
286 292 return self.permission_checker is not None
287 293
288 294
289 295 class RepoGroupAppView(BaseAppView):
290 296 def __init__(self, context, request):
291 297 super(RepoGroupAppView, self).__init__(context, request)
292 298 self.db_repo_group = request.db_repo_group
293 299 self.db_repo_group_name = self.db_repo_group.group_name
294 300
295 301 def _revoke_perms_on_yourself(self, form_result):
296 302 _updates = filter(lambda u: self._rhodecode_user.user_id == int(u[0]),
297 303 form_result['perm_updates'])
298 304 _additions = filter(lambda u: self._rhodecode_user.user_id == int(u[0]),
299 305 form_result['perm_additions'])
300 306 _deletions = filter(lambda u: self._rhodecode_user.user_id == int(u[0]),
301 307 form_result['perm_deletions'])
302 308 admin_perm = 'group.admin'
303 309 if _updates and _updates[0][1] != admin_perm or \
304 310 _additions and _additions[0][1] != admin_perm or \
305 311 _deletions and _deletions[0][1] != admin_perm:
306 312 return True
307 313 return False
308 314
309 315
310 316 class UserGroupAppView(BaseAppView):
311 317 def __init__(self, context, request):
312 318 super(UserGroupAppView, self).__init__(context, request)
313 319 self.db_user_group = request.db_user_group
314 320 self.db_user_group_name = self.db_user_group.users_group_name
315 321
316 322
317 323 class UserAppView(BaseAppView):
318 324 def __init__(self, context, request):
319 325 super(UserAppView, self).__init__(context, request)
320 326 self.db_user = request.db_user
321 327 self.db_user_id = self.db_user.user_id
322 328
323 329 _ = self.request.translate
324 330 if not request.db_user_supports_default:
325 331 if self.db_user.username == User.DEFAULT_USER:
326 332 h.flash(_("Editing user `{}` is disabled.".format(
327 333 User.DEFAULT_USER)), category='warning')
328 334 raise HTTPFound(h.route_path('users'))
329 335
330 336
331 337 class DataGridAppView(object):
332 338 """
333 339 Common class to have re-usable grid rendering components
334 340 """
335 341
336 342 def _extract_ordering(self, request, column_map=None):
337 343 column_map = column_map or {}
338 344 column_index = safe_int(request.GET.get('order[0][column]'))
339 345 order_dir = request.GET.get(
340 346 'order[0][dir]', 'desc')
341 347 order_by = request.GET.get(
342 348 'columns[%s][data][sort]' % column_index, 'name_raw')
343 349
344 350 # translate datatable to DB columns
345 351 order_by = column_map.get(order_by) or order_by
346 352
347 353 search_q = request.GET.get('search[value]')
348 354 return search_q, order_by, order_dir
349 355
350 356 def _extract_chunk(self, request):
351 357 start = safe_int(request.GET.get('start'), 0)
352 358 length = safe_int(request.GET.get('length'), 25)
353 359 draw = safe_int(request.GET.get('draw'))
354 360 return draw, start, length
355 361
356 362 def _get_order_col(self, order_by, model):
357 363 if isinstance(order_by, basestring):
358 364 try:
359 365 return operator.attrgetter(order_by)(model)
360 366 except AttributeError:
361 367 return None
362 368 else:
363 369 return order_by
364 370
365 371
366 372 class BaseReferencesView(RepoAppView):
367 373 """
368 374 Base for reference view for branches, tags and bookmarks.
369 375 """
370 376 def load_default_context(self):
371 377 c = self._get_local_tmpl_context()
372 378
373 379
374 380 return c
375 381
376 382 def load_refs_context(self, ref_items, partials_template):
377 383 _render = self.request.get_partial_renderer(partials_template)
378 384 pre_load = ["author", "date", "message"]
379 385
380 386 is_svn = h.is_svn(self.rhodecode_vcs_repo)
381 387 is_hg = h.is_hg(self.rhodecode_vcs_repo)
382 388
383 389 format_ref_id = get_format_ref_id(self.rhodecode_vcs_repo)
384 390
385 391 closed_refs = {}
386 392 if is_hg:
387 393 closed_refs = self.rhodecode_vcs_repo.branches_closed
388 394
389 395 data = []
390 396 for ref_name, commit_id in ref_items:
391 397 commit = self.rhodecode_vcs_repo.get_commit(
392 398 commit_id=commit_id, pre_load=pre_load)
393 399 closed = ref_name in closed_refs
394 400
395 401 # TODO: johbo: Unify generation of reference links
396 402 use_commit_id = '/' in ref_name or is_svn
397 403
398 404 if use_commit_id:
399 405 files_url = h.route_path(
400 406 'repo_files',
401 407 repo_name=self.db_repo_name,
402 408 f_path=ref_name if is_svn else '',
403 409 commit_id=commit_id)
404 410
405 411 else:
406 412 files_url = h.route_path(
407 413 'repo_files',
408 414 repo_name=self.db_repo_name,
409 415 f_path=ref_name if is_svn else '',
410 416 commit_id=ref_name,
411 417 _query=dict(at=ref_name))
412 418
413 419 data.append({
414 420 "name": _render('name', ref_name, files_url, closed),
415 421 "name_raw": ref_name,
416 422 "date": _render('date', commit.date),
417 423 "date_raw": datetime_to_time(commit.date),
418 424 "author": _render('author', commit.author),
419 425 "commit": _render(
420 426 'commit', commit.message, commit.raw_id, commit.idx),
421 427 "commit_raw": commit.idx,
422 428 "compare": _render(
423 429 'compare', format_ref_id(ref_name, commit.raw_id)),
424 430 })
425 431
426 432 return data
427 433
428 434
429 435 class RepoRoutePredicate(object):
430 436 def __init__(self, val, config):
431 437 self.val = val
432 438
433 439 def text(self):
434 440 return 'repo_route = %s' % self.val
435 441
436 442 phash = text
437 443
438 444 def __call__(self, info, request):
439 445
440 446 if hasattr(request, 'vcs_call'):
441 447 # skip vcs calls
442 448 return
443 449
444 450 repo_name = info['match']['repo_name']
445 451 repo_model = repo.RepoModel()
446 452 by_name_match = repo_model.get_by_repo_name(repo_name, cache=True)
447 453
448 454 def redirect_if_creating(db_repo):
449 455 if db_repo.repo_state in [repo.Repository.STATE_PENDING]:
450 456 raise HTTPFound(
451 457 request.route_path('repo_creating',
452 458 repo_name=db_repo.repo_name))
453 459
454 460 if by_name_match:
455 461 # register this as request object we can re-use later
456 462 request.db_repo = by_name_match
457 463 redirect_if_creating(by_name_match)
458 464 return True
459 465
460 466 by_id_match = repo_model.get_repo_by_id(repo_name)
461 467 if by_id_match:
462 468 request.db_repo = by_id_match
463 469 redirect_if_creating(by_id_match)
464 470 return True
465 471
466 472 return False
467 473
468 474
469 475 class RepoTypeRoutePredicate(object):
470 476 def __init__(self, val, config):
471 477 self.val = val or ['hg', 'git', 'svn']
472 478
473 479 def text(self):
474 480 return 'repo_accepted_type = %s' % self.val
475 481
476 482 phash = text
477 483
478 484 def __call__(self, info, request):
479 485 if hasattr(request, 'vcs_call'):
480 486 # skip vcs calls
481 487 return
482 488
483 489 rhodecode_db_repo = request.db_repo
484 490
485 491 log.debug(
486 492 '%s checking repo type for %s in %s',
487 493 self.__class__.__name__, rhodecode_db_repo.repo_type, self.val)
488 494
489 495 if rhodecode_db_repo.repo_type in self.val:
490 496 return True
491 497 else:
492 498 log.warning('Current view is not supported for repo type:%s',
493 499 rhodecode_db_repo.repo_type)
494 500 #
495 501 # h.flash(h.literal(
496 502 # _('Action not supported for %s.' % rhodecode_repo.alias)),
497 503 # category='warning')
498 504 # return redirect(
499 505 # route_path('repo_summary', repo_name=cls.rhodecode_db_repo.repo_name))
500 506
501 507 return False
502 508
503 509
504 510 class RepoGroupRoutePredicate(object):
505 511 def __init__(self, val, config):
506 512 self.val = val
507 513
508 514 def text(self):
509 515 return 'repo_group_route = %s' % self.val
510 516
511 517 phash = text
512 518
513 519 def __call__(self, info, request):
514 520 if hasattr(request, 'vcs_call'):
515 521 # skip vcs calls
516 522 return
517 523
518 524 repo_group_name = info['match']['repo_group_name']
519 525 repo_group_model = repo_group.RepoGroupModel()
520 526 by_name_match = repo_group_model.get_by_group_name(
521 527 repo_group_name, cache=True)
522 528
523 529 if by_name_match:
524 530 # register this as request object we can re-use later
525 531 request.db_repo_group = by_name_match
526 532 return True
527 533
528 534 return False
529 535
530 536
531 537 class UserGroupRoutePredicate(object):
532 538 def __init__(self, val, config):
533 539 self.val = val
534 540
535 541 def text(self):
536 542 return 'user_group_route = %s' % self.val
537 543
538 544 phash = text
539 545
540 546 def __call__(self, info, request):
541 547 if hasattr(request, 'vcs_call'):
542 548 # skip vcs calls
543 549 return
544 550
545 551 user_group_id = info['match']['user_group_id']
546 552 user_group_model = user_group.UserGroup()
547 553 by_id_match = user_group_model.get(
548 554 user_group_id, cache=True)
549 555
550 556 if by_id_match:
551 557 # register this as request object we can re-use later
552 558 request.db_user_group = by_id_match
553 559 return True
554 560
555 561 return False
556 562
557 563
558 564 class UserRoutePredicateBase(object):
559 565 supports_default = None
560 566
561 567 def __init__(self, val, config):
562 568 self.val = val
563 569
564 570 def text(self):
565 571 raise NotImplementedError()
566 572
567 573 def __call__(self, info, request):
568 574 if hasattr(request, 'vcs_call'):
569 575 # skip vcs calls
570 576 return
571 577
572 578 user_id = info['match']['user_id']
573 579 user_model = user.User()
574 580 by_id_match = user_model.get(
575 581 user_id, cache=True)
576 582
577 583 if by_id_match:
578 584 # register this as request object we can re-use later
579 585 request.db_user = by_id_match
580 586 request.db_user_supports_default = self.supports_default
581 587 return True
582 588
583 589 return False
584 590
585 591
586 592 class UserRoutePredicate(UserRoutePredicateBase):
587 593 supports_default = False
588 594
589 595 def text(self):
590 596 return 'user_route = %s' % self.val
591 597
592 598 phash = text
593 599
594 600
595 601 class UserRouteWithDefaultPredicate(UserRoutePredicateBase):
596 602 supports_default = True
597 603
598 604 def text(self):
599 605 return 'user_with_default_route = %s' % self.val
600 606
601 607 phash = text
602 608
603 609
604 610 def includeme(config):
605 611 config.add_route_predicate(
606 612 'repo_route', RepoRoutePredicate)
607 613 config.add_route_predicate(
608 614 'repo_accepted_types', RepoTypeRoutePredicate)
609 615 config.add_route_predicate(
610 616 'repo_group_route', RepoGroupRoutePredicate)
611 617 config.add_route_predicate(
612 618 'user_group_route', UserGroupRoutePredicate)
613 619 config.add_route_predicate(
614 620 'user_route_with_default', UserRouteWithDefaultPredicate)
615 621 config.add_route_predicate(
616 622 'user_route', UserRoutePredicate) No newline at end of file
@@ -1,1292 +1,1292 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2011-2018 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
28 28 from pyramid.httpexceptions import HTTPNotFound, HTTPBadRequest, HTTPFound
29 29 from pyramid.view import view_config
30 30 from pyramid.renderers import render
31 31 from pyramid.response import Response
32 32
33 33 from rhodecode.apps._base import RepoAppView
34 34
35 35 from rhodecode.controllers.utils import parse_path_ref
36 36 from rhodecode.lib import diffs, helpers as h, caches
37 37 from rhodecode.lib import audit_logger
38 38 from rhodecode.lib.exceptions import NonRelativePathError
39 39 from rhodecode.lib.codeblocks import (
40 40 filenode_as_lines_tokens, filenode_as_annotated_lines_tokens)
41 41 from rhodecode.lib.utils2 import (
42 42 convert_line_endings, detect_mode, safe_str, str2bool)
43 43 from rhodecode.lib.auth import (
44 44 LoginRequired, HasRepoPermissionAnyDecorator, CSRFRequired)
45 45 from rhodecode.lib.vcs import path as vcspath
46 46 from rhodecode.lib.vcs.backends.base import EmptyCommit
47 47 from rhodecode.lib.vcs.conf import settings
48 48 from rhodecode.lib.vcs.nodes import FileNode
49 49 from rhodecode.lib.vcs.exceptions import (
50 50 RepositoryError, CommitDoesNotExistError, EmptyRepositoryError,
51 51 ImproperArchiveTypeError, VCSError, NodeAlreadyExistsError,
52 52 NodeDoesNotExistError, CommitError, NodeError)
53 53
54 54 from rhodecode.model.scm import ScmModel
55 55 from rhodecode.model.db import Repository
56 56
57 57 log = logging.getLogger(__name__)
58 58
59 59
60 60 class RepoFilesView(RepoAppView):
61 61
62 62 @staticmethod
63 63 def adjust_file_path_for_svn(f_path, repo):
64 64 """
65 65 Computes the relative path of `f_path`.
66 66
67 67 This is mainly based on prefix matching of the recognized tags and
68 68 branches in the underlying repository.
69 69 """
70 70 tags_and_branches = itertools.chain(
71 71 repo.branches.iterkeys(),
72 72 repo.tags.iterkeys())
73 73 tags_and_branches = sorted(tags_and_branches, key=len, reverse=True)
74 74
75 75 for name in tags_and_branches:
76 76 if f_path.startswith('{}/'.format(name)):
77 77 f_path = vcspath.relpath(f_path, name)
78 78 break
79 79 return f_path
80 80
81 81 def load_default_context(self):
82 82 c = self._get_local_tmpl_context(include_app_defaults=True)
83 83
84 84 c.rhodecode_repo = self.rhodecode_vcs_repo
85 85
86 86
87 87 return c
88 88
89 89 def _ensure_not_locked(self):
90 90 _ = self.request.translate
91 91
92 92 repo = self.db_repo
93 93 if repo.enable_locking and repo.locked[0]:
94 94 h.flash(_('This repository has been locked by %s on %s')
95 95 % (h.person_by_id(repo.locked[0]),
96 96 h.format_date(h.time_to_datetime(repo.locked[1]))),
97 97 'warning')
98 98 files_url = h.route_path(
99 99 'repo_files:default_path',
100 100 repo_name=self.db_repo_name, commit_id='tip')
101 101 raise HTTPFound(files_url)
102 102
103 103 def _get_commit_and_path(self):
104 104 default_commit_id = self.db_repo.landing_rev[1]
105 105 default_f_path = '/'
106 106
107 107 commit_id = self.request.matchdict.get(
108 108 'commit_id', default_commit_id)
109 109 f_path = self._get_f_path(self.request.matchdict, default_f_path)
110 110 return commit_id, f_path
111 111
112 112 def _get_default_encoding(self, c):
113 113 enc_list = getattr(c, 'default_encodings', [])
114 114 return enc_list[0] if enc_list else 'UTF-8'
115 115
116 116 def _get_commit_or_redirect(self, commit_id, redirect_after=True):
117 117 """
118 118 This is a safe way to get commit. If an error occurs it redirects to
119 119 tip with proper message
120 120
121 121 :param commit_id: id of commit to fetch
122 122 :param redirect_after: toggle redirection
123 123 """
124 124 _ = self.request.translate
125 125
126 126 try:
127 127 return self.rhodecode_vcs_repo.get_commit(commit_id)
128 128 except EmptyRepositoryError:
129 129 if not redirect_after:
130 130 return None
131 131
132 132 _url = h.route_path(
133 133 'repo_files_add_file',
134 134 repo_name=self.db_repo_name, commit_id=0, f_path='',
135 135 _anchor='edit')
136 136
137 137 if h.HasRepoPermissionAny(
138 138 'repository.write', 'repository.admin')(self.db_repo_name):
139 139 add_new = h.link_to(
140 140 _('Click here to add a new file.'), _url, class_="alert-link")
141 141 else:
142 142 add_new = ""
143 143
144 144 h.flash(h.literal(
145 145 _('There are no files yet. %s') % add_new), category='warning')
146 146 raise HTTPFound(
147 147 h.route_path('repo_summary', repo_name=self.db_repo_name))
148 148
149 149 except (CommitDoesNotExistError, LookupError):
150 150 msg = _('No such commit exists for this repository')
151 151 h.flash(msg, category='error')
152 152 raise HTTPNotFound()
153 153 except RepositoryError as e:
154 154 h.flash(safe_str(h.escape(e)), category='error')
155 155 raise HTTPNotFound()
156 156
157 157 def _get_filenode_or_redirect(self, commit_obj, path):
158 158 """
159 159 Returns file_node, if error occurs or given path is directory,
160 160 it'll redirect to top level path
161 161 """
162 162 _ = self.request.translate
163 163
164 164 try:
165 165 file_node = commit_obj.get_node(path)
166 166 if file_node.is_dir():
167 167 raise RepositoryError('The given path is a directory')
168 168 except CommitDoesNotExistError:
169 169 log.exception('No such commit exists for this repository')
170 170 h.flash(_('No such commit exists for this repository'), category='error')
171 171 raise HTTPNotFound()
172 172 except RepositoryError as e:
173 173 log.warning('Repository error while fetching '
174 174 'filenode `%s`. Err:%s', path, e)
175 175 h.flash(safe_str(h.escape(e)), category='error')
176 176 raise HTTPNotFound()
177 177
178 178 return file_node
179 179
180 180 def _is_valid_head(self, commit_id, repo):
181 181 # check if commit is a branch identifier- basically we cannot
182 182 # create multiple heads via file editing
183 183 valid_heads = repo.branches.keys() + repo.branches.values()
184 184
185 185 if h.is_svn(repo) and not repo.is_empty():
186 186 # Note: Subversion only has one head, we add it here in case there
187 187 # is no branch matched.
188 188 valid_heads.append(repo.get_commit(commit_idx=-1).raw_id)
189 189
190 190 # check if commit is a branch name or branch hash
191 191 return commit_id in valid_heads
192 192
193 193 def _get_tree_cache_manager(self, namespace_type):
194 194 _namespace = caches.get_repo_namespace_key(
195 195 namespace_type, self.db_repo_name)
196 196 return caches.get_cache_manager('repo_cache_long', _namespace)
197 197
198 198 def _get_tree_at_commit(
199 199 self, c, commit_id, f_path, full_load=False, force=False):
200 200 def _cached_tree():
201 201 log.debug('Generating cached file tree for %s, %s, %s',
202 202 self.db_repo_name, commit_id, f_path)
203 203
204 204 c.full_load = full_load
205 205 return render(
206 206 'rhodecode:templates/files/files_browser_tree.mako',
207 207 self._get_template_context(c), self.request)
208 208
209 209 cache_manager = self._get_tree_cache_manager(caches.FILE_TREE)
210 210
211 211 cache_key = caches.compute_key_from_params(
212 212 self.db_repo_name, commit_id, f_path)
213 213
214 214 if force:
215 215 # we want to force recompute of caches
216 216 cache_manager.remove_value(cache_key)
217 217
218 218 return cache_manager.get(cache_key, createfunc=_cached_tree)
219 219
220 220 def _get_archive_spec(self, fname):
221 221 log.debug('Detecting archive spec for: `%s`', fname)
222 222
223 223 fileformat = None
224 224 ext = None
225 225 content_type = None
226 226 for a_type, ext_data in settings.ARCHIVE_SPECS.items():
227 227 content_type, extension = ext_data
228 228
229 229 if fname.endswith(extension):
230 230 fileformat = a_type
231 231 log.debug('archive is of type: %s', fileformat)
232 232 ext = extension
233 233 break
234 234
235 235 if not fileformat:
236 236 raise ValueError()
237 237
238 238 # left over part of whole fname is the commit
239 239 commit_id = fname[:-len(ext)]
240 240
241 241 return commit_id, ext, fileformat, content_type
242 242
243 243 @LoginRequired()
244 244 @HasRepoPermissionAnyDecorator(
245 245 'repository.read', 'repository.write', 'repository.admin')
246 246 @view_config(
247 247 route_name='repo_archivefile', request_method='GET',
248 248 renderer=None)
249 249 def repo_archivefile(self):
250 250 # archive cache config
251 251 from rhodecode import CONFIG
252 252 _ = self.request.translate
253 253 self.load_default_context()
254 254
255 255 fname = self.request.matchdict['fname']
256 256 subrepos = self.request.GET.get('subrepos') == 'true'
257 257
258 258 if not self.db_repo.enable_downloads:
259 259 return Response(_('Downloads disabled'))
260 260
261 261 try:
262 262 commit_id, ext, fileformat, content_type = \
263 263 self._get_archive_spec(fname)
264 264 except ValueError:
265 265 return Response(_('Unknown archive type for: `{}`').format(
266 266 h.escape(fname)))
267 267
268 268 try:
269 269 commit = self.rhodecode_vcs_repo.get_commit(commit_id)
270 270 except CommitDoesNotExistError:
271 271 return Response(_('Unknown commit_id {}').format(
272 272 h.escape(commit_id)))
273 273 except EmptyRepositoryError:
274 274 return Response(_('Empty repository'))
275 275
276 276 archive_name = '%s-%s%s%s' % (
277 277 safe_str(self.db_repo_name.replace('/', '_')),
278 278 '-sub' if subrepos else '',
279 279 safe_str(commit.short_id), ext)
280 280
281 281 use_cached_archive = False
282 282 archive_cache_enabled = CONFIG.get(
283 283 'archive_cache_dir') and not self.request.GET.get('no_cache')
284 284
285 285 if archive_cache_enabled:
286 286 # check if we it's ok to write
287 287 if not os.path.isdir(CONFIG['archive_cache_dir']):
288 288 os.makedirs(CONFIG['archive_cache_dir'])
289 289 cached_archive_path = os.path.join(
290 290 CONFIG['archive_cache_dir'], archive_name)
291 291 if os.path.isfile(cached_archive_path):
292 292 log.debug('Found cached archive in %s', cached_archive_path)
293 293 fd, archive = None, cached_archive_path
294 294 use_cached_archive = True
295 295 else:
296 296 log.debug('Archive %s is not yet cached', archive_name)
297 297
298 298 if not use_cached_archive:
299 299 # generate new archive
300 300 fd, archive = tempfile.mkstemp()
301 301 log.debug('Creating new temp archive in %s', archive)
302 302 try:
303 303 commit.archive_repo(archive, kind=fileformat, subrepos=subrepos)
304 304 except ImproperArchiveTypeError:
305 305 return _('Unknown archive type')
306 306 if archive_cache_enabled:
307 307 # if we generated the archive and we have cache enabled
308 308 # let's use this for future
309 309 log.debug('Storing new archive in %s', cached_archive_path)
310 310 shutil.move(archive, cached_archive_path)
311 311 archive = cached_archive_path
312 312
313 313 # store download action
314 314 audit_logger.store_web(
315 315 'repo.archive.download', action_data={
316 316 'user_agent': self.request.user_agent,
317 317 'archive_name': archive_name,
318 318 'archive_spec': fname,
319 319 'archive_cached': use_cached_archive},
320 320 user=self._rhodecode_user,
321 321 repo=self.db_repo,
322 322 commit=True
323 323 )
324 324
325 325 def get_chunked_archive(archive):
326 326 with open(archive, 'rb') as stream:
327 327 while True:
328 328 data = stream.read(16 * 1024)
329 329 if not data:
330 330 if fd: # fd means we used temporary file
331 331 os.close(fd)
332 332 if not archive_cache_enabled:
333 333 log.debug('Destroying temp archive %s', archive)
334 334 os.remove(archive)
335 335 break
336 336 yield data
337 337
338 338 response = Response(app_iter=get_chunked_archive(archive))
339 339 response.content_disposition = str(
340 340 'attachment; filename=%s' % archive_name)
341 341 response.content_type = str(content_type)
342 342
343 343 return response
344 344
345 345 def _get_file_node(self, commit_id, f_path):
346 346 if commit_id not in ['', None, 'None', '0' * 12, '0' * 40]:
347 347 commit = self.rhodecode_vcs_repo.get_commit(commit_id=commit_id)
348 348 try:
349 349 node = commit.get_node(f_path)
350 350 if node.is_dir():
351 351 raise NodeError('%s path is a %s not a file'
352 352 % (node, type(node)))
353 353 except NodeDoesNotExistError:
354 354 commit = EmptyCommit(
355 355 commit_id=commit_id,
356 356 idx=commit.idx,
357 357 repo=commit.repository,
358 358 alias=commit.repository.alias,
359 359 message=commit.message,
360 360 author=commit.author,
361 361 date=commit.date)
362 362 node = FileNode(f_path, '', commit=commit)
363 363 else:
364 364 commit = EmptyCommit(
365 365 repo=self.rhodecode_vcs_repo,
366 366 alias=self.rhodecode_vcs_repo.alias)
367 367 node = FileNode(f_path, '', commit=commit)
368 368 return node
369 369
370 370 @LoginRequired()
371 371 @HasRepoPermissionAnyDecorator(
372 372 'repository.read', 'repository.write', 'repository.admin')
373 373 @view_config(
374 374 route_name='repo_files_diff', request_method='GET',
375 375 renderer=None)
376 376 def repo_files_diff(self):
377 377 c = self.load_default_context()
378 378 f_path = self._get_f_path(self.request.matchdict)
379 379 diff1 = self.request.GET.get('diff1', '')
380 380 diff2 = self.request.GET.get('diff2', '')
381 381
382 382 path1, diff1 = parse_path_ref(diff1, default_path=f_path)
383 383
384 384 ignore_whitespace = str2bool(self.request.GET.get('ignorews'))
385 385 line_context = self.request.GET.get('context', 3)
386 386
387 387 if not any((diff1, diff2)):
388 388 h.flash(
389 389 'Need query parameter "diff1" or "diff2" to generate a diff.',
390 390 category='error')
391 391 raise HTTPBadRequest()
392 392
393 393 c.action = self.request.GET.get('diff')
394 394 if c.action not in ['download', 'raw']:
395 395 compare_url = h.route_path(
396 396 'repo_compare',
397 397 repo_name=self.db_repo_name,
398 398 source_ref_type='rev',
399 399 source_ref=diff1,
400 400 target_repo=self.db_repo_name,
401 401 target_ref_type='rev',
402 402 target_ref=diff2,
403 403 _query=dict(f_path=f_path))
404 404 # redirect to new view if we render diff
405 405 raise HTTPFound(compare_url)
406 406
407 407 try:
408 408 node1 = self._get_file_node(diff1, path1)
409 409 node2 = self._get_file_node(diff2, f_path)
410 410 except (RepositoryError, NodeError):
411 411 log.exception("Exception while trying to get node from repository")
412 412 raise HTTPFound(
413 413 h.route_path('repo_files', repo_name=self.db_repo_name,
414 414 commit_id='tip', f_path=f_path))
415 415
416 416 if all(isinstance(node.commit, EmptyCommit)
417 417 for node in (node1, node2)):
418 418 raise HTTPNotFound()
419 419
420 420 c.commit_1 = node1.commit
421 421 c.commit_2 = node2.commit
422 422
423 423 if c.action == 'download':
424 424 _diff = diffs.get_gitdiff(node1, node2,
425 425 ignore_whitespace=ignore_whitespace,
426 426 context=line_context)
427 427 diff = diffs.DiffProcessor(_diff, format='gitdiff')
428 428
429 429 response = Response(self.path_filter.get_raw_patch(diff))
430 430 response.content_type = 'text/plain'
431 431 response.content_disposition = (
432 432 'attachment; filename=%s_%s_vs_%s.diff' % (f_path, diff1, diff2)
433 433 )
434 434 charset = self._get_default_encoding(c)
435 435 if charset:
436 436 response.charset = charset
437 437 return response
438 438
439 439 elif c.action == 'raw':
440 440 _diff = diffs.get_gitdiff(node1, node2,
441 441 ignore_whitespace=ignore_whitespace,
442 442 context=line_context)
443 443 diff = diffs.DiffProcessor(_diff, format='gitdiff')
444 444
445 445 response = Response(self.path_filter.get_raw_patch(diff))
446 446 response.content_type = 'text/plain'
447 447 charset = self._get_default_encoding(c)
448 448 if charset:
449 449 response.charset = charset
450 450 return response
451 451
452 452 # in case we ever end up here
453 453 raise HTTPNotFound()
454 454
455 455 @LoginRequired()
456 456 @HasRepoPermissionAnyDecorator(
457 457 'repository.read', 'repository.write', 'repository.admin')
458 458 @view_config(
459 459 route_name='repo_files_diff_2way_redirect', request_method='GET',
460 460 renderer=None)
461 461 def repo_files_diff_2way_redirect(self):
462 462 """
463 463 Kept only to make OLD links work
464 464 """
465 f_path = self._get_f_path(self.request.matchdict)
465 f_path = self._get_f_path_unchecked(self.request.matchdict)
466 466 diff1 = self.request.GET.get('diff1', '')
467 467 diff2 = self.request.GET.get('diff2', '')
468 468
469 469 if not any((diff1, diff2)):
470 470 h.flash(
471 471 'Need query parameter "diff1" or "diff2" to generate a diff.',
472 472 category='error')
473 473 raise HTTPBadRequest()
474 474
475 475 compare_url = h.route_path(
476 476 'repo_compare',
477 477 repo_name=self.db_repo_name,
478 478 source_ref_type='rev',
479 479 source_ref=diff1,
480 480 target_ref_type='rev',
481 481 target_ref=diff2,
482 482 _query=dict(f_path=f_path, diffmode='sideside',
483 483 target_repo=self.db_repo_name,))
484 484 raise HTTPFound(compare_url)
485 485
486 486 @LoginRequired()
487 487 @HasRepoPermissionAnyDecorator(
488 488 'repository.read', 'repository.write', 'repository.admin')
489 489 @view_config(
490 490 route_name='repo_files', request_method='GET',
491 491 renderer=None)
492 492 @view_config(
493 493 route_name='repo_files:default_path', request_method='GET',
494 494 renderer=None)
495 495 @view_config(
496 496 route_name='repo_files:default_commit', request_method='GET',
497 497 renderer=None)
498 498 @view_config(
499 499 route_name='repo_files:rendered', request_method='GET',
500 500 renderer=None)
501 501 @view_config(
502 502 route_name='repo_files:annotated', request_method='GET',
503 503 renderer=None)
504 504 def repo_files(self):
505 505 c = self.load_default_context()
506 506
507 507 view_name = getattr(self.request.matched_route, 'name', None)
508 508
509 509 c.annotate = view_name == 'repo_files:annotated'
510 510 # default is false, but .rst/.md files later are auto rendered, we can
511 511 # overwrite auto rendering by setting this GET flag
512 512 c.renderer = view_name == 'repo_files:rendered' or \
513 513 not self.request.GET.get('no-render', False)
514 514
515 515 # redirect to given commit_id from form if given
516 516 get_commit_id = self.request.GET.get('at_rev', None)
517 517 if get_commit_id:
518 518 self._get_commit_or_redirect(get_commit_id)
519 519
520 520 commit_id, f_path = self._get_commit_and_path()
521 521 c.commit = self._get_commit_or_redirect(commit_id)
522 522 c.branch = self.request.GET.get('branch', None)
523 523 c.f_path = f_path
524 524
525 525 # prev link
526 526 try:
527 527 prev_commit = c.commit.prev(c.branch)
528 528 c.prev_commit = prev_commit
529 529 c.url_prev = h.route_path(
530 530 'repo_files', repo_name=self.db_repo_name,
531 531 commit_id=prev_commit.raw_id, f_path=f_path)
532 532 if c.branch:
533 533 c.url_prev += '?branch=%s' % c.branch
534 534 except (CommitDoesNotExistError, VCSError):
535 535 c.url_prev = '#'
536 536 c.prev_commit = EmptyCommit()
537 537
538 538 # next link
539 539 try:
540 540 next_commit = c.commit.next(c.branch)
541 541 c.next_commit = next_commit
542 542 c.url_next = h.route_path(
543 543 'repo_files', repo_name=self.db_repo_name,
544 544 commit_id=next_commit.raw_id, f_path=f_path)
545 545 if c.branch:
546 546 c.url_next += '?branch=%s' % c.branch
547 547 except (CommitDoesNotExistError, VCSError):
548 548 c.url_next = '#'
549 549 c.next_commit = EmptyCommit()
550 550
551 551 # files or dirs
552 552 try:
553 553 c.file = c.commit.get_node(f_path)
554 554 c.file_author = True
555 555 c.file_tree = ''
556 556
557 557 # load file content
558 558 if c.file.is_file():
559 559 c.lf_node = c.file.get_largefile_node()
560 560
561 561 c.file_source_page = 'true'
562 562 c.file_last_commit = c.file.last_commit
563 563 if c.file.size < c.visual.cut_off_limit_diff:
564 564 if c.annotate: # annotation has precedence over renderer
565 565 c.annotated_lines = filenode_as_annotated_lines_tokens(
566 566 c.file
567 567 )
568 568 else:
569 569 c.renderer = (
570 570 c.renderer and h.renderer_from_filename(c.file.path)
571 571 )
572 572 if not c.renderer:
573 573 c.lines = filenode_as_lines_tokens(c.file)
574 574
575 575 c.on_branch_head = self._is_valid_head(
576 576 commit_id, self.rhodecode_vcs_repo)
577 577
578 578 branch = c.commit.branch if (
579 579 c.commit.branch and '/' not in c.commit.branch) else None
580 580 c.branch_or_raw_id = branch or c.commit.raw_id
581 581 c.branch_name = c.commit.branch or h.short_id(c.commit.raw_id)
582 582
583 583 author = c.file_last_commit.author
584 584 c.authors = [[
585 585 h.email(author),
586 586 h.person(author, 'username_or_name_or_email'),
587 587 1
588 588 ]]
589 589
590 590 else: # load tree content at path
591 591 c.file_source_page = 'false'
592 592 c.authors = []
593 593 # this loads a simple tree without metadata to speed things up
594 594 # later via ajax we call repo_nodetree_full and fetch whole
595 595 c.file_tree = self._get_tree_at_commit(
596 596 c, c.commit.raw_id, f_path)
597 597
598 598 except RepositoryError as e:
599 599 h.flash(safe_str(h.escape(e)), category='error')
600 600 raise HTTPNotFound()
601 601
602 602 if self.request.environ.get('HTTP_X_PJAX'):
603 603 html = render('rhodecode:templates/files/files_pjax.mako',
604 604 self._get_template_context(c), self.request)
605 605 else:
606 606 html = render('rhodecode:templates/files/files.mako',
607 607 self._get_template_context(c), self.request)
608 608 return Response(html)
609 609
610 610 @HasRepoPermissionAnyDecorator(
611 611 'repository.read', 'repository.write', 'repository.admin')
612 612 @view_config(
613 613 route_name='repo_files:annotated_previous', request_method='GET',
614 614 renderer=None)
615 615 def repo_files_annotated_previous(self):
616 616 self.load_default_context()
617 617
618 618 commit_id, f_path = self._get_commit_and_path()
619 619 commit = self._get_commit_or_redirect(commit_id)
620 620 prev_commit_id = commit.raw_id
621 621 line_anchor = self.request.GET.get('line_anchor')
622 622 is_file = False
623 623 try:
624 624 _file = commit.get_node(f_path)
625 625 is_file = _file.is_file()
626 626 except (NodeDoesNotExistError, CommitDoesNotExistError, VCSError):
627 627 pass
628 628
629 629 if is_file:
630 630 history = commit.get_file_history(f_path)
631 631 prev_commit_id = history[1].raw_id \
632 632 if len(history) > 1 else prev_commit_id
633 633 prev_url = h.route_path(
634 634 'repo_files:annotated', repo_name=self.db_repo_name,
635 635 commit_id=prev_commit_id, f_path=f_path,
636 636 _anchor='L{}'.format(line_anchor))
637 637
638 638 raise HTTPFound(prev_url)
639 639
640 640 @LoginRequired()
641 641 @HasRepoPermissionAnyDecorator(
642 642 'repository.read', 'repository.write', 'repository.admin')
643 643 @view_config(
644 644 route_name='repo_nodetree_full', request_method='GET',
645 645 renderer=None, xhr=True)
646 646 @view_config(
647 647 route_name='repo_nodetree_full:default_path', request_method='GET',
648 648 renderer=None, xhr=True)
649 649 def repo_nodetree_full(self):
650 650 """
651 651 Returns rendered html of file tree that contains commit date,
652 652 author, commit_id for the specified combination of
653 653 repo, commit_id and file path
654 654 """
655 655 c = self.load_default_context()
656 656
657 657 commit_id, f_path = self._get_commit_and_path()
658 658 commit = self._get_commit_or_redirect(commit_id)
659 659 try:
660 660 dir_node = commit.get_node(f_path)
661 661 except RepositoryError as e:
662 662 return Response('error: {}'.format(h.escape(safe_str(e))))
663 663
664 664 if dir_node.is_file():
665 665 return Response('')
666 666
667 667 c.file = dir_node
668 668 c.commit = commit
669 669
670 670 # using force=True here, make a little trick. We flush the cache and
671 671 # compute it using the same key as without previous full_load, so now
672 672 # the fully loaded tree is now returned instead of partial,
673 673 # and we store this in caches
674 674 html = self._get_tree_at_commit(
675 675 c, commit.raw_id, dir_node.path, full_load=True, force=True)
676 676
677 677 return Response(html)
678 678
679 679 def _get_attachement_disposition(self, f_path):
680 680 return 'attachment; filename=%s' % \
681 681 safe_str(f_path.split(Repository.NAME_SEP)[-1])
682 682
683 683 @LoginRequired()
684 684 @HasRepoPermissionAnyDecorator(
685 685 'repository.read', 'repository.write', 'repository.admin')
686 686 @view_config(
687 687 route_name='repo_file_raw', request_method='GET',
688 688 renderer=None)
689 689 def repo_file_raw(self):
690 690 """
691 691 Action for show as raw, some mimetypes are "rendered",
692 692 those include images, icons.
693 693 """
694 694 c = self.load_default_context()
695 695
696 696 commit_id, f_path = self._get_commit_and_path()
697 697 commit = self._get_commit_or_redirect(commit_id)
698 698 file_node = self._get_filenode_or_redirect(commit, f_path)
699 699
700 700 raw_mimetype_mapping = {
701 701 # map original mimetype to a mimetype used for "show as raw"
702 702 # you can also provide a content-disposition to override the
703 703 # default "attachment" disposition.
704 704 # orig_type: (new_type, new_dispo)
705 705
706 706 # show images inline:
707 707 # Do not re-add SVG: it is unsafe and permits XSS attacks. One can
708 708 # for example render an SVG with javascript inside or even render
709 709 # HTML.
710 710 'image/x-icon': ('image/x-icon', 'inline'),
711 711 'image/png': ('image/png', 'inline'),
712 712 'image/gif': ('image/gif', 'inline'),
713 713 'image/jpeg': ('image/jpeg', 'inline'),
714 714 'application/pdf': ('application/pdf', 'inline'),
715 715 }
716 716
717 717 mimetype = file_node.mimetype
718 718 try:
719 719 mimetype, disposition = raw_mimetype_mapping[mimetype]
720 720 except KeyError:
721 721 # we don't know anything special about this, handle it safely
722 722 if file_node.is_binary:
723 723 # do same as download raw for binary files
724 724 mimetype, disposition = 'application/octet-stream', 'attachment'
725 725 else:
726 726 # do not just use the original mimetype, but force text/plain,
727 727 # otherwise it would serve text/html and that might be unsafe.
728 728 # Note: underlying vcs library fakes text/plain mimetype if the
729 729 # mimetype can not be determined and it thinks it is not
730 730 # binary.This might lead to erroneous text display in some
731 731 # cases, but helps in other cases, like with text files
732 732 # without extension.
733 733 mimetype, disposition = 'text/plain', 'inline'
734 734
735 735 if disposition == 'attachment':
736 736 disposition = self._get_attachement_disposition(f_path)
737 737
738 738 def stream_node():
739 739 yield file_node.raw_bytes
740 740
741 741 response = Response(app_iter=stream_node())
742 742 response.content_disposition = disposition
743 743 response.content_type = mimetype
744 744
745 745 charset = self._get_default_encoding(c)
746 746 if charset:
747 747 response.charset = charset
748 748
749 749 return response
750 750
751 751 @LoginRequired()
752 752 @HasRepoPermissionAnyDecorator(
753 753 'repository.read', 'repository.write', 'repository.admin')
754 754 @view_config(
755 755 route_name='repo_file_download', request_method='GET',
756 756 renderer=None)
757 757 @view_config(
758 758 route_name='repo_file_download:legacy', request_method='GET',
759 759 renderer=None)
760 760 def repo_file_download(self):
761 761 c = self.load_default_context()
762 762
763 763 commit_id, f_path = self._get_commit_and_path()
764 764 commit = self._get_commit_or_redirect(commit_id)
765 765 file_node = self._get_filenode_or_redirect(commit, f_path)
766 766
767 767 if self.request.GET.get('lf'):
768 768 # only if lf get flag is passed, we download this file
769 769 # as LFS/Largefile
770 770 lf_node = file_node.get_largefile_node()
771 771 if lf_node:
772 772 # overwrite our pointer with the REAL large-file
773 773 file_node = lf_node
774 774
775 775 disposition = self._get_attachement_disposition(f_path)
776 776
777 777 def stream_node():
778 778 yield file_node.raw_bytes
779 779
780 780 response = Response(app_iter=stream_node())
781 781 response.content_disposition = disposition
782 782 response.content_type = file_node.mimetype
783 783
784 784 charset = self._get_default_encoding(c)
785 785 if charset:
786 786 response.charset = charset
787 787
788 788 return response
789 789
790 790 def _get_nodelist_at_commit(self, repo_name, commit_id, f_path):
791 791 def _cached_nodes():
792 792 log.debug('Generating cached nodelist for %s, %s, %s',
793 793 repo_name, commit_id, f_path)
794 794 try:
795 795 _d, _f = ScmModel().get_nodes(
796 796 repo_name, commit_id, f_path, flat=False)
797 797 except (RepositoryError, CommitDoesNotExistError, Exception) as e:
798 798 log.exception(safe_str(e))
799 799 h.flash(safe_str(h.escape(e)), category='error')
800 800 raise HTTPFound(h.route_path(
801 801 'repo_files', repo_name=self.db_repo_name,
802 802 commit_id='tip', f_path='/'))
803 803 return _d + _f
804 804
805 805 cache_manager = self._get_tree_cache_manager(
806 806 caches.FILE_SEARCH_TREE_META)
807 807
808 808 cache_key = caches.compute_key_from_params(
809 809 repo_name, commit_id, f_path)
810 810 return cache_manager.get(cache_key, createfunc=_cached_nodes)
811 811
812 812 @LoginRequired()
813 813 @HasRepoPermissionAnyDecorator(
814 814 'repository.read', 'repository.write', 'repository.admin')
815 815 @view_config(
816 816 route_name='repo_files_nodelist', request_method='GET',
817 817 renderer='json_ext', xhr=True)
818 818 def repo_nodelist(self):
819 819 self.load_default_context()
820 820
821 821 commit_id, f_path = self._get_commit_and_path()
822 822 commit = self._get_commit_or_redirect(commit_id)
823 823
824 824 metadata = self._get_nodelist_at_commit(
825 825 self.db_repo_name, commit.raw_id, f_path)
826 826 return {'nodes': metadata}
827 827
828 828 def _create_references(
829 829 self, branches_or_tags, symbolic_reference, f_path):
830 830 items = []
831 831 for name, commit_id in branches_or_tags.items():
832 832 sym_ref = symbolic_reference(commit_id, name, f_path)
833 833 items.append((sym_ref, name))
834 834 return items
835 835
836 836 def _symbolic_reference(self, commit_id, name, f_path):
837 837 return commit_id
838 838
839 839 def _symbolic_reference_svn(self, commit_id, name, f_path):
840 840 new_f_path = vcspath.join(name, f_path)
841 841 return u'%s@%s' % (new_f_path, commit_id)
842 842
843 843 def _get_node_history(self, commit_obj, f_path, commits=None):
844 844 """
845 845 get commit history for given node
846 846
847 847 :param commit_obj: commit to calculate history
848 848 :param f_path: path for node to calculate history for
849 849 :param commits: if passed don't calculate history and take
850 850 commits defined in this list
851 851 """
852 852 _ = self.request.translate
853 853
854 854 # calculate history based on tip
855 855 tip = self.rhodecode_vcs_repo.get_commit()
856 856 if commits is None:
857 857 pre_load = ["author", "branch"]
858 858 try:
859 859 commits = tip.get_file_history(f_path, pre_load=pre_load)
860 860 except (NodeDoesNotExistError, CommitError):
861 861 # this node is not present at tip!
862 862 commits = commit_obj.get_file_history(f_path, pre_load=pre_load)
863 863
864 864 history = []
865 865 commits_group = ([], _("Changesets"))
866 866 for commit in commits:
867 867 branch = ' (%s)' % commit.branch if commit.branch else ''
868 868 n_desc = 'r%s:%s%s' % (commit.idx, commit.short_id, branch)
869 869 commits_group[0].append((commit.raw_id, n_desc,))
870 870 history.append(commits_group)
871 871
872 872 symbolic_reference = self._symbolic_reference
873 873
874 874 if self.rhodecode_vcs_repo.alias == 'svn':
875 875 adjusted_f_path = RepoFilesView.adjust_file_path_for_svn(
876 876 f_path, self.rhodecode_vcs_repo)
877 877 if adjusted_f_path != f_path:
878 878 log.debug(
879 879 'Recognized svn tag or branch in file "%s", using svn '
880 880 'specific symbolic references', f_path)
881 881 f_path = adjusted_f_path
882 882 symbolic_reference = self._symbolic_reference_svn
883 883
884 884 branches = self._create_references(
885 885 self.rhodecode_vcs_repo.branches, symbolic_reference, f_path)
886 886 branches_group = (branches, _("Branches"))
887 887
888 888 tags = self._create_references(
889 889 self.rhodecode_vcs_repo.tags, symbolic_reference, f_path)
890 890 tags_group = (tags, _("Tags"))
891 891
892 892 history.append(branches_group)
893 893 history.append(tags_group)
894 894
895 895 return history, commits
896 896
897 897 @LoginRequired()
898 898 @HasRepoPermissionAnyDecorator(
899 899 'repository.read', 'repository.write', 'repository.admin')
900 900 @view_config(
901 901 route_name='repo_file_history', request_method='GET',
902 902 renderer='json_ext')
903 903 def repo_file_history(self):
904 904 self.load_default_context()
905 905
906 906 commit_id, f_path = self._get_commit_and_path()
907 907 commit = self._get_commit_or_redirect(commit_id)
908 908 file_node = self._get_filenode_or_redirect(commit, f_path)
909 909
910 910 if file_node.is_file():
911 911 file_history, _hist = self._get_node_history(commit, f_path)
912 912
913 913 res = []
914 914 for obj in file_history:
915 915 res.append({
916 916 'text': obj[1],
917 917 'children': [{'id': o[0], 'text': o[1]} for o in obj[0]]
918 918 })
919 919
920 920 data = {
921 921 'more': False,
922 922 'results': res
923 923 }
924 924 return data
925 925
926 926 log.warning('Cannot fetch history for directory')
927 927 raise HTTPBadRequest()
928 928
929 929 @LoginRequired()
930 930 @HasRepoPermissionAnyDecorator(
931 931 'repository.read', 'repository.write', 'repository.admin')
932 932 @view_config(
933 933 route_name='repo_file_authors', request_method='GET',
934 934 renderer='rhodecode:templates/files/file_authors_box.mako')
935 935 def repo_file_authors(self):
936 936 c = self.load_default_context()
937 937
938 938 commit_id, f_path = self._get_commit_and_path()
939 939 commit = self._get_commit_or_redirect(commit_id)
940 940 file_node = self._get_filenode_or_redirect(commit, f_path)
941 941
942 942 if not file_node.is_file():
943 943 raise HTTPBadRequest()
944 944
945 945 c.file_last_commit = file_node.last_commit
946 946 if self.request.GET.get('annotate') == '1':
947 947 # use _hist from annotation if annotation mode is on
948 948 commit_ids = set(x[1] for x in file_node.annotate)
949 949 _hist = (
950 950 self.rhodecode_vcs_repo.get_commit(commit_id)
951 951 for commit_id in commit_ids)
952 952 else:
953 953 _f_history, _hist = self._get_node_history(commit, f_path)
954 954 c.file_author = False
955 955
956 956 unique = collections.OrderedDict()
957 957 for commit in _hist:
958 958 author = commit.author
959 959 if author not in unique:
960 960 unique[commit.author] = [
961 961 h.email(author),
962 962 h.person(author, 'username_or_name_or_email'),
963 963 1 # counter
964 964 ]
965 965
966 966 else:
967 967 # increase counter
968 968 unique[commit.author][2] += 1
969 969
970 970 c.authors = [val for val in unique.values()]
971 971
972 972 return self._get_template_context(c)
973 973
974 974 @LoginRequired()
975 975 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
976 976 @view_config(
977 977 route_name='repo_files_remove_file', request_method='GET',
978 978 renderer='rhodecode:templates/files/files_delete.mako')
979 979 def repo_files_remove_file(self):
980 980 _ = self.request.translate
981 981 c = self.load_default_context()
982 982 commit_id, f_path = self._get_commit_and_path()
983 983
984 984 self._ensure_not_locked()
985 985
986 986 if not self._is_valid_head(commit_id, self.rhodecode_vcs_repo):
987 987 h.flash(_('You can only delete files with commit '
988 988 'being a valid branch '), category='warning')
989 989 raise HTTPFound(
990 990 h.route_path('repo_files',
991 991 repo_name=self.db_repo_name, commit_id='tip',
992 992 f_path=f_path))
993 993
994 994 c.commit = self._get_commit_or_redirect(commit_id)
995 995 c.file = self._get_filenode_or_redirect(c.commit, f_path)
996 996
997 997 c.default_message = _(
998 998 'Deleted file {} via RhodeCode Enterprise').format(f_path)
999 999 c.f_path = f_path
1000 1000
1001 1001 return self._get_template_context(c)
1002 1002
1003 1003 @LoginRequired()
1004 1004 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1005 1005 @CSRFRequired()
1006 1006 @view_config(
1007 1007 route_name='repo_files_delete_file', request_method='POST',
1008 1008 renderer=None)
1009 1009 def repo_files_delete_file(self):
1010 1010 _ = self.request.translate
1011 1011
1012 1012 c = self.load_default_context()
1013 1013 commit_id, f_path = self._get_commit_and_path()
1014 1014
1015 1015 self._ensure_not_locked()
1016 1016
1017 1017 if not self._is_valid_head(commit_id, self.rhodecode_vcs_repo):
1018 1018 h.flash(_('You can only delete files with commit '
1019 1019 'being a valid branch '), category='warning')
1020 1020 raise HTTPFound(
1021 1021 h.route_path('repo_files',
1022 1022 repo_name=self.db_repo_name, commit_id='tip',
1023 1023 f_path=f_path))
1024 1024
1025 1025 c.commit = self._get_commit_or_redirect(commit_id)
1026 1026 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1027 1027
1028 1028 c.default_message = _(
1029 1029 'Deleted file {} via RhodeCode Enterprise').format(f_path)
1030 1030 c.f_path = f_path
1031 1031 node_path = f_path
1032 1032 author = self._rhodecode_db_user.full_contact
1033 1033 message = self.request.POST.get('message') or c.default_message
1034 1034 try:
1035 1035 nodes = {
1036 1036 node_path: {
1037 1037 'content': ''
1038 1038 }
1039 1039 }
1040 1040 ScmModel().delete_nodes(
1041 1041 user=self._rhodecode_db_user.user_id, repo=self.db_repo,
1042 1042 message=message,
1043 1043 nodes=nodes,
1044 1044 parent_commit=c.commit,
1045 1045 author=author,
1046 1046 )
1047 1047
1048 1048 h.flash(
1049 1049 _('Successfully deleted file `{}`').format(
1050 1050 h.escape(f_path)), category='success')
1051 1051 except Exception:
1052 1052 log.exception('Error during commit operation')
1053 1053 h.flash(_('Error occurred during commit'), category='error')
1054 1054 raise HTTPFound(
1055 1055 h.route_path('repo_commit', repo_name=self.db_repo_name,
1056 1056 commit_id='tip'))
1057 1057
1058 1058 @LoginRequired()
1059 1059 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1060 1060 @view_config(
1061 1061 route_name='repo_files_edit_file', request_method='GET',
1062 1062 renderer='rhodecode:templates/files/files_edit.mako')
1063 1063 def repo_files_edit_file(self):
1064 1064 _ = self.request.translate
1065 1065 c = self.load_default_context()
1066 1066 commit_id, f_path = self._get_commit_and_path()
1067 1067
1068 1068 self._ensure_not_locked()
1069 1069
1070 1070 if not self._is_valid_head(commit_id, self.rhodecode_vcs_repo):
1071 1071 h.flash(_('You can only edit files with commit '
1072 1072 'being a valid branch '), category='warning')
1073 1073 raise HTTPFound(
1074 1074 h.route_path('repo_files',
1075 1075 repo_name=self.db_repo_name, commit_id='tip',
1076 1076 f_path=f_path))
1077 1077
1078 1078 c.commit = self._get_commit_or_redirect(commit_id)
1079 1079 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1080 1080
1081 1081 if c.file.is_binary:
1082 1082 files_url = h.route_path(
1083 1083 'repo_files',
1084 1084 repo_name=self.db_repo_name,
1085 1085 commit_id=c.commit.raw_id, f_path=f_path)
1086 1086 raise HTTPFound(files_url)
1087 1087
1088 1088 c.default_message = _(
1089 1089 'Edited file {} via RhodeCode Enterprise').format(f_path)
1090 1090 c.f_path = f_path
1091 1091
1092 1092 return self._get_template_context(c)
1093 1093
1094 1094 @LoginRequired()
1095 1095 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1096 1096 @CSRFRequired()
1097 1097 @view_config(
1098 1098 route_name='repo_files_update_file', request_method='POST',
1099 1099 renderer=None)
1100 1100 def repo_files_update_file(self):
1101 1101 _ = self.request.translate
1102 1102 c = self.load_default_context()
1103 1103 commit_id, f_path = self._get_commit_and_path()
1104 1104
1105 1105 self._ensure_not_locked()
1106 1106
1107 1107 if not self._is_valid_head(commit_id, self.rhodecode_vcs_repo):
1108 1108 h.flash(_('You can only edit files with commit '
1109 1109 'being a valid branch '), category='warning')
1110 1110 raise HTTPFound(
1111 1111 h.route_path('repo_files',
1112 1112 repo_name=self.db_repo_name, commit_id='tip',
1113 1113 f_path=f_path))
1114 1114
1115 1115 c.commit = self._get_commit_or_redirect(commit_id)
1116 1116 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1117 1117
1118 1118 if c.file.is_binary:
1119 1119 raise HTTPFound(
1120 1120 h.route_path('repo_files',
1121 1121 repo_name=self.db_repo_name,
1122 1122 commit_id=c.commit.raw_id,
1123 1123 f_path=f_path))
1124 1124
1125 1125 c.default_message = _(
1126 1126 'Edited file {} via RhodeCode Enterprise').format(f_path)
1127 1127 c.f_path = f_path
1128 1128 old_content = c.file.content
1129 1129 sl = old_content.splitlines(1)
1130 1130 first_line = sl[0] if sl else ''
1131 1131
1132 1132 r_post = self.request.POST
1133 1133 # modes: 0 - Unix, 1 - Mac, 2 - DOS
1134 1134 mode = detect_mode(first_line, 0)
1135 1135 content = convert_line_endings(r_post.get('content', ''), mode)
1136 1136
1137 1137 message = r_post.get('message') or c.default_message
1138 1138 org_f_path = c.file.unicode_path
1139 1139 filename = r_post['filename']
1140 1140 org_filename = c.file.name
1141 1141
1142 1142 if content == old_content and filename == org_filename:
1143 1143 h.flash(_('No changes'), category='warning')
1144 1144 raise HTTPFound(
1145 1145 h.route_path('repo_commit', repo_name=self.db_repo_name,
1146 1146 commit_id='tip'))
1147 1147 try:
1148 1148 mapping = {
1149 1149 org_f_path: {
1150 1150 'org_filename': org_f_path,
1151 1151 'filename': os.path.join(c.file.dir_path, filename),
1152 1152 'content': content,
1153 1153 'lexer': '',
1154 1154 'op': 'mod',
1155 1155 }
1156 1156 }
1157 1157
1158 1158 ScmModel().update_nodes(
1159 1159 user=self._rhodecode_db_user.user_id,
1160 1160 repo=self.db_repo,
1161 1161 message=message,
1162 1162 nodes=mapping,
1163 1163 parent_commit=c.commit,
1164 1164 )
1165 1165
1166 1166 h.flash(
1167 1167 _('Successfully committed changes to file `{}`').format(
1168 1168 h.escape(f_path)), category='success')
1169 1169 except Exception:
1170 1170 log.exception('Error occurred during commit')
1171 1171 h.flash(_('Error occurred during commit'), category='error')
1172 1172 raise HTTPFound(
1173 1173 h.route_path('repo_commit', repo_name=self.db_repo_name,
1174 1174 commit_id='tip'))
1175 1175
1176 1176 @LoginRequired()
1177 1177 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1178 1178 @view_config(
1179 1179 route_name='repo_files_add_file', request_method='GET',
1180 1180 renderer='rhodecode:templates/files/files_add.mako')
1181 1181 def repo_files_add_file(self):
1182 1182 _ = self.request.translate
1183 1183 c = self.load_default_context()
1184 1184 commit_id, f_path = self._get_commit_and_path()
1185 1185
1186 1186 self._ensure_not_locked()
1187 1187
1188 1188 c.commit = self._get_commit_or_redirect(commit_id, redirect_after=False)
1189 1189 if c.commit is None:
1190 1190 c.commit = EmptyCommit(alias=self.rhodecode_vcs_repo.alias)
1191 1191 c.default_message = (_('Added file via RhodeCode Enterprise'))
1192 1192 c.f_path = f_path.lstrip('/') # ensure not relative path
1193 1193
1194 1194 return self._get_template_context(c)
1195 1195
1196 1196 @LoginRequired()
1197 1197 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1198 1198 @CSRFRequired()
1199 1199 @view_config(
1200 1200 route_name='repo_files_create_file', request_method='POST',
1201 1201 renderer=None)
1202 1202 def repo_files_create_file(self):
1203 1203 _ = self.request.translate
1204 1204 c = self.load_default_context()
1205 1205 commit_id, f_path = self._get_commit_and_path()
1206 1206
1207 1207 self._ensure_not_locked()
1208 1208
1209 1209 r_post = self.request.POST
1210 1210
1211 1211 c.commit = self._get_commit_or_redirect(
1212 1212 commit_id, redirect_after=False)
1213 1213 if c.commit is None:
1214 1214 c.commit = EmptyCommit(alias=self.rhodecode_vcs_repo.alias)
1215 1215 c.default_message = (_('Added file via RhodeCode Enterprise'))
1216 1216 c.f_path = f_path
1217 1217 unix_mode = 0
1218 1218 content = convert_line_endings(r_post.get('content', ''), unix_mode)
1219 1219
1220 1220 message = r_post.get('message') or c.default_message
1221 1221 filename = r_post.get('filename')
1222 1222 location = r_post.get('location', '') # dir location
1223 1223 file_obj = r_post.get('upload_file', None)
1224 1224
1225 1225 if file_obj is not None and hasattr(file_obj, 'filename'):
1226 1226 filename = r_post.get('filename_upload')
1227 1227 content = file_obj.file
1228 1228
1229 1229 if hasattr(content, 'file'):
1230 1230 # non posix systems store real file under file attr
1231 1231 content = content.file
1232 1232
1233 1233 if self.rhodecode_vcs_repo.is_empty:
1234 1234 default_redirect_url = h.route_path(
1235 1235 'repo_summary', repo_name=self.db_repo_name)
1236 1236 else:
1237 1237 default_redirect_url = h.route_path(
1238 1238 'repo_commit', repo_name=self.db_repo_name, commit_id='tip')
1239 1239
1240 1240 # If there's no commit, redirect to repo summary
1241 1241 if type(c.commit) is EmptyCommit:
1242 1242 redirect_url = h.route_path(
1243 1243 'repo_summary', repo_name=self.db_repo_name)
1244 1244 else:
1245 1245 redirect_url = default_redirect_url
1246 1246
1247 1247 if not filename:
1248 1248 h.flash(_('No filename'), category='warning')
1249 1249 raise HTTPFound(redirect_url)
1250 1250
1251 1251 # extract the location from filename,
1252 1252 # allows using foo/bar.txt syntax to create subdirectories
1253 1253 subdir_loc = filename.rsplit('/', 1)
1254 1254 if len(subdir_loc) == 2:
1255 1255 location = os.path.join(location, subdir_loc[0])
1256 1256
1257 1257 # strip all crap out of file, just leave the basename
1258 1258 filename = os.path.basename(filename)
1259 1259 node_path = os.path.join(location, filename)
1260 1260 author = self._rhodecode_db_user.full_contact
1261 1261
1262 1262 try:
1263 1263 nodes = {
1264 1264 node_path: {
1265 1265 'content': content
1266 1266 }
1267 1267 }
1268 1268 ScmModel().create_nodes(
1269 1269 user=self._rhodecode_db_user.user_id,
1270 1270 repo=self.db_repo,
1271 1271 message=message,
1272 1272 nodes=nodes,
1273 1273 parent_commit=c.commit,
1274 1274 author=author,
1275 1275 )
1276 1276
1277 1277 h.flash(
1278 1278 _('Successfully committed new file `{}`').format(
1279 1279 h.escape(node_path)), category='success')
1280 1280 except NonRelativePathError:
1281 1281 log.exception('Non Relative path found')
1282 1282 h.flash(_(
1283 1283 'The location specified must be a relative path and must not '
1284 1284 'contain .. in the path'), category='warning')
1285 1285 raise HTTPFound(default_redirect_url)
1286 1286 except (NodeError, NodeAlreadyExistsError) as e:
1287 1287 h.flash(_(h.escape(e)), category='error')
1288 1288 except Exception:
1289 1289 log.exception('Error occurred during commit')
1290 1290 h.flash(_('Error occurred during commit'), category='error')
1291 1291
1292 1292 raise HTTPFound(default_redirect_url)
General Comments 0
You need to be logged in to leave comments. Login now