##// END OF EJS Templates
files: prevent XSS in fake errors message on filenodes.
marcink -
r2160:844c2584 default
parent child Browse files
Show More
@@ -1,1290 +1,1292 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2011-2017 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 self._register_global_c(c)
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 return Response(_('Unknown archive type for: `{}`').format(fname))
265 return Response(_('Unknown archive type for: `{}`').format(
266 h.escape(fname)))
266 267
267 268 try:
268 269 commit = self.rhodecode_vcs_repo.get_commit(commit_id)
269 270 except CommitDoesNotExistError:
270 return Response(_('Unknown commit_id %s') % commit_id)
271 return Response(_('Unknown commit_id {}').format(
272 h.escape(commit_id)))
271 273 except EmptyRepositoryError:
272 274 return Response(_('Empty repository'))
273 275
274 276 archive_name = '%s-%s%s%s' % (
275 277 safe_str(self.db_repo_name.replace('/', '_')),
276 278 '-sub' if subrepos else '',
277 279 safe_str(commit.short_id), ext)
278 280
279 281 use_cached_archive = False
280 282 archive_cache_enabled = CONFIG.get(
281 283 'archive_cache_dir') and not self.request.GET.get('no_cache')
282 284
283 285 if archive_cache_enabled:
284 286 # check if we it's ok to write
285 287 if not os.path.isdir(CONFIG['archive_cache_dir']):
286 288 os.makedirs(CONFIG['archive_cache_dir'])
287 289 cached_archive_path = os.path.join(
288 290 CONFIG['archive_cache_dir'], archive_name)
289 291 if os.path.isfile(cached_archive_path):
290 292 log.debug('Found cached archive in %s', cached_archive_path)
291 293 fd, archive = None, cached_archive_path
292 294 use_cached_archive = True
293 295 else:
294 296 log.debug('Archive %s is not yet cached', archive_name)
295 297
296 298 if not use_cached_archive:
297 299 # generate new archive
298 300 fd, archive = tempfile.mkstemp()
299 301 log.debug('Creating new temp archive in %s', archive)
300 302 try:
301 303 commit.archive_repo(archive, kind=fileformat, subrepos=subrepos)
302 304 except ImproperArchiveTypeError:
303 305 return _('Unknown archive type')
304 306 if archive_cache_enabled:
305 307 # if we generated the archive and we have cache enabled
306 308 # let's use this for future
307 309 log.debug('Storing new archive in %s', cached_archive_path)
308 310 shutil.move(archive, cached_archive_path)
309 311 archive = cached_archive_path
310 312
311 313 # store download action
312 314 audit_logger.store_web(
313 315 'repo.archive.download', action_data={
314 316 'user_agent': self.request.user_agent,
315 317 'archive_name': archive_name,
316 318 'archive_spec': fname,
317 319 'archive_cached': use_cached_archive},
318 320 user=self._rhodecode_user,
319 321 repo=self.db_repo,
320 322 commit=True
321 323 )
322 324
323 325 def get_chunked_archive(archive):
324 326 with open(archive, 'rb') as stream:
325 327 while True:
326 328 data = stream.read(16 * 1024)
327 329 if not data:
328 330 if fd: # fd means we used temporary file
329 331 os.close(fd)
330 332 if not archive_cache_enabled:
331 333 log.debug('Destroying temp archive %s', archive)
332 334 os.remove(archive)
333 335 break
334 336 yield data
335 337
336 338 response = Response(app_iter=get_chunked_archive(archive))
337 339 response.content_disposition = str(
338 340 'attachment; filename=%s' % archive_name)
339 341 response.content_type = str(content_type)
340 342
341 343 return response
342 344
343 345 def _get_file_node(self, commit_id, f_path):
344 346 if commit_id not in ['', None, 'None', '0' * 12, '0' * 40]:
345 347 commit = self.rhodecode_vcs_repo.get_commit(commit_id=commit_id)
346 348 try:
347 349 node = commit.get_node(f_path)
348 350 if node.is_dir():
349 351 raise NodeError('%s path is a %s not a file'
350 352 % (node, type(node)))
351 353 except NodeDoesNotExistError:
352 354 commit = EmptyCommit(
353 355 commit_id=commit_id,
354 356 idx=commit.idx,
355 357 repo=commit.repository,
356 358 alias=commit.repository.alias,
357 359 message=commit.message,
358 360 author=commit.author,
359 361 date=commit.date)
360 362 node = FileNode(f_path, '', commit=commit)
361 363 else:
362 364 commit = EmptyCommit(
363 365 repo=self.rhodecode_vcs_repo,
364 366 alias=self.rhodecode_vcs_repo.alias)
365 367 node = FileNode(f_path, '', commit=commit)
366 368 return node
367 369
368 370 @LoginRequired()
369 371 @HasRepoPermissionAnyDecorator(
370 372 'repository.read', 'repository.write', 'repository.admin')
371 373 @view_config(
372 374 route_name='repo_files_diff', request_method='GET',
373 375 renderer=None)
374 376 def repo_files_diff(self):
375 377 c = self.load_default_context()
376 378 f_path = self._get_f_path(self.request.matchdict)
377 379 diff1 = self.request.GET.get('diff1', '')
378 380 diff2 = self.request.GET.get('diff2', '')
379 381
380 382 path1, diff1 = parse_path_ref(diff1, default_path=f_path)
381 383
382 384 ignore_whitespace = str2bool(self.request.GET.get('ignorews'))
383 385 line_context = self.request.GET.get('context', 3)
384 386
385 387 if not any((diff1, diff2)):
386 388 h.flash(
387 389 'Need query parameter "diff1" or "diff2" to generate a diff.',
388 390 category='error')
389 391 raise HTTPBadRequest()
390 392
391 393 c.action = self.request.GET.get('diff')
392 394 if c.action not in ['download', 'raw']:
393 395 compare_url = h.route_path(
394 396 'repo_compare',
395 397 repo_name=self.db_repo_name,
396 398 source_ref_type='rev',
397 399 source_ref=diff1,
398 400 target_repo=self.db_repo_name,
399 401 target_ref_type='rev',
400 402 target_ref=diff2,
401 403 _query=dict(f_path=f_path))
402 404 # redirect to new view if we render diff
403 405 raise HTTPFound(compare_url)
404 406
405 407 try:
406 408 node1 = self._get_file_node(diff1, path1)
407 409 node2 = self._get_file_node(diff2, f_path)
408 410 except (RepositoryError, NodeError):
409 411 log.exception("Exception while trying to get node from repository")
410 412 raise HTTPFound(
411 413 h.route_path('repo_files', repo_name=self.db_repo_name,
412 414 commit_id='tip', f_path=f_path))
413 415
414 416 if all(isinstance(node.commit, EmptyCommit)
415 417 for node in (node1, node2)):
416 418 raise HTTPNotFound()
417 419
418 420 c.commit_1 = node1.commit
419 421 c.commit_2 = node2.commit
420 422
421 423 if c.action == 'download':
422 424 _diff = diffs.get_gitdiff(node1, node2,
423 425 ignore_whitespace=ignore_whitespace,
424 426 context=line_context)
425 427 diff = diffs.DiffProcessor(_diff, format='gitdiff')
426 428
427 429 response = Response(diff.as_raw())
428 430 response.content_type = 'text/plain'
429 431 response.content_disposition = (
430 432 'attachment; filename=%s_%s_vs_%s.diff' % (f_path, diff1, diff2)
431 433 )
432 434 charset = self._get_default_encoding(c)
433 435 if charset:
434 436 response.charset = charset
435 437 return response
436 438
437 439 elif c.action == 'raw':
438 440 _diff = diffs.get_gitdiff(node1, node2,
439 441 ignore_whitespace=ignore_whitespace,
440 442 context=line_context)
441 443 diff = diffs.DiffProcessor(_diff, format='gitdiff')
442 444
443 445 response = Response(diff.as_raw())
444 446 response.content_type = 'text/plain'
445 447 charset = self._get_default_encoding(c)
446 448 if charset:
447 449 response.charset = charset
448 450 return response
449 451
450 452 # in case we ever end up here
451 453 raise HTTPNotFound()
452 454
453 455 @LoginRequired()
454 456 @HasRepoPermissionAnyDecorator(
455 457 'repository.read', 'repository.write', 'repository.admin')
456 458 @view_config(
457 459 route_name='repo_files_diff_2way_redirect', request_method='GET',
458 460 renderer=None)
459 461 def repo_files_diff_2way_redirect(self):
460 462 """
461 463 Kept only to make OLD links work
462 464 """
463 465 f_path = self._get_f_path(self.request.matchdict)
464 466 diff1 = self.request.GET.get('diff1', '')
465 467 diff2 = self.request.GET.get('diff2', '')
466 468
467 469 if not any((diff1, diff2)):
468 470 h.flash(
469 471 'Need query parameter "diff1" or "diff2" to generate a diff.',
470 472 category='error')
471 473 raise HTTPBadRequest()
472 474
473 475 compare_url = h.route_path(
474 476 'repo_compare',
475 477 repo_name=self.db_repo_name,
476 478 source_ref_type='rev',
477 479 source_ref=diff1,
478 480 target_ref_type='rev',
479 481 target_ref=diff2,
480 482 _query=dict(f_path=f_path, diffmode='sideside',
481 483 target_repo=self.db_repo_name,))
482 484 raise HTTPFound(compare_url)
483 485
484 486 @LoginRequired()
485 487 @HasRepoPermissionAnyDecorator(
486 488 'repository.read', 'repository.write', 'repository.admin')
487 489 @view_config(
488 490 route_name='repo_files', request_method='GET',
489 491 renderer=None)
490 492 @view_config(
491 493 route_name='repo_files:default_path', request_method='GET',
492 494 renderer=None)
493 495 @view_config(
494 496 route_name='repo_files:default_commit', request_method='GET',
495 497 renderer=None)
496 498 @view_config(
497 499 route_name='repo_files:rendered', request_method='GET',
498 500 renderer=None)
499 501 @view_config(
500 502 route_name='repo_files:annotated', request_method='GET',
501 503 renderer=None)
502 504 def repo_files(self):
503 505 c = self.load_default_context()
504 506
505 507 view_name = getattr(self.request.matched_route, 'name', None)
506 508
507 509 c.annotate = view_name == 'repo_files:annotated'
508 510 # default is false, but .rst/.md files later are auto rendered, we can
509 511 # overwrite auto rendering by setting this GET flag
510 512 c.renderer = view_name == 'repo_files:rendered' or \
511 513 not self.request.GET.get('no-render', False)
512 514
513 515 # redirect to given commit_id from form if given
514 516 get_commit_id = self.request.GET.get('at_rev', None)
515 517 if get_commit_id:
516 518 self._get_commit_or_redirect(get_commit_id)
517 519
518 520 commit_id, f_path = self._get_commit_and_path()
519 521 c.commit = self._get_commit_or_redirect(commit_id)
520 522 c.branch = self.request.GET.get('branch', None)
521 523 c.f_path = f_path
522 524
523 525 # prev link
524 526 try:
525 527 prev_commit = c.commit.prev(c.branch)
526 528 c.prev_commit = prev_commit
527 529 c.url_prev = h.route_path(
528 530 'repo_files', repo_name=self.db_repo_name,
529 531 commit_id=prev_commit.raw_id, f_path=f_path)
530 532 if c.branch:
531 533 c.url_prev += '?branch=%s' % c.branch
532 534 except (CommitDoesNotExistError, VCSError):
533 535 c.url_prev = '#'
534 536 c.prev_commit = EmptyCommit()
535 537
536 538 # next link
537 539 try:
538 540 next_commit = c.commit.next(c.branch)
539 541 c.next_commit = next_commit
540 542 c.url_next = h.route_path(
541 543 'repo_files', repo_name=self.db_repo_name,
542 544 commit_id=next_commit.raw_id, f_path=f_path)
543 545 if c.branch:
544 546 c.url_next += '?branch=%s' % c.branch
545 547 except (CommitDoesNotExistError, VCSError):
546 548 c.url_next = '#'
547 549 c.next_commit = EmptyCommit()
548 550
549 551 # files or dirs
550 552 try:
551 553 c.file = c.commit.get_node(f_path)
552 554 c.file_author = True
553 555 c.file_tree = ''
554 556
555 557 # load file content
556 558 if c.file.is_file():
557 559 c.lf_node = c.file.get_largefile_node()
558 560
559 561 c.file_source_page = 'true'
560 562 c.file_last_commit = c.file.last_commit
561 563 if c.file.size < c.visual.cut_off_limit_diff:
562 564 if c.annotate: # annotation has precedence over renderer
563 565 c.annotated_lines = filenode_as_annotated_lines_tokens(
564 566 c.file
565 567 )
566 568 else:
567 569 c.renderer = (
568 570 c.renderer and h.renderer_from_filename(c.file.path)
569 571 )
570 572 if not c.renderer:
571 573 c.lines = filenode_as_lines_tokens(c.file)
572 574
573 575 c.on_branch_head = self._is_valid_head(
574 576 commit_id, self.rhodecode_vcs_repo)
575 577
576 578 branch = c.commit.branch if (
577 579 c.commit.branch and '/' not in c.commit.branch) else None
578 580 c.branch_or_raw_id = branch or c.commit.raw_id
579 581 c.branch_name = c.commit.branch or h.short_id(c.commit.raw_id)
580 582
581 583 author = c.file_last_commit.author
582 584 c.authors = [[
583 585 h.email(author),
584 586 h.person(author, 'username_or_name_or_email'),
585 587 1
586 588 ]]
587 589
588 590 else: # load tree content at path
589 591 c.file_source_page = 'false'
590 592 c.authors = []
591 593 # this loads a simple tree without metadata to speed things up
592 594 # later via ajax we call repo_nodetree_full and fetch whole
593 595 c.file_tree = self._get_tree_at_commit(
594 596 c, c.commit.raw_id, f_path)
595 597
596 598 except RepositoryError as e:
597 599 h.flash(safe_str(h.escape(e)), category='error')
598 600 raise HTTPNotFound()
599 601
600 602 if self.request.environ.get('HTTP_X_PJAX'):
601 603 html = render('rhodecode:templates/files/files_pjax.mako',
602 604 self._get_template_context(c), self.request)
603 605 else:
604 606 html = render('rhodecode:templates/files/files.mako',
605 607 self._get_template_context(c), self.request)
606 608 return Response(html)
607 609
608 610 @HasRepoPermissionAnyDecorator(
609 611 'repository.read', 'repository.write', 'repository.admin')
610 612 @view_config(
611 613 route_name='repo_files:annotated_previous', request_method='GET',
612 614 renderer=None)
613 615 def repo_files_annotated_previous(self):
614 616 self.load_default_context()
615 617
616 618 commit_id, f_path = self._get_commit_and_path()
617 619 commit = self._get_commit_or_redirect(commit_id)
618 620 prev_commit_id = commit.raw_id
619 621 line_anchor = self.request.GET.get('line_anchor')
620 622 is_file = False
621 623 try:
622 624 _file = commit.get_node(f_path)
623 625 is_file = _file.is_file()
624 626 except (NodeDoesNotExistError, CommitDoesNotExistError, VCSError):
625 627 pass
626 628
627 629 if is_file:
628 630 history = commit.get_file_history(f_path)
629 631 prev_commit_id = history[1].raw_id \
630 632 if len(history) > 1 else prev_commit_id
631 633 prev_url = h.route_path(
632 634 'repo_files:annotated', repo_name=self.db_repo_name,
633 635 commit_id=prev_commit_id, f_path=f_path,
634 636 _anchor='L{}'.format(line_anchor))
635 637
636 638 raise HTTPFound(prev_url)
637 639
638 640 @LoginRequired()
639 641 @HasRepoPermissionAnyDecorator(
640 642 'repository.read', 'repository.write', 'repository.admin')
641 643 @view_config(
642 644 route_name='repo_nodetree_full', request_method='GET',
643 645 renderer=None, xhr=True)
644 646 @view_config(
645 647 route_name='repo_nodetree_full:default_path', request_method='GET',
646 648 renderer=None, xhr=True)
647 649 def repo_nodetree_full(self):
648 650 """
649 651 Returns rendered html of file tree that contains commit date,
650 652 author, commit_id for the specified combination of
651 653 repo, commit_id and file path
652 654 """
653 655 c = self.load_default_context()
654 656
655 657 commit_id, f_path = self._get_commit_and_path()
656 658 commit = self._get_commit_or_redirect(commit_id)
657 659 try:
658 660 dir_node = commit.get_node(f_path)
659 661 except RepositoryError as e:
660 return Response('error: {}'.format(safe_str(e)))
662 return Response('error: {}'.format(h.escape(safe_str(e))))
661 663
662 664 if dir_node.is_file():
663 665 return Response('')
664 666
665 667 c.file = dir_node
666 668 c.commit = commit
667 669
668 670 # using force=True here, make a little trick. We flush the cache and
669 671 # compute it using the same key as without previous full_load, so now
670 672 # the fully loaded tree is now returned instead of partial,
671 673 # and we store this in caches
672 674 html = self._get_tree_at_commit(
673 675 c, commit.raw_id, dir_node.path, full_load=True, force=True)
674 676
675 677 return Response(html)
676 678
677 679 def _get_attachement_disposition(self, f_path):
678 680 return 'attachment; filename=%s' % \
679 681 safe_str(f_path.split(Repository.NAME_SEP)[-1])
680 682
681 683 @LoginRequired()
682 684 @HasRepoPermissionAnyDecorator(
683 685 'repository.read', 'repository.write', 'repository.admin')
684 686 @view_config(
685 687 route_name='repo_file_raw', request_method='GET',
686 688 renderer=None)
687 689 def repo_file_raw(self):
688 690 """
689 691 Action for show as raw, some mimetypes are "rendered",
690 692 those include images, icons.
691 693 """
692 694 c = self.load_default_context()
693 695
694 696 commit_id, f_path = self._get_commit_and_path()
695 697 commit = self._get_commit_or_redirect(commit_id)
696 698 file_node = self._get_filenode_or_redirect(commit, f_path)
697 699
698 700 raw_mimetype_mapping = {
699 701 # map original mimetype to a mimetype used for "show as raw"
700 702 # you can also provide a content-disposition to override the
701 703 # default "attachment" disposition.
702 704 # orig_type: (new_type, new_dispo)
703 705
704 706 # show images inline:
705 707 # Do not re-add SVG: it is unsafe and permits XSS attacks. One can
706 708 # for example render an SVG with javascript inside or even render
707 709 # HTML.
708 710 'image/x-icon': ('image/x-icon', 'inline'),
709 711 'image/png': ('image/png', 'inline'),
710 712 'image/gif': ('image/gif', 'inline'),
711 713 'image/jpeg': ('image/jpeg', 'inline'),
712 714 'application/pdf': ('application/pdf', 'inline'),
713 715 }
714 716
715 717 mimetype = file_node.mimetype
716 718 try:
717 719 mimetype, disposition = raw_mimetype_mapping[mimetype]
718 720 except KeyError:
719 721 # we don't know anything special about this, handle it safely
720 722 if file_node.is_binary:
721 723 # do same as download raw for binary files
722 724 mimetype, disposition = 'application/octet-stream', 'attachment'
723 725 else:
724 726 # do not just use the original mimetype, but force text/plain,
725 727 # otherwise it would serve text/html and that might be unsafe.
726 728 # Note: underlying vcs library fakes text/plain mimetype if the
727 729 # mimetype can not be determined and it thinks it is not
728 730 # binary.This might lead to erroneous text display in some
729 731 # cases, but helps in other cases, like with text files
730 732 # without extension.
731 733 mimetype, disposition = 'text/plain', 'inline'
732 734
733 735 if disposition == 'attachment':
734 736 disposition = self._get_attachement_disposition(f_path)
735 737
736 738 def stream_node():
737 739 yield file_node.raw_bytes
738 740
739 741 response = Response(app_iter=stream_node())
740 742 response.content_disposition = disposition
741 743 response.content_type = mimetype
742 744
743 745 charset = self._get_default_encoding(c)
744 746 if charset:
745 747 response.charset = charset
746 748
747 749 return response
748 750
749 751 @LoginRequired()
750 752 @HasRepoPermissionAnyDecorator(
751 753 'repository.read', 'repository.write', 'repository.admin')
752 754 @view_config(
753 755 route_name='repo_file_download', request_method='GET',
754 756 renderer=None)
755 757 @view_config(
756 758 route_name='repo_file_download:legacy', request_method='GET',
757 759 renderer=None)
758 760 def repo_file_download(self):
759 761 c = self.load_default_context()
760 762
761 763 commit_id, f_path = self._get_commit_and_path()
762 764 commit = self._get_commit_or_redirect(commit_id)
763 765 file_node = self._get_filenode_or_redirect(commit, f_path)
764 766
765 767 if self.request.GET.get('lf'):
766 768 # only if lf get flag is passed, we download this file
767 769 # as LFS/Largefile
768 770 lf_node = file_node.get_largefile_node()
769 771 if lf_node:
770 772 # overwrite our pointer with the REAL large-file
771 773 file_node = lf_node
772 774
773 775 disposition = self._get_attachement_disposition(f_path)
774 776
775 777 def stream_node():
776 778 yield file_node.raw_bytes
777 779
778 780 response = Response(app_iter=stream_node())
779 781 response.content_disposition = disposition
780 782 response.content_type = file_node.mimetype
781 783
782 784 charset = self._get_default_encoding(c)
783 785 if charset:
784 786 response.charset = charset
785 787
786 788 return response
787 789
788 790 def _get_nodelist_at_commit(self, repo_name, commit_id, f_path):
789 791 def _cached_nodes():
790 792 log.debug('Generating cached nodelist for %s, %s, %s',
791 793 repo_name, commit_id, f_path)
792 794 try:
793 795 _d, _f = ScmModel().get_nodes(
794 796 repo_name, commit_id, f_path, flat=False)
795 797 except (RepositoryError, CommitDoesNotExistError, Exception) as e:
796 798 log.exception(safe_str(e))
797 799 h.flash(safe_str(h.escape(e)), category='error')
798 800 raise HTTPFound(h.route_path(
799 801 'repo_files', repo_name=self.db_repo_name,
800 802 commit_id='tip', f_path='/'))
801 803 return _d + _f
802 804
803 805 cache_manager = self._get_tree_cache_manager(
804 806 caches.FILE_SEARCH_TREE_META)
805 807
806 808 cache_key = caches.compute_key_from_params(
807 809 repo_name, commit_id, f_path)
808 810 return cache_manager.get(cache_key, createfunc=_cached_nodes)
809 811
810 812 @LoginRequired()
811 813 @HasRepoPermissionAnyDecorator(
812 814 'repository.read', 'repository.write', 'repository.admin')
813 815 @view_config(
814 816 route_name='repo_files_nodelist', request_method='GET',
815 817 renderer='json_ext', xhr=True)
816 818 def repo_nodelist(self):
817 819 self.load_default_context()
818 820
819 821 commit_id, f_path = self._get_commit_and_path()
820 822 commit = self._get_commit_or_redirect(commit_id)
821 823
822 824 metadata = self._get_nodelist_at_commit(
823 825 self.db_repo_name, commit.raw_id, f_path)
824 826 return {'nodes': metadata}
825 827
826 828 def _create_references(
827 829 self, branches_or_tags, symbolic_reference, f_path):
828 830 items = []
829 831 for name, commit_id in branches_or_tags.items():
830 832 sym_ref = symbolic_reference(commit_id, name, f_path)
831 833 items.append((sym_ref, name))
832 834 return items
833 835
834 836 def _symbolic_reference(self, commit_id, name, f_path):
835 837 return commit_id
836 838
837 839 def _symbolic_reference_svn(self, commit_id, name, f_path):
838 840 new_f_path = vcspath.join(name, f_path)
839 841 return u'%s@%s' % (new_f_path, commit_id)
840 842
841 843 def _get_node_history(self, commit_obj, f_path, commits=None):
842 844 """
843 845 get commit history for given node
844 846
845 847 :param commit_obj: commit to calculate history
846 848 :param f_path: path for node to calculate history for
847 849 :param commits: if passed don't calculate history and take
848 850 commits defined in this list
849 851 """
850 852 _ = self.request.translate
851 853
852 854 # calculate history based on tip
853 855 tip = self.rhodecode_vcs_repo.get_commit()
854 856 if commits is None:
855 857 pre_load = ["author", "branch"]
856 858 try:
857 859 commits = tip.get_file_history(f_path, pre_load=pre_load)
858 860 except (NodeDoesNotExistError, CommitError):
859 861 # this node is not present at tip!
860 862 commits = commit_obj.get_file_history(f_path, pre_load=pre_load)
861 863
862 864 history = []
863 865 commits_group = ([], _("Changesets"))
864 866 for commit in commits:
865 867 branch = ' (%s)' % commit.branch if commit.branch else ''
866 868 n_desc = 'r%s:%s%s' % (commit.idx, commit.short_id, branch)
867 869 commits_group[0].append((commit.raw_id, n_desc,))
868 870 history.append(commits_group)
869 871
870 872 symbolic_reference = self._symbolic_reference
871 873
872 874 if self.rhodecode_vcs_repo.alias == 'svn':
873 875 adjusted_f_path = RepoFilesView.adjust_file_path_for_svn(
874 876 f_path, self.rhodecode_vcs_repo)
875 877 if adjusted_f_path != f_path:
876 878 log.debug(
877 879 'Recognized svn tag or branch in file "%s", using svn '
878 880 'specific symbolic references', f_path)
879 881 f_path = adjusted_f_path
880 882 symbolic_reference = self._symbolic_reference_svn
881 883
882 884 branches = self._create_references(
883 885 self.rhodecode_vcs_repo.branches, symbolic_reference, f_path)
884 886 branches_group = (branches, _("Branches"))
885 887
886 888 tags = self._create_references(
887 889 self.rhodecode_vcs_repo.tags, symbolic_reference, f_path)
888 890 tags_group = (tags, _("Tags"))
889 891
890 892 history.append(branches_group)
891 893 history.append(tags_group)
892 894
893 895 return history, commits
894 896
895 897 @LoginRequired()
896 898 @HasRepoPermissionAnyDecorator(
897 899 'repository.read', 'repository.write', 'repository.admin')
898 900 @view_config(
899 901 route_name='repo_file_history', request_method='GET',
900 902 renderer='json_ext')
901 903 def repo_file_history(self):
902 904 self.load_default_context()
903 905
904 906 commit_id, f_path = self._get_commit_and_path()
905 907 commit = self._get_commit_or_redirect(commit_id)
906 908 file_node = self._get_filenode_or_redirect(commit, f_path)
907 909
908 910 if file_node.is_file():
909 911 file_history, _hist = self._get_node_history(commit, f_path)
910 912
911 913 res = []
912 914 for obj in file_history:
913 915 res.append({
914 916 'text': obj[1],
915 917 'children': [{'id': o[0], 'text': o[1]} for o in obj[0]]
916 918 })
917 919
918 920 data = {
919 921 'more': False,
920 922 'results': res
921 923 }
922 924 return data
923 925
924 926 log.warning('Cannot fetch history for directory')
925 927 raise HTTPBadRequest()
926 928
927 929 @LoginRequired()
928 930 @HasRepoPermissionAnyDecorator(
929 931 'repository.read', 'repository.write', 'repository.admin')
930 932 @view_config(
931 933 route_name='repo_file_authors', request_method='GET',
932 934 renderer='rhodecode:templates/files/file_authors_box.mako')
933 935 def repo_file_authors(self):
934 936 c = self.load_default_context()
935 937
936 938 commit_id, f_path = self._get_commit_and_path()
937 939 commit = self._get_commit_or_redirect(commit_id)
938 940 file_node = self._get_filenode_or_redirect(commit, f_path)
939 941
940 942 if not file_node.is_file():
941 943 raise HTTPBadRequest()
942 944
943 945 c.file_last_commit = file_node.last_commit
944 946 if self.request.GET.get('annotate') == '1':
945 947 # use _hist from annotation if annotation mode is on
946 948 commit_ids = set(x[1] for x in file_node.annotate)
947 949 _hist = (
948 950 self.rhodecode_vcs_repo.get_commit(commit_id)
949 951 for commit_id in commit_ids)
950 952 else:
951 953 _f_history, _hist = self._get_node_history(commit, f_path)
952 954 c.file_author = False
953 955
954 956 unique = collections.OrderedDict()
955 957 for commit in _hist:
956 958 author = commit.author
957 959 if author not in unique:
958 960 unique[commit.author] = [
959 961 h.email(author),
960 962 h.person(author, 'username_or_name_or_email'),
961 963 1 # counter
962 964 ]
963 965
964 966 else:
965 967 # increase counter
966 968 unique[commit.author][2] += 1
967 969
968 970 c.authors = [val for val in unique.values()]
969 971
970 972 return self._get_template_context(c)
971 973
972 974 @LoginRequired()
973 975 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
974 976 @view_config(
975 977 route_name='repo_files_remove_file', request_method='GET',
976 978 renderer='rhodecode:templates/files/files_delete.mako')
977 979 def repo_files_remove_file(self):
978 980 _ = self.request.translate
979 981 c = self.load_default_context()
980 982 commit_id, f_path = self._get_commit_and_path()
981 983
982 984 self._ensure_not_locked()
983 985
984 986 if not self._is_valid_head(commit_id, self.rhodecode_vcs_repo):
985 987 h.flash(_('You can only delete files with commit '
986 988 'being a valid branch '), category='warning')
987 989 raise HTTPFound(
988 990 h.route_path('repo_files',
989 991 repo_name=self.db_repo_name, commit_id='tip',
990 992 f_path=f_path))
991 993
992 994 c.commit = self._get_commit_or_redirect(commit_id)
993 995 c.file = self._get_filenode_or_redirect(c.commit, f_path)
994 996
995 997 c.default_message = _(
996 998 'Deleted file {} via RhodeCode Enterprise').format(f_path)
997 999 c.f_path = f_path
998 1000
999 1001 return self._get_template_context(c)
1000 1002
1001 1003 @LoginRequired()
1002 1004 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1003 1005 @CSRFRequired()
1004 1006 @view_config(
1005 1007 route_name='repo_files_delete_file', request_method='POST',
1006 1008 renderer=None)
1007 1009 def repo_files_delete_file(self):
1008 1010 _ = self.request.translate
1009 1011
1010 1012 c = self.load_default_context()
1011 1013 commit_id, f_path = self._get_commit_and_path()
1012 1014
1013 1015 self._ensure_not_locked()
1014 1016
1015 1017 if not self._is_valid_head(commit_id, self.rhodecode_vcs_repo):
1016 1018 h.flash(_('You can only delete files with commit '
1017 1019 'being a valid branch '), category='warning')
1018 1020 raise HTTPFound(
1019 1021 h.route_path('repo_files',
1020 1022 repo_name=self.db_repo_name, commit_id='tip',
1021 1023 f_path=f_path))
1022 1024
1023 1025 c.commit = self._get_commit_or_redirect(commit_id)
1024 1026 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1025 1027
1026 1028 c.default_message = _(
1027 1029 'Deleted file {} via RhodeCode Enterprise').format(f_path)
1028 1030 c.f_path = f_path
1029 1031 node_path = f_path
1030 1032 author = self._rhodecode_db_user.full_contact
1031 1033 message = self.request.POST.get('message') or c.default_message
1032 1034 try:
1033 1035 nodes = {
1034 1036 node_path: {
1035 1037 'content': ''
1036 1038 }
1037 1039 }
1038 1040 ScmModel().delete_nodes(
1039 1041 user=self._rhodecode_db_user.user_id, repo=self.db_repo,
1040 1042 message=message,
1041 1043 nodes=nodes,
1042 1044 parent_commit=c.commit,
1043 1045 author=author,
1044 1046 )
1045 1047
1046 1048 h.flash(
1047 1049 _('Successfully deleted file `{}`').format(
1048 1050 h.escape(f_path)), category='success')
1049 1051 except Exception:
1050 1052 log.exception('Error during commit operation')
1051 1053 h.flash(_('Error occurred during commit'), category='error')
1052 1054 raise HTTPFound(
1053 1055 h.route_path('repo_commit', repo_name=self.db_repo_name,
1054 1056 commit_id='tip'))
1055 1057
1056 1058 @LoginRequired()
1057 1059 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1058 1060 @view_config(
1059 1061 route_name='repo_files_edit_file', request_method='GET',
1060 1062 renderer='rhodecode:templates/files/files_edit.mako')
1061 1063 def repo_files_edit_file(self):
1062 1064 _ = self.request.translate
1063 1065 c = self.load_default_context()
1064 1066 commit_id, f_path = self._get_commit_and_path()
1065 1067
1066 1068 self._ensure_not_locked()
1067 1069
1068 1070 if not self._is_valid_head(commit_id, self.rhodecode_vcs_repo):
1069 1071 h.flash(_('You can only edit files with commit '
1070 1072 'being a valid branch '), category='warning')
1071 1073 raise HTTPFound(
1072 1074 h.route_path('repo_files',
1073 1075 repo_name=self.db_repo_name, commit_id='tip',
1074 1076 f_path=f_path))
1075 1077
1076 1078 c.commit = self._get_commit_or_redirect(commit_id)
1077 1079 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1078 1080
1079 1081 if c.file.is_binary:
1080 1082 files_url = h.route_path(
1081 1083 'repo_files',
1082 1084 repo_name=self.db_repo_name,
1083 1085 commit_id=c.commit.raw_id, f_path=f_path)
1084 1086 raise HTTPFound(files_url)
1085 1087
1086 1088 c.default_message = _(
1087 1089 'Edited file {} via RhodeCode Enterprise').format(f_path)
1088 1090 c.f_path = f_path
1089 1091
1090 1092 return self._get_template_context(c)
1091 1093
1092 1094 @LoginRequired()
1093 1095 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1094 1096 @CSRFRequired()
1095 1097 @view_config(
1096 1098 route_name='repo_files_update_file', request_method='POST',
1097 1099 renderer=None)
1098 1100 def repo_files_update_file(self):
1099 1101 _ = self.request.translate
1100 1102 c = self.load_default_context()
1101 1103 commit_id, f_path = self._get_commit_and_path()
1102 1104
1103 1105 self._ensure_not_locked()
1104 1106
1105 1107 if not self._is_valid_head(commit_id, self.rhodecode_vcs_repo):
1106 1108 h.flash(_('You can only edit files with commit '
1107 1109 'being a valid branch '), category='warning')
1108 1110 raise HTTPFound(
1109 1111 h.route_path('repo_files',
1110 1112 repo_name=self.db_repo_name, commit_id='tip',
1111 1113 f_path=f_path))
1112 1114
1113 1115 c.commit = self._get_commit_or_redirect(commit_id)
1114 1116 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1115 1117
1116 1118 if c.file.is_binary:
1117 1119 raise HTTPFound(
1118 1120 h.route_path('repo_files',
1119 1121 repo_name=self.db_repo_name,
1120 1122 commit_id=c.commit.raw_id,
1121 1123 f_path=f_path))
1122 1124
1123 1125 c.default_message = _(
1124 1126 'Edited file {} via RhodeCode Enterprise').format(f_path)
1125 1127 c.f_path = f_path
1126 1128 old_content = c.file.content
1127 1129 sl = old_content.splitlines(1)
1128 1130 first_line = sl[0] if sl else ''
1129 1131
1130 1132 r_post = self.request.POST
1131 1133 # modes: 0 - Unix, 1 - Mac, 2 - DOS
1132 1134 mode = detect_mode(first_line, 0)
1133 1135 content = convert_line_endings(r_post.get('content', ''), mode)
1134 1136
1135 1137 message = r_post.get('message') or c.default_message
1136 1138 org_f_path = c.file.unicode_path
1137 1139 filename = r_post['filename']
1138 1140 org_filename = c.file.name
1139 1141
1140 1142 if content == old_content and filename == org_filename:
1141 1143 h.flash(_('No changes'), category='warning')
1142 1144 raise HTTPFound(
1143 1145 h.route_path('repo_commit', repo_name=self.db_repo_name,
1144 1146 commit_id='tip'))
1145 1147 try:
1146 1148 mapping = {
1147 1149 org_f_path: {
1148 1150 'org_filename': org_f_path,
1149 1151 'filename': os.path.join(c.file.dir_path, filename),
1150 1152 'content': content,
1151 1153 'lexer': '',
1152 1154 'op': 'mod',
1153 1155 }
1154 1156 }
1155 1157
1156 1158 ScmModel().update_nodes(
1157 1159 user=self._rhodecode_db_user.user_id,
1158 1160 repo=self.db_repo,
1159 1161 message=message,
1160 1162 nodes=mapping,
1161 1163 parent_commit=c.commit,
1162 1164 )
1163 1165
1164 1166 h.flash(
1165 1167 _('Successfully committed changes to file `{}`').format(
1166 1168 h.escape(f_path)), category='success')
1167 1169 except Exception:
1168 1170 log.exception('Error occurred during commit')
1169 1171 h.flash(_('Error occurred during commit'), category='error')
1170 1172 raise HTTPFound(
1171 1173 h.route_path('repo_commit', repo_name=self.db_repo_name,
1172 1174 commit_id='tip'))
1173 1175
1174 1176 @LoginRequired()
1175 1177 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1176 1178 @view_config(
1177 1179 route_name='repo_files_add_file', request_method='GET',
1178 1180 renderer='rhodecode:templates/files/files_add.mako')
1179 1181 def repo_files_add_file(self):
1180 1182 _ = self.request.translate
1181 1183 c = self.load_default_context()
1182 1184 commit_id, f_path = self._get_commit_and_path()
1183 1185
1184 1186 self._ensure_not_locked()
1185 1187
1186 1188 c.commit = self._get_commit_or_redirect(commit_id, redirect_after=False)
1187 1189 if c.commit is None:
1188 1190 c.commit = EmptyCommit(alias=self.rhodecode_vcs_repo.alias)
1189 1191 c.default_message = (_('Added file via RhodeCode Enterprise'))
1190 1192 c.f_path = f_path.lstrip('/') # ensure not relative path
1191 1193
1192 1194 return self._get_template_context(c)
1193 1195
1194 1196 @LoginRequired()
1195 1197 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1196 1198 @CSRFRequired()
1197 1199 @view_config(
1198 1200 route_name='repo_files_create_file', request_method='POST',
1199 1201 renderer=None)
1200 1202 def repo_files_create_file(self):
1201 1203 _ = self.request.translate
1202 1204 c = self.load_default_context()
1203 1205 commit_id, f_path = self._get_commit_and_path()
1204 1206
1205 1207 self._ensure_not_locked()
1206 1208
1207 1209 r_post = self.request.POST
1208 1210
1209 1211 c.commit = self._get_commit_or_redirect(
1210 1212 commit_id, redirect_after=False)
1211 1213 if c.commit is None:
1212 1214 c.commit = EmptyCommit(alias=self.rhodecode_vcs_repo.alias)
1213 1215 c.default_message = (_('Added file via RhodeCode Enterprise'))
1214 1216 c.f_path = f_path
1215 1217 unix_mode = 0
1216 1218 content = convert_line_endings(r_post.get('content', ''), unix_mode)
1217 1219
1218 1220 message = r_post.get('message') or c.default_message
1219 1221 filename = r_post.get('filename')
1220 1222 location = r_post.get('location', '') # dir location
1221 1223 file_obj = r_post.get('upload_file', None)
1222 1224
1223 1225 if file_obj is not None and hasattr(file_obj, 'filename'):
1224 1226 filename = r_post.get('filename_upload')
1225 1227 content = file_obj.file
1226 1228
1227 1229 if hasattr(content, 'file'):
1228 1230 # non posix systems store real file under file attr
1229 1231 content = content.file
1230 1232
1231 1233 if self.rhodecode_vcs_repo.is_empty:
1232 1234 default_redirect_url = h.route_path(
1233 1235 'repo_summary', repo_name=self.db_repo_name)
1234 1236 else:
1235 1237 default_redirect_url = h.route_path(
1236 1238 'repo_commit', repo_name=self.db_repo_name, commit_id='tip')
1237 1239
1238 1240 # If there's no commit, redirect to repo summary
1239 1241 if type(c.commit) is EmptyCommit:
1240 1242 redirect_url = h.route_path(
1241 1243 'repo_summary', repo_name=self.db_repo_name)
1242 1244 else:
1243 1245 redirect_url = default_redirect_url
1244 1246
1245 1247 if not filename:
1246 1248 h.flash(_('No filename'), category='warning')
1247 1249 raise HTTPFound(redirect_url)
1248 1250
1249 1251 # extract the location from filename,
1250 1252 # allows using foo/bar.txt syntax to create subdirectories
1251 1253 subdir_loc = filename.rsplit('/', 1)
1252 1254 if len(subdir_loc) == 2:
1253 1255 location = os.path.join(location, subdir_loc[0])
1254 1256
1255 1257 # strip all crap out of file, just leave the basename
1256 1258 filename = os.path.basename(filename)
1257 1259 node_path = os.path.join(location, filename)
1258 1260 author = self._rhodecode_db_user.full_contact
1259 1261
1260 1262 try:
1261 1263 nodes = {
1262 1264 node_path: {
1263 1265 'content': content
1264 1266 }
1265 1267 }
1266 1268 ScmModel().create_nodes(
1267 1269 user=self._rhodecode_db_user.user_id,
1268 1270 repo=self.db_repo,
1269 1271 message=message,
1270 1272 nodes=nodes,
1271 1273 parent_commit=c.commit,
1272 1274 author=author,
1273 1275 )
1274 1276
1275 1277 h.flash(
1276 1278 _('Successfully committed new file `{}`').format(
1277 1279 h.escape(node_path)), category='success')
1278 1280 except NonRelativePathError:
1279 1281 log.exception('Non Relative path found')
1280 1282 h.flash(_(
1281 1283 'The location specified must be a relative path and must not '
1282 1284 'contain .. in the path'), category='warning')
1283 1285 raise HTTPFound(default_redirect_url)
1284 1286 except (NodeError, NodeAlreadyExistsError) as e:
1285 1287 h.flash(_(h.escape(e)), category='error')
1286 1288 except Exception:
1287 1289 log.exception('Error occurred during commit')
1288 1290 h.flash(_('Error occurred during commit'), category='error')
1289 1291
1290 1292 raise HTTPFound(default_redirect_url)
General Comments 0
You need to be logged in to leave comments. Login now