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