##// END OF EJS Templates
files: added updated new repo instructions.
marcink -
r3775:78fc7cee new-ui
parent child Browse files
Show More
@@ -1,1528 +1,1530 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2011-2019 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import itertools
22 22 import logging
23 23 import os
24 24 import shutil
25 25 import tempfile
26 26 import collections
27 27 import urllib
28 28 import pathlib2
29 29
30 30 from pyramid.httpexceptions import HTTPNotFound, HTTPBadRequest, HTTPFound
31 31 from pyramid.view import view_config
32 32 from pyramid.renderers import render
33 33 from pyramid.response import Response
34 34
35 35 import rhodecode
36 36 from rhodecode.apps._base import RepoAppView
37 37
38 38
39 39 from rhodecode.lib import diffs, helpers as h, rc_cache
40 40 from rhodecode.lib import audit_logger
41 41 from rhodecode.lib.view_utils import parse_path_ref
42 42 from rhodecode.lib.exceptions import NonRelativePathError
43 43 from rhodecode.lib.codeblocks import (
44 44 filenode_as_lines_tokens, filenode_as_annotated_lines_tokens)
45 45 from rhodecode.lib.utils2 import (
46 46 convert_line_endings, detect_mode, safe_str, str2bool, safe_int, sha1, safe_unicode)
47 47 from rhodecode.lib.auth import (
48 48 LoginRequired, HasRepoPermissionAnyDecorator, CSRFRequired)
49 49 from rhodecode.lib.vcs import path as vcspath
50 50 from rhodecode.lib.vcs.backends.base import EmptyCommit
51 51 from rhodecode.lib.vcs.conf import settings
52 52 from rhodecode.lib.vcs.nodes import FileNode
53 53 from rhodecode.lib.vcs.exceptions import (
54 54 RepositoryError, CommitDoesNotExistError, EmptyRepositoryError,
55 55 ImproperArchiveTypeError, VCSError, NodeAlreadyExistsError,
56 56 NodeDoesNotExistError, CommitError, NodeError)
57 57
58 58 from rhodecode.model.scm import ScmModel
59 59 from rhodecode.model.db import Repository
60 60
61 61 log = logging.getLogger(__name__)
62 62
63 63
64 64 class RepoFilesView(RepoAppView):
65 65
66 66 @staticmethod
67 67 def adjust_file_path_for_svn(f_path, repo):
68 68 """
69 69 Computes the relative path of `f_path`.
70 70
71 71 This is mainly based on prefix matching of the recognized tags and
72 72 branches in the underlying repository.
73 73 """
74 74 tags_and_branches = itertools.chain(
75 75 repo.branches.iterkeys(),
76 76 repo.tags.iterkeys())
77 77 tags_and_branches = sorted(tags_and_branches, key=len, reverse=True)
78 78
79 79 for name in tags_and_branches:
80 80 if f_path.startswith('{}/'.format(name)):
81 81 f_path = vcspath.relpath(f_path, name)
82 82 break
83 83 return f_path
84 84
85 85 def load_default_context(self):
86 86 c = self._get_local_tmpl_context(include_app_defaults=True)
87 87 c.rhodecode_repo = self.rhodecode_vcs_repo
88 88 c.enable_downloads = self.db_repo.enable_downloads
89 89 return c
90 90
91 91 def _ensure_not_locked(self, commit_id='tip'):
92 92 _ = self.request.translate
93 93
94 94 repo = self.db_repo
95 95 if repo.enable_locking and repo.locked[0]:
96 96 h.flash(_('This repository has been locked by %s on %s')
97 97 % (h.person_by_id(repo.locked[0]),
98 98 h.format_date(h.time_to_datetime(repo.locked[1]))),
99 99 'warning')
100 100 files_url = h.route_path(
101 101 'repo_files:default_path',
102 102 repo_name=self.db_repo_name, commit_id=commit_id)
103 103 raise HTTPFound(files_url)
104 104
105 105 def forbid_non_head(self, is_head, f_path, commit_id='tip', json_mode=False):
106 106 _ = self.request.translate
107 107
108 108 if not is_head:
109 message = _('You can only modify files with commit being a valid branch head.')
109 message = _('Cannot modify file. '
110 'Given commit `{}` is not head of a branch.').format(commit_id)
110 111 h.flash(message, category='warning')
111 112
112 113 if json_mode:
113 114 return message
114 115
115 116 files_url = h.route_path(
116 117 'repo_files', repo_name=self.db_repo_name, commit_id=commit_id,
117 118 f_path=f_path)
118 119 raise HTTPFound(files_url)
119 120
120 121 def check_branch_permission(self, branch_name, commit_id='tip', json_mode=False):
121 122 _ = self.request.translate
122 123
123 124 rule, branch_perm = self._rhodecode_user.get_rule_and_branch_permission(
124 125 self.db_repo_name, branch_name)
125 126 if branch_perm and branch_perm not in ['branch.push', 'branch.push_force']:
126 127 message = _('Branch `{}` changes forbidden by rule {}.').format(
127 128 branch_name, rule)
128 129 h.flash(message, 'warning')
129 130
130 131 if json_mode:
131 132 return message
132 133
133 134 files_url = h.route_path(
134 135 'repo_files:default_path', repo_name=self.db_repo_name, commit_id=commit_id)
135 136
136 137 raise HTTPFound(files_url)
137 138
138 139 def _get_commit_and_path(self):
139 140 default_commit_id = self.db_repo.landing_rev[1]
140 141 default_f_path = '/'
141 142
142 143 commit_id = self.request.matchdict.get(
143 144 'commit_id', default_commit_id)
144 145 f_path = self._get_f_path(self.request.matchdict, default_f_path)
145 146 return commit_id, f_path
146 147
147 148 def _get_default_encoding(self, c):
148 149 enc_list = getattr(c, 'default_encodings', [])
149 150 return enc_list[0] if enc_list else 'UTF-8'
150 151
151 152 def _get_commit_or_redirect(self, commit_id, redirect_after=True):
152 153 """
153 154 This is a safe way to get commit. If an error occurs it redirects to
154 155 tip with proper message
155 156
156 157 :param commit_id: id of commit to fetch
157 158 :param redirect_after: toggle redirection
158 159 """
159 160 _ = self.request.translate
160 161
161 162 try:
162 163 return self.rhodecode_vcs_repo.get_commit(commit_id)
163 164 except EmptyRepositoryError:
164 165 if not redirect_after:
165 166 return None
166 167
167 168 _url = h.route_path(
168 169 'repo_files_add_file',
169 170 repo_name=self.db_repo_name, commit_id=0, f_path='')
170 171
171 172 if h.HasRepoPermissionAny(
172 173 'repository.write', 'repository.admin')(self.db_repo_name):
173 174 add_new = h.link_to(
174 175 _('Click here to add a new file.'), _url, class_="alert-link")
175 176 else:
176 177 add_new = ""
177 178
178 179 h.flash(h.literal(
179 180 _('There are no files yet. %s') % add_new), category='warning')
180 181 raise HTTPFound(
181 182 h.route_path('repo_summary', repo_name=self.db_repo_name))
182 183
183 184 except (CommitDoesNotExistError, LookupError):
184 185 msg = _('No such commit exists for this repository')
185 186 h.flash(msg, category='error')
186 187 raise HTTPNotFound()
187 188 except RepositoryError as e:
188 189 h.flash(safe_str(h.escape(e)), category='error')
189 190 raise HTTPNotFound()
190 191
191 192 def _get_filenode_or_redirect(self, commit_obj, path):
192 193 """
193 194 Returns file_node, if error occurs or given path is directory,
194 195 it'll redirect to top level path
195 196 """
196 197 _ = self.request.translate
197 198
198 199 try:
199 200 file_node = commit_obj.get_node(path)
200 201 if file_node.is_dir():
201 202 raise RepositoryError('The given path is a directory')
202 203 except CommitDoesNotExistError:
203 204 log.exception('No such commit exists for this repository')
204 205 h.flash(_('No such commit exists for this repository'), category='error')
205 206 raise HTTPNotFound()
206 207 except RepositoryError as e:
207 208 log.warning('Repository error while fetching '
208 209 'filenode `%s`. Err:%s', path, e)
209 210 h.flash(safe_str(h.escape(e)), category='error')
210 211 raise HTTPNotFound()
211 212
212 213 return file_node
213 214
214 215 def _is_valid_head(self, commit_id, repo):
215 216 branch_name = sha_commit_id = ''
216 217 is_head = False
218 log.debug('Checking if commit_id %s is a head.', commit_id)
217 219
218 220 if h.is_svn(repo) and not repo.is_empty():
219 221 # Note: Subversion only has one head.
220 222 if commit_id == repo.get_commit(commit_idx=-1).raw_id:
221 223 is_head = True
222 224 return branch_name, sha_commit_id, is_head
223 225
224 226 for _branch_name, branch_commit_id in repo.branches.items():
225 227 # simple case we pass in branch name, it's a HEAD
226 228 if commit_id == _branch_name:
227 229 is_head = True
228 230 branch_name = _branch_name
229 231 sha_commit_id = branch_commit_id
230 232 break
231 233 # case when we pass in full sha commit_id, which is a head
232 234 elif commit_id == branch_commit_id:
233 235 is_head = True
234 236 branch_name = _branch_name
235 237 sha_commit_id = branch_commit_id
236 238 break
237 239
238 240 # checked branches, means we only need to try to get the branch/commit_sha
239 241 if not repo.is_empty():
240 242 commit = repo.get_commit(commit_id=commit_id)
241 243 if commit:
242 244 branch_name = commit.branch
243 245 sha_commit_id = commit.raw_id
244 246
245 247 return branch_name, sha_commit_id, is_head
246 248
247 249 def _get_tree_at_commit(self, c, commit_id, f_path, full_load=False):
248 250
249 251 repo_id = self.db_repo.repo_id
250 252 force_recache = self.get_recache_flag()
251 253
252 254 cache_seconds = safe_int(
253 255 rhodecode.CONFIG.get('rc_cache.cache_repo.expiration_time'))
254 256 cache_on = not force_recache and cache_seconds > 0
255 257 log.debug(
256 258 'Computing FILE TREE for repo_id %s commit_id `%s` and path `%s`'
257 259 'with caching: %s[TTL: %ss]' % (
258 260 repo_id, commit_id, f_path, cache_on, cache_seconds or 0))
259 261
260 262 cache_namespace_uid = 'cache_repo.{}'.format(repo_id)
261 263 region = rc_cache.get_or_create_region('cache_repo', cache_namespace_uid)
262 264
263 265 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid,
264 266 condition=cache_on)
265 267 def compute_file_tree(ver, repo_id, commit_id, f_path, full_load):
266 268 log.debug('Generating cached file tree at ver:%s for repo_id: %s, %s, %s',
267 269 ver, repo_id, commit_id, f_path)
268 270
269 271 c.full_load = full_load
270 272 return render(
271 273 'rhodecode:templates/files/files_browser_tree.mako',
272 274 self._get_template_context(c), self.request)
273 275
274 276 return compute_file_tree('v1', self.db_repo.repo_id, commit_id, f_path, full_load)
275 277
276 278 def _get_archive_spec(self, fname):
277 279 log.debug('Detecting archive spec for: `%s`', fname)
278 280
279 281 fileformat = None
280 282 ext = None
281 283 content_type = None
282 284 for a_type, content_type, extension in settings.ARCHIVE_SPECS:
283 285
284 286 if fname.endswith(extension):
285 287 fileformat = a_type
286 288 log.debug('archive is of type: %s', fileformat)
287 289 ext = extension
288 290 break
289 291
290 292 if not fileformat:
291 293 raise ValueError()
292 294
293 295 # left over part of whole fname is the commit
294 296 commit_id = fname[:-len(ext)]
295 297
296 298 return commit_id, ext, fileformat, content_type
297 299
298 300 def create_pure_path(self, *parts):
299 301 # Split paths and sanitize them, removing any ../ etc
300 302 sanitized_path = [
301 303 x for x in pathlib2.PurePath(*parts).parts
302 304 if x not in ['.', '..']]
303 305
304 306 pure_path = pathlib2.PurePath(*sanitized_path)
305 307 return pure_path
306 308
307 309 @LoginRequired()
308 310 @HasRepoPermissionAnyDecorator(
309 311 'repository.read', 'repository.write', 'repository.admin')
310 312 @view_config(
311 313 route_name='repo_archivefile', request_method='GET',
312 314 renderer=None)
313 315 def repo_archivefile(self):
314 316 # archive cache config
315 317 from rhodecode import CONFIG
316 318 _ = self.request.translate
317 319 self.load_default_context()
318 320 default_at_path = '/'
319 321 fname = self.request.matchdict['fname']
320 322 subrepos = self.request.GET.get('subrepos') == 'true'
321 323 at_path = self.request.GET.get('at_path') or default_at_path
322 324
323 325 if not self.db_repo.enable_downloads:
324 326 return Response(_('Downloads disabled'))
325 327
326 328 try:
327 329 commit_id, ext, fileformat, content_type = \
328 330 self._get_archive_spec(fname)
329 331 except ValueError:
330 332 return Response(_('Unknown archive type for: `{}`').format(
331 333 h.escape(fname)))
332 334
333 335 try:
334 336 commit = self.rhodecode_vcs_repo.get_commit(commit_id)
335 337 except CommitDoesNotExistError:
336 338 return Response(_('Unknown commit_id {}').format(
337 339 h.escape(commit_id)))
338 340 except EmptyRepositoryError:
339 341 return Response(_('Empty repository'))
340 342
341 343 try:
342 344 at_path = commit.get_node(at_path).path or default_at_path
343 345 except Exception:
344 346 return Response(_('No node at path {} for this repository').format(at_path))
345 347
346 348 path_sha = sha1(at_path)[:8]
347 349
348 350 # original backward compat name of archive
349 351 clean_name = safe_str(self.db_repo_name.replace('/', '_'))
350 352 short_sha = safe_str(commit.short_id)
351 353
352 354 if at_path == default_at_path:
353 355 archive_name = '{}-{}{}{}'.format(
354 356 clean_name,
355 357 '-sub' if subrepos else '',
356 358 short_sha,
357 359 ext)
358 360 # custom path and new name
359 361 else:
360 362 archive_name = '{}-{}{}-{}{}'.format(
361 363 clean_name,
362 364 '-sub' if subrepos else '',
363 365 short_sha,
364 366 path_sha,
365 367 ext)
366 368
367 369 use_cached_archive = False
368 370 archive_cache_enabled = CONFIG.get(
369 371 'archive_cache_dir') and not self.request.GET.get('no_cache')
370 372 cached_archive_path = None
371 373
372 374 if archive_cache_enabled:
373 375 # check if we it's ok to write
374 376 if not os.path.isdir(CONFIG['archive_cache_dir']):
375 377 os.makedirs(CONFIG['archive_cache_dir'])
376 378 cached_archive_path = os.path.join(
377 379 CONFIG['archive_cache_dir'], archive_name)
378 380 if os.path.isfile(cached_archive_path):
379 381 log.debug('Found cached archive in %s', cached_archive_path)
380 382 fd, archive = None, cached_archive_path
381 383 use_cached_archive = True
382 384 else:
383 385 log.debug('Archive %s is not yet cached', archive_name)
384 386
385 387 if not use_cached_archive:
386 388 # generate new archive
387 389 fd, archive = tempfile.mkstemp()
388 390 log.debug('Creating new temp archive in %s', archive)
389 391 try:
390 392 commit.archive_repo(archive, kind=fileformat, subrepos=subrepos,
391 393 archive_at_path=at_path)
392 394 except ImproperArchiveTypeError:
393 395 return _('Unknown archive type')
394 396 if archive_cache_enabled:
395 397 # if we generated the archive and we have cache enabled
396 398 # let's use this for future
397 399 log.debug('Storing new archive in %s', cached_archive_path)
398 400 shutil.move(archive, cached_archive_path)
399 401 archive = cached_archive_path
400 402
401 403 # store download action
402 404 audit_logger.store_web(
403 405 'repo.archive.download', action_data={
404 406 'user_agent': self.request.user_agent,
405 407 'archive_name': archive_name,
406 408 'archive_spec': fname,
407 409 'archive_cached': use_cached_archive},
408 410 user=self._rhodecode_user,
409 411 repo=self.db_repo,
410 412 commit=True
411 413 )
412 414
413 415 def get_chunked_archive(archive_path):
414 416 with open(archive_path, 'rb') as stream:
415 417 while True:
416 418 data = stream.read(16 * 1024)
417 419 if not data:
418 420 if fd: # fd means we used temporary file
419 421 os.close(fd)
420 422 if not archive_cache_enabled:
421 423 log.debug('Destroying temp archive %s', archive_path)
422 424 os.remove(archive_path)
423 425 break
424 426 yield data
425 427
426 428 response = Response(app_iter=get_chunked_archive(archive))
427 429 response.content_disposition = str(
428 430 'attachment; filename=%s' % archive_name)
429 431 response.content_type = str(content_type)
430 432
431 433 return response
432 434
433 435 def _get_file_node(self, commit_id, f_path):
434 436 if commit_id not in ['', None, 'None', '0' * 12, '0' * 40]:
435 437 commit = self.rhodecode_vcs_repo.get_commit(commit_id=commit_id)
436 438 try:
437 439 node = commit.get_node(f_path)
438 440 if node.is_dir():
439 441 raise NodeError('%s path is a %s not a file'
440 442 % (node, type(node)))
441 443 except NodeDoesNotExistError:
442 444 commit = EmptyCommit(
443 445 commit_id=commit_id,
444 446 idx=commit.idx,
445 447 repo=commit.repository,
446 448 alias=commit.repository.alias,
447 449 message=commit.message,
448 450 author=commit.author,
449 451 date=commit.date)
450 452 node = FileNode(f_path, '', commit=commit)
451 453 else:
452 454 commit = EmptyCommit(
453 455 repo=self.rhodecode_vcs_repo,
454 456 alias=self.rhodecode_vcs_repo.alias)
455 457 node = FileNode(f_path, '', commit=commit)
456 458 return node
457 459
458 460 @LoginRequired()
459 461 @HasRepoPermissionAnyDecorator(
460 462 'repository.read', 'repository.write', 'repository.admin')
461 463 @view_config(
462 464 route_name='repo_files_diff', request_method='GET',
463 465 renderer=None)
464 466 def repo_files_diff(self):
465 467 c = self.load_default_context()
466 468 f_path = self._get_f_path(self.request.matchdict)
467 469 diff1 = self.request.GET.get('diff1', '')
468 470 diff2 = self.request.GET.get('diff2', '')
469 471
470 472 path1, diff1 = parse_path_ref(diff1, default_path=f_path)
471 473
472 474 ignore_whitespace = str2bool(self.request.GET.get('ignorews'))
473 475 line_context = self.request.GET.get('context', 3)
474 476
475 477 if not any((diff1, diff2)):
476 478 h.flash(
477 479 'Need query parameter "diff1" or "diff2" to generate a diff.',
478 480 category='error')
479 481 raise HTTPBadRequest()
480 482
481 483 c.action = self.request.GET.get('diff')
482 484 if c.action not in ['download', 'raw']:
483 485 compare_url = h.route_path(
484 486 'repo_compare',
485 487 repo_name=self.db_repo_name,
486 488 source_ref_type='rev',
487 489 source_ref=diff1,
488 490 target_repo=self.db_repo_name,
489 491 target_ref_type='rev',
490 492 target_ref=diff2,
491 493 _query=dict(f_path=f_path))
492 494 # redirect to new view if we render diff
493 495 raise HTTPFound(compare_url)
494 496
495 497 try:
496 498 node1 = self._get_file_node(diff1, path1)
497 499 node2 = self._get_file_node(diff2, f_path)
498 500 except (RepositoryError, NodeError):
499 501 log.exception("Exception while trying to get node from repository")
500 502 raise HTTPFound(
501 503 h.route_path('repo_files', repo_name=self.db_repo_name,
502 504 commit_id='tip', f_path=f_path))
503 505
504 506 if all(isinstance(node.commit, EmptyCommit)
505 507 for node in (node1, node2)):
506 508 raise HTTPNotFound()
507 509
508 510 c.commit_1 = node1.commit
509 511 c.commit_2 = node2.commit
510 512
511 513 if c.action == 'download':
512 514 _diff = diffs.get_gitdiff(node1, node2,
513 515 ignore_whitespace=ignore_whitespace,
514 516 context=line_context)
515 517 diff = diffs.DiffProcessor(_diff, format='gitdiff')
516 518
517 519 response = Response(self.path_filter.get_raw_patch(diff))
518 520 response.content_type = 'text/plain'
519 521 response.content_disposition = (
520 522 'attachment; filename=%s_%s_vs_%s.diff' % (f_path, diff1, diff2)
521 523 )
522 524 charset = self._get_default_encoding(c)
523 525 if charset:
524 526 response.charset = charset
525 527 return response
526 528
527 529 elif c.action == 'raw':
528 530 _diff = diffs.get_gitdiff(node1, node2,
529 531 ignore_whitespace=ignore_whitespace,
530 532 context=line_context)
531 533 diff = diffs.DiffProcessor(_diff, format='gitdiff')
532 534
533 535 response = Response(self.path_filter.get_raw_patch(diff))
534 536 response.content_type = 'text/plain'
535 537 charset = self._get_default_encoding(c)
536 538 if charset:
537 539 response.charset = charset
538 540 return response
539 541
540 542 # in case we ever end up here
541 543 raise HTTPNotFound()
542 544
543 545 @LoginRequired()
544 546 @HasRepoPermissionAnyDecorator(
545 547 'repository.read', 'repository.write', 'repository.admin')
546 548 @view_config(
547 549 route_name='repo_files_diff_2way_redirect', request_method='GET',
548 550 renderer=None)
549 551 def repo_files_diff_2way_redirect(self):
550 552 """
551 553 Kept only to make OLD links work
552 554 """
553 555 f_path = self._get_f_path_unchecked(self.request.matchdict)
554 556 diff1 = self.request.GET.get('diff1', '')
555 557 diff2 = self.request.GET.get('diff2', '')
556 558
557 559 if not any((diff1, diff2)):
558 560 h.flash(
559 561 'Need query parameter "diff1" or "diff2" to generate a diff.',
560 562 category='error')
561 563 raise HTTPBadRequest()
562 564
563 565 compare_url = h.route_path(
564 566 'repo_compare',
565 567 repo_name=self.db_repo_name,
566 568 source_ref_type='rev',
567 569 source_ref=diff1,
568 570 target_ref_type='rev',
569 571 target_ref=diff2,
570 572 _query=dict(f_path=f_path, diffmode='sideside',
571 573 target_repo=self.db_repo_name,))
572 574 raise HTTPFound(compare_url)
573 575
574 576 @LoginRequired()
575 577 @HasRepoPermissionAnyDecorator(
576 578 'repository.read', 'repository.write', 'repository.admin')
577 579 @view_config(
578 580 route_name='repo_files', request_method='GET',
579 581 renderer=None)
580 582 @view_config(
581 583 route_name='repo_files:default_path', request_method='GET',
582 584 renderer=None)
583 585 @view_config(
584 586 route_name='repo_files:default_commit', request_method='GET',
585 587 renderer=None)
586 588 @view_config(
587 589 route_name='repo_files:rendered', request_method='GET',
588 590 renderer=None)
589 591 @view_config(
590 592 route_name='repo_files:annotated', request_method='GET',
591 593 renderer=None)
592 594 def repo_files(self):
593 595 c = self.load_default_context()
594 596
595 597 view_name = getattr(self.request.matched_route, 'name', None)
596 598
597 599 c.annotate = view_name == 'repo_files:annotated'
598 600 # default is false, but .rst/.md files later are auto rendered, we can
599 601 # overwrite auto rendering by setting this GET flag
600 602 c.renderer = view_name == 'repo_files:rendered' or \
601 603 not self.request.GET.get('no-render', False)
602 604
603 605 # redirect to given commit_id from form if given
604 606 get_commit_id = self.request.GET.get('at_rev', None)
605 607 if get_commit_id:
606 608 self._get_commit_or_redirect(get_commit_id)
607 609
608 610 commit_id, f_path = self._get_commit_and_path()
609 611 c.commit = self._get_commit_or_redirect(commit_id)
610 612 c.branch = self.request.GET.get('branch', None)
611 613 c.f_path = f_path
612 614
613 615 # prev link
614 616 try:
615 617 prev_commit = c.commit.prev(c.branch)
616 618 c.prev_commit = prev_commit
617 619 c.url_prev = h.route_path(
618 620 'repo_files', repo_name=self.db_repo_name,
619 621 commit_id=prev_commit.raw_id, f_path=f_path)
620 622 if c.branch:
621 623 c.url_prev += '?branch=%s' % c.branch
622 624 except (CommitDoesNotExistError, VCSError):
623 625 c.url_prev = '#'
624 626 c.prev_commit = EmptyCommit()
625 627
626 628 # next link
627 629 try:
628 630 next_commit = c.commit.next(c.branch)
629 631 c.next_commit = next_commit
630 632 c.url_next = h.route_path(
631 633 'repo_files', repo_name=self.db_repo_name,
632 634 commit_id=next_commit.raw_id, f_path=f_path)
633 635 if c.branch:
634 636 c.url_next += '?branch=%s' % c.branch
635 637 except (CommitDoesNotExistError, VCSError):
636 638 c.url_next = '#'
637 639 c.next_commit = EmptyCommit()
638 640
639 641 # files or dirs
640 642 try:
641 643 c.file = c.commit.get_node(f_path)
642 644 c.file_author = True
643 645 c.file_tree = ''
644 646
645 647 # load file content
646 648 if c.file.is_file():
647 649 c.lf_node = c.file.get_largefile_node()
648 650
649 651 c.file_source_page = 'true'
650 652 c.file_last_commit = c.file.last_commit
651 653 if c.file.size < c.visual.cut_off_limit_diff:
652 654 if c.annotate: # annotation has precedence over renderer
653 655 c.annotated_lines = filenode_as_annotated_lines_tokens(
654 656 c.file
655 657 )
656 658 else:
657 659 c.renderer = (
658 660 c.renderer and h.renderer_from_filename(c.file.path)
659 661 )
660 662 if not c.renderer:
661 663 c.lines = filenode_as_lines_tokens(c.file)
662 664
663 665 _branch_name, _sha_commit_id, is_head = self._is_valid_head(
664 666 commit_id, self.rhodecode_vcs_repo)
665 667 c.on_branch_head = is_head
666 668
667 669 branch = c.commit.branch if (
668 670 c.commit.branch and '/' not in c.commit.branch) else None
669 671 c.branch_or_raw_id = branch or c.commit.raw_id
670 672 c.branch_name = c.commit.branch or h.short_id(c.commit.raw_id)
671 673
672 674 author = c.file_last_commit.author
673 675 c.authors = [[
674 676 h.email(author),
675 677 h.person(author, 'username_or_name_or_email'),
676 678 1
677 679 ]]
678 680
679 681 else: # load tree content at path
680 682 c.file_source_page = 'false'
681 683 c.authors = []
682 684 # this loads a simple tree without metadata to speed things up
683 685 # later via ajax we call repo_nodetree_full and fetch whole
684 686 c.file_tree = self._get_tree_at_commit(c, c.commit.raw_id, f_path)
685 687
686 688 except RepositoryError as e:
687 689 h.flash(safe_str(h.escape(e)), category='error')
688 690 raise HTTPNotFound()
689 691
690 692 if self.request.environ.get('HTTP_X_PJAX'):
691 693 html = render('rhodecode:templates/files/files_pjax.mako',
692 694 self._get_template_context(c), self.request)
693 695 else:
694 696 html = render('rhodecode:templates/files/files.mako',
695 697 self._get_template_context(c), self.request)
696 698 return Response(html)
697 699
698 700 @HasRepoPermissionAnyDecorator(
699 701 'repository.read', 'repository.write', 'repository.admin')
700 702 @view_config(
701 703 route_name='repo_files:annotated_previous', request_method='GET',
702 704 renderer=None)
703 705 def repo_files_annotated_previous(self):
704 706 self.load_default_context()
705 707
706 708 commit_id, f_path = self._get_commit_and_path()
707 709 commit = self._get_commit_or_redirect(commit_id)
708 710 prev_commit_id = commit.raw_id
709 711 line_anchor = self.request.GET.get('line_anchor')
710 712 is_file = False
711 713 try:
712 714 _file = commit.get_node(f_path)
713 715 is_file = _file.is_file()
714 716 except (NodeDoesNotExistError, CommitDoesNotExistError, VCSError):
715 717 pass
716 718
717 719 if is_file:
718 720 history = commit.get_path_history(f_path)
719 721 prev_commit_id = history[1].raw_id \
720 722 if len(history) > 1 else prev_commit_id
721 723 prev_url = h.route_path(
722 724 'repo_files:annotated', repo_name=self.db_repo_name,
723 725 commit_id=prev_commit_id, f_path=f_path,
724 726 _anchor='L{}'.format(line_anchor))
725 727
726 728 raise HTTPFound(prev_url)
727 729
728 730 @LoginRequired()
729 731 @HasRepoPermissionAnyDecorator(
730 732 'repository.read', 'repository.write', 'repository.admin')
731 733 @view_config(
732 734 route_name='repo_nodetree_full', request_method='GET',
733 735 renderer=None, xhr=True)
734 736 @view_config(
735 737 route_name='repo_nodetree_full:default_path', request_method='GET',
736 738 renderer=None, xhr=True)
737 739 def repo_nodetree_full(self):
738 740 """
739 741 Returns rendered html of file tree that contains commit date,
740 742 author, commit_id for the specified combination of
741 743 repo, commit_id and file path
742 744 """
743 745 c = self.load_default_context()
744 746
745 747 commit_id, f_path = self._get_commit_and_path()
746 748 commit = self._get_commit_or_redirect(commit_id)
747 749 try:
748 750 dir_node = commit.get_node(f_path)
749 751 except RepositoryError as e:
750 752 return Response('error: {}'.format(h.escape(safe_str(e))))
751 753
752 754 if dir_node.is_file():
753 755 return Response('')
754 756
755 757 c.file = dir_node
756 758 c.commit = commit
757 759
758 760 html = self._get_tree_at_commit(
759 761 c, commit.raw_id, dir_node.path, full_load=True)
760 762
761 763 return Response(html)
762 764
763 765 def _get_attachement_headers(self, f_path):
764 766 f_name = safe_str(f_path.split(Repository.NAME_SEP)[-1])
765 767 safe_path = f_name.replace('"', '\\"')
766 768 encoded_path = urllib.quote(f_name)
767 769
768 770 return "attachment; " \
769 771 "filename=\"{}\"; " \
770 772 "filename*=UTF-8\'\'{}".format(safe_path, encoded_path)
771 773
772 774 @LoginRequired()
773 775 @HasRepoPermissionAnyDecorator(
774 776 'repository.read', 'repository.write', 'repository.admin')
775 777 @view_config(
776 778 route_name='repo_file_raw', request_method='GET',
777 779 renderer=None)
778 780 def repo_file_raw(self):
779 781 """
780 782 Action for show as raw, some mimetypes are "rendered",
781 783 those include images, icons.
782 784 """
783 785 c = self.load_default_context()
784 786
785 787 commit_id, f_path = self._get_commit_and_path()
786 788 commit = self._get_commit_or_redirect(commit_id)
787 789 file_node = self._get_filenode_or_redirect(commit, f_path)
788 790
789 791 raw_mimetype_mapping = {
790 792 # map original mimetype to a mimetype used for "show as raw"
791 793 # you can also provide a content-disposition to override the
792 794 # default "attachment" disposition.
793 795 # orig_type: (new_type, new_dispo)
794 796
795 797 # show images inline:
796 798 # Do not re-add SVG: it is unsafe and permits XSS attacks. One can
797 799 # for example render an SVG with javascript inside or even render
798 800 # HTML.
799 801 'image/x-icon': ('image/x-icon', 'inline'),
800 802 'image/png': ('image/png', 'inline'),
801 803 'image/gif': ('image/gif', 'inline'),
802 804 'image/jpeg': ('image/jpeg', 'inline'),
803 805 'application/pdf': ('application/pdf', 'inline'),
804 806 }
805 807
806 808 mimetype = file_node.mimetype
807 809 try:
808 810 mimetype, disposition = raw_mimetype_mapping[mimetype]
809 811 except KeyError:
810 812 # we don't know anything special about this, handle it safely
811 813 if file_node.is_binary:
812 814 # do same as download raw for binary files
813 815 mimetype, disposition = 'application/octet-stream', 'attachment'
814 816 else:
815 817 # do not just use the original mimetype, but force text/plain,
816 818 # otherwise it would serve text/html and that might be unsafe.
817 819 # Note: underlying vcs library fakes text/plain mimetype if the
818 820 # mimetype can not be determined and it thinks it is not
819 821 # binary.This might lead to erroneous text display in some
820 822 # cases, but helps in other cases, like with text files
821 823 # without extension.
822 824 mimetype, disposition = 'text/plain', 'inline'
823 825
824 826 if disposition == 'attachment':
825 827 disposition = self._get_attachement_headers(f_path)
826 828
827 829 def stream_node():
828 830 yield file_node.raw_bytes
829 831
830 832 response = Response(app_iter=stream_node())
831 833 response.content_disposition = disposition
832 834 response.content_type = mimetype
833 835
834 836 charset = self._get_default_encoding(c)
835 837 if charset:
836 838 response.charset = charset
837 839
838 840 return response
839 841
840 842 @LoginRequired()
841 843 @HasRepoPermissionAnyDecorator(
842 844 'repository.read', 'repository.write', 'repository.admin')
843 845 @view_config(
844 846 route_name='repo_file_download', request_method='GET',
845 847 renderer=None)
846 848 @view_config(
847 849 route_name='repo_file_download:legacy', request_method='GET',
848 850 renderer=None)
849 851 def repo_file_download(self):
850 852 c = self.load_default_context()
851 853
852 854 commit_id, f_path = self._get_commit_and_path()
853 855 commit = self._get_commit_or_redirect(commit_id)
854 856 file_node = self._get_filenode_or_redirect(commit, f_path)
855 857
856 858 if self.request.GET.get('lf'):
857 859 # only if lf get flag is passed, we download this file
858 860 # as LFS/Largefile
859 861 lf_node = file_node.get_largefile_node()
860 862 if lf_node:
861 863 # overwrite our pointer with the REAL large-file
862 864 file_node = lf_node
863 865
864 866 disposition = self._get_attachement_headers(f_path)
865 867
866 868 def stream_node():
867 869 yield file_node.raw_bytes
868 870
869 871 response = Response(app_iter=stream_node())
870 872 response.content_disposition = disposition
871 873 response.content_type = file_node.mimetype
872 874
873 875 charset = self._get_default_encoding(c)
874 876 if charset:
875 877 response.charset = charset
876 878
877 879 return response
878 880
879 881 def _get_nodelist_at_commit(self, repo_name, repo_id, commit_id, f_path):
880 882
881 883 cache_seconds = safe_int(
882 884 rhodecode.CONFIG.get('rc_cache.cache_repo.expiration_time'))
883 885 cache_on = cache_seconds > 0
884 886 log.debug(
885 887 'Computing FILE SEARCH for repo_id %s commit_id `%s` and path `%s`'
886 888 'with caching: %s[TTL: %ss]' % (
887 889 repo_id, commit_id, f_path, cache_on, cache_seconds or 0))
888 890
889 891 cache_namespace_uid = 'cache_repo.{}'.format(repo_id)
890 892 region = rc_cache.get_or_create_region('cache_repo', cache_namespace_uid)
891 893
892 894 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid,
893 895 condition=cache_on)
894 896 def compute_file_search(repo_id, commit_id, f_path):
895 897 log.debug('Generating cached nodelist for repo_id:%s, %s, %s',
896 898 repo_id, commit_id, f_path)
897 899 try:
898 900 _d, _f = ScmModel().get_nodes(
899 901 repo_name, commit_id, f_path, flat=False)
900 902 except (RepositoryError, CommitDoesNotExistError, Exception) as e:
901 903 log.exception(safe_str(e))
902 904 h.flash(safe_str(h.escape(e)), category='error')
903 905 raise HTTPFound(h.route_path(
904 906 'repo_files', repo_name=self.db_repo_name,
905 907 commit_id='tip', f_path='/'))
906 908 return _d + _f
907 909
908 910 return compute_file_search(self.db_repo.repo_id, commit_id, f_path)
909 911
910 912 @LoginRequired()
911 913 @HasRepoPermissionAnyDecorator(
912 914 'repository.read', 'repository.write', 'repository.admin')
913 915 @view_config(
914 916 route_name='repo_files_nodelist', request_method='GET',
915 917 renderer='json_ext', xhr=True)
916 918 def repo_nodelist(self):
917 919 self.load_default_context()
918 920
919 921 commit_id, f_path = self._get_commit_and_path()
920 922 commit = self._get_commit_or_redirect(commit_id)
921 923
922 924 metadata = self._get_nodelist_at_commit(
923 925 self.db_repo_name, self.db_repo.repo_id, commit.raw_id, f_path)
924 926 return {'nodes': metadata}
925 927
926 928 def _create_references(self, branches_or_tags, symbolic_reference, f_path, ref_type):
927 929 items = []
928 930 for name, commit_id in branches_or_tags.items():
929 931 sym_ref = symbolic_reference(commit_id, name, f_path, ref_type)
930 932 items.append((sym_ref, name, ref_type))
931 933 return items
932 934
933 935 def _symbolic_reference(self, commit_id, name, f_path, ref_type):
934 936 return commit_id
935 937
936 938 def _symbolic_reference_svn(self, commit_id, name, f_path, ref_type):
937 939 new_f_path = vcspath.join(name, f_path)
938 940 return u'%s@%s' % (new_f_path, commit_id)
939 941
940 942 def _get_node_history(self, commit_obj, f_path, commits=None):
941 943 """
942 944 get commit history for given node
943 945
944 946 :param commit_obj: commit to calculate history
945 947 :param f_path: path for node to calculate history for
946 948 :param commits: if passed don't calculate history and take
947 949 commits defined in this list
948 950 """
949 951 _ = self.request.translate
950 952
951 953 # calculate history based on tip
952 954 tip = self.rhodecode_vcs_repo.get_commit()
953 955 if commits is None:
954 956 pre_load = ["author", "branch"]
955 957 try:
956 958 commits = tip.get_path_history(f_path, pre_load=pre_load)
957 959 except (NodeDoesNotExistError, CommitError):
958 960 # this node is not present at tip!
959 961 commits = commit_obj.get_path_history(f_path, pre_load=pre_load)
960 962
961 963 history = []
962 964 commits_group = ([], _("Changesets"))
963 965 for commit in commits:
964 966 branch = ' (%s)' % commit.branch if commit.branch else ''
965 967 n_desc = 'r%s:%s%s' % (commit.idx, commit.short_id, branch)
966 968 commits_group[0].append((commit.raw_id, n_desc, 'sha'))
967 969 history.append(commits_group)
968 970
969 971 symbolic_reference = self._symbolic_reference
970 972
971 973 if self.rhodecode_vcs_repo.alias == 'svn':
972 974 adjusted_f_path = RepoFilesView.adjust_file_path_for_svn(
973 975 f_path, self.rhodecode_vcs_repo)
974 976 if adjusted_f_path != f_path:
975 977 log.debug(
976 978 'Recognized svn tag or branch in file "%s", using svn '
977 979 'specific symbolic references', f_path)
978 980 f_path = adjusted_f_path
979 981 symbolic_reference = self._symbolic_reference_svn
980 982
981 983 branches = self._create_references(
982 984 self.rhodecode_vcs_repo.branches, symbolic_reference, f_path, 'branch')
983 985 branches_group = (branches, _("Branches"))
984 986
985 987 tags = self._create_references(
986 988 self.rhodecode_vcs_repo.tags, symbolic_reference, f_path, 'tag')
987 989 tags_group = (tags, _("Tags"))
988 990
989 991 history.append(branches_group)
990 992 history.append(tags_group)
991 993
992 994 return history, commits
993 995
994 996 @LoginRequired()
995 997 @HasRepoPermissionAnyDecorator(
996 998 'repository.read', 'repository.write', 'repository.admin')
997 999 @view_config(
998 1000 route_name='repo_file_history', request_method='GET',
999 1001 renderer='json_ext')
1000 1002 def repo_file_history(self):
1001 1003 self.load_default_context()
1002 1004
1003 1005 commit_id, f_path = self._get_commit_and_path()
1004 1006 commit = self._get_commit_or_redirect(commit_id)
1005 1007 file_node = self._get_filenode_or_redirect(commit, f_path)
1006 1008
1007 1009 if file_node.is_file():
1008 1010 file_history, _hist = self._get_node_history(commit, f_path)
1009 1011
1010 1012 res = []
1011 1013 for obj in file_history:
1012 1014 res.append({
1013 1015 'text': obj[1],
1014 1016 'children': [{'id': o[0], 'text': o[1], 'type': o[2]} for o in obj[0]]
1015 1017 })
1016 1018
1017 1019 data = {
1018 1020 'more': False,
1019 1021 'results': res
1020 1022 }
1021 1023 return data
1022 1024
1023 1025 log.warning('Cannot fetch history for directory')
1024 1026 raise HTTPBadRequest()
1025 1027
1026 1028 @LoginRequired()
1027 1029 @HasRepoPermissionAnyDecorator(
1028 1030 'repository.read', 'repository.write', 'repository.admin')
1029 1031 @view_config(
1030 1032 route_name='repo_file_authors', request_method='GET',
1031 1033 renderer='rhodecode:templates/files/file_authors_box.mako')
1032 1034 def repo_file_authors(self):
1033 1035 c = self.load_default_context()
1034 1036
1035 1037 commit_id, f_path = self._get_commit_and_path()
1036 1038 commit = self._get_commit_or_redirect(commit_id)
1037 1039 file_node = self._get_filenode_or_redirect(commit, f_path)
1038 1040
1039 1041 if not file_node.is_file():
1040 1042 raise HTTPBadRequest()
1041 1043
1042 1044 c.file_last_commit = file_node.last_commit
1043 1045 if self.request.GET.get('annotate') == '1':
1044 1046 # use _hist from annotation if annotation mode is on
1045 1047 commit_ids = set(x[1] for x in file_node.annotate)
1046 1048 _hist = (
1047 1049 self.rhodecode_vcs_repo.get_commit(commit_id)
1048 1050 for commit_id in commit_ids)
1049 1051 else:
1050 1052 _f_history, _hist = self._get_node_history(commit, f_path)
1051 1053 c.file_author = False
1052 1054
1053 1055 unique = collections.OrderedDict()
1054 1056 for commit in _hist:
1055 1057 author = commit.author
1056 1058 if author not in unique:
1057 1059 unique[commit.author] = [
1058 1060 h.email(author),
1059 1061 h.person(author, 'username_or_name_or_email'),
1060 1062 1 # counter
1061 1063 ]
1062 1064
1063 1065 else:
1064 1066 # increase counter
1065 1067 unique[commit.author][2] += 1
1066 1068
1067 1069 c.authors = [val for val in unique.values()]
1068 1070
1069 1071 return self._get_template_context(c)
1070 1072
1071 1073 @LoginRequired()
1072 1074 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1073 1075 @view_config(
1074 1076 route_name='repo_files_remove_file', request_method='GET',
1075 1077 renderer='rhodecode:templates/files/files_delete.mako')
1076 1078 def repo_files_remove_file(self):
1077 1079 _ = self.request.translate
1078 1080 c = self.load_default_context()
1079 1081 commit_id, f_path = self._get_commit_and_path()
1080 1082
1081 1083 self._ensure_not_locked()
1082 1084 _branch_name, _sha_commit_id, is_head = \
1083 1085 self._is_valid_head(commit_id, self.rhodecode_vcs_repo)
1084 1086
1085 1087 self.forbid_non_head(is_head, f_path)
1086 1088 self.check_branch_permission(_branch_name)
1087 1089
1088 1090 c.commit = self._get_commit_or_redirect(commit_id)
1089 1091 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1090 1092
1091 1093 c.default_message = _(
1092 1094 'Deleted file {} via RhodeCode Enterprise').format(f_path)
1093 1095 c.f_path = f_path
1094 1096
1095 1097 return self._get_template_context(c)
1096 1098
1097 1099 @LoginRequired()
1098 1100 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1099 1101 @CSRFRequired()
1100 1102 @view_config(
1101 1103 route_name='repo_files_delete_file', request_method='POST',
1102 1104 renderer=None)
1103 1105 def repo_files_delete_file(self):
1104 1106 _ = self.request.translate
1105 1107
1106 1108 c = self.load_default_context()
1107 1109 commit_id, f_path = self._get_commit_and_path()
1108 1110
1109 1111 self._ensure_not_locked()
1110 1112 _branch_name, _sha_commit_id, is_head = \
1111 1113 self._is_valid_head(commit_id, self.rhodecode_vcs_repo)
1112 1114
1113 1115 self.forbid_non_head(is_head, f_path)
1114 1116 self.check_branch_permission(_branch_name)
1115 1117
1116 1118 c.commit = self._get_commit_or_redirect(commit_id)
1117 1119 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1118 1120
1119 1121 c.default_message = _(
1120 1122 'Deleted file {} via RhodeCode Enterprise').format(f_path)
1121 1123 c.f_path = f_path
1122 1124 node_path = f_path
1123 1125 author = self._rhodecode_db_user.full_contact
1124 1126 message = self.request.POST.get('message') or c.default_message
1125 1127 try:
1126 1128 nodes = {
1127 1129 node_path: {
1128 1130 'content': ''
1129 1131 }
1130 1132 }
1131 1133 ScmModel().delete_nodes(
1132 1134 user=self._rhodecode_db_user.user_id, repo=self.db_repo,
1133 1135 message=message,
1134 1136 nodes=nodes,
1135 1137 parent_commit=c.commit,
1136 1138 author=author,
1137 1139 )
1138 1140
1139 1141 h.flash(
1140 1142 _('Successfully deleted file `{}`').format(
1141 1143 h.escape(f_path)), category='success')
1142 1144 except Exception:
1143 1145 log.exception('Error during commit operation')
1144 1146 h.flash(_('Error occurred during commit'), category='error')
1145 1147 raise HTTPFound(
1146 1148 h.route_path('repo_commit', repo_name=self.db_repo_name,
1147 1149 commit_id='tip'))
1148 1150
1149 1151 @LoginRequired()
1150 1152 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1151 1153 @view_config(
1152 1154 route_name='repo_files_edit_file', request_method='GET',
1153 1155 renderer='rhodecode:templates/files/files_edit.mako')
1154 1156 def repo_files_edit_file(self):
1155 1157 _ = self.request.translate
1156 1158 c = self.load_default_context()
1157 1159 commit_id, f_path = self._get_commit_and_path()
1158 1160
1159 1161 self._ensure_not_locked()
1160 1162 _branch_name, _sha_commit_id, is_head = \
1161 1163 self._is_valid_head(commit_id, self.rhodecode_vcs_repo)
1162 1164
1163 1165 self.forbid_non_head(is_head, f_path, commit_id=commit_id)
1164 1166 self.check_branch_permission(_branch_name, commit_id=commit_id)
1165 1167
1166 1168 c.commit = self._get_commit_or_redirect(commit_id)
1167 1169 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1168 1170
1169 1171 if c.file.is_binary:
1170 1172 files_url = h.route_path(
1171 1173 'repo_files',
1172 1174 repo_name=self.db_repo_name,
1173 1175 commit_id=c.commit.raw_id, f_path=f_path)
1174 1176 raise HTTPFound(files_url)
1175 1177
1176 1178 c.default_message = _('Edited file {} via RhodeCode Enterprise').format(f_path)
1177 1179 c.f_path = f_path
1178 1180
1179 1181 return self._get_template_context(c)
1180 1182
1181 1183 @LoginRequired()
1182 1184 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1183 1185 @CSRFRequired()
1184 1186 @view_config(
1185 1187 route_name='repo_files_update_file', request_method='POST',
1186 1188 renderer=None)
1187 1189 def repo_files_update_file(self):
1188 1190 _ = self.request.translate
1189 1191 c = self.load_default_context()
1190 1192 commit_id, f_path = self._get_commit_and_path()
1191 1193
1192 1194 self._ensure_not_locked()
1193 1195
1194 1196 c.commit = self._get_commit_or_redirect(commit_id)
1195 1197 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1196 1198
1197 1199 if c.file.is_binary:
1198 1200 raise HTTPFound(h.route_path('repo_files', repo_name=self.db_repo_name,
1199 1201 commit_id=c.commit.raw_id, f_path=f_path))
1200 1202
1201 1203 _branch_name, _sha_commit_id, is_head = \
1202 1204 self._is_valid_head(commit_id, self.rhodecode_vcs_repo)
1203 1205
1204 1206 self.forbid_non_head(is_head, f_path, commit_id=commit_id)
1205 1207 self.check_branch_permission(_branch_name, commit_id=commit_id)
1206 1208
1207 1209 c.default_message = _('Edited file {} via RhodeCode Enterprise').format(f_path)
1208 1210 c.f_path = f_path
1209 1211
1210 1212 old_content = c.file.content
1211 1213 sl = old_content.splitlines(1)
1212 1214 first_line = sl[0] if sl else ''
1213 1215
1214 1216 r_post = self.request.POST
1215 1217 # line endings: 0 - Unix, 1 - Mac, 2 - DOS
1216 1218 line_ending_mode = detect_mode(first_line, 0)
1217 1219 content = convert_line_endings(r_post.get('content', ''), line_ending_mode)
1218 1220
1219 1221 message = r_post.get('message') or c.default_message
1220 1222 org_node_path = c.file.unicode_path
1221 1223 filename = r_post['filename']
1222 1224
1223 1225 root_path = c.file.dir_path
1224 1226 pure_path = self.create_pure_path(root_path, filename)
1225 1227 node_path = safe_unicode(bytes(pure_path))
1226 1228
1227 1229 default_redirect_url = h.route_path('repo_commit', repo_name=self.db_repo_name,
1228 1230 commit_id=commit_id)
1229 1231 if content == old_content and node_path == org_node_path:
1230 1232 h.flash(_('No changes detected on {}').format(org_node_path),
1231 1233 category='warning')
1232 1234 raise HTTPFound(default_redirect_url)
1233 1235
1234 1236 try:
1235 1237 mapping = {
1236 1238 org_node_path: {
1237 1239 'org_filename': org_node_path,
1238 1240 'filename': node_path,
1239 1241 'content': content,
1240 1242 'lexer': '',
1241 1243 'op': 'mod',
1242 1244 'mode': c.file.mode
1243 1245 }
1244 1246 }
1245 1247
1246 1248 commit = ScmModel().update_nodes(
1247 1249 user=self._rhodecode_db_user.user_id,
1248 1250 repo=self.db_repo,
1249 1251 message=message,
1250 1252 nodes=mapping,
1251 1253 parent_commit=c.commit,
1252 1254 )
1253 1255
1254 1256 h.flash(_('Successfully committed changes to file `{}`').format(
1255 1257 h.escape(f_path)), category='success')
1256 1258 default_redirect_url = h.route_path(
1257 1259 'repo_commit', repo_name=self.db_repo_name, commit_id=commit.raw_id)
1258 1260
1259 1261 except Exception:
1260 1262 log.exception('Error occurred during commit')
1261 1263 h.flash(_('Error occurred during commit'), category='error')
1262 1264
1263 1265 raise HTTPFound(default_redirect_url)
1264 1266
1265 1267 @LoginRequired()
1266 1268 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1267 1269 @view_config(
1268 1270 route_name='repo_files_add_file', request_method='GET',
1269 1271 renderer='rhodecode:templates/files/files_add.mako')
1270 1272 @view_config(
1271 1273 route_name='repo_files_upload_file', request_method='GET',
1272 1274 renderer='rhodecode:templates/files/files_upload.mako')
1273 1275 def repo_files_add_file(self):
1274 1276 _ = self.request.translate
1275 1277 c = self.load_default_context()
1276 1278 commit_id, f_path = self._get_commit_and_path()
1277 1279
1278 1280 self._ensure_not_locked()
1279 1281
1280 1282 c.commit = self._get_commit_or_redirect(commit_id, redirect_after=False)
1281 1283 if c.commit is None:
1282 1284 c.commit = EmptyCommit(alias=self.rhodecode_vcs_repo.alias)
1283 1285
1284 1286 if self.rhodecode_vcs_repo.is_empty():
1285 1287 # for empty repository we cannot check for current branch, we rely on
1286 1288 # c.commit.branch instead
1287 1289 _branch_name = c.commit.branch
1288 1290 is_head = True
1289 1291 else:
1290 1292 _branch_name, _sha_commit_id, is_head = \
1291 1293 self._is_valid_head(commit_id, self.rhodecode_vcs_repo)
1292 1294
1293 1295 self.forbid_non_head(is_head, f_path, commit_id=commit_id)
1294 1296 self.check_branch_permission(_branch_name, commit_id=commit_id)
1295 1297
1296 1298 c.default_message = (_('Added file via RhodeCode Enterprise'))
1297 1299 c.f_path = f_path.lstrip('/') # ensure not relative path
1298 1300
1299 1301 return self._get_template_context(c)
1300 1302
1301 1303 @LoginRequired()
1302 1304 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1303 1305 @CSRFRequired()
1304 1306 @view_config(
1305 1307 route_name='repo_files_create_file', request_method='POST',
1306 1308 renderer=None)
1307 1309 def repo_files_create_file(self):
1308 1310 _ = self.request.translate
1309 1311 c = self.load_default_context()
1310 1312 commit_id, f_path = self._get_commit_and_path()
1311 1313
1312 1314 self._ensure_not_locked()
1313 1315
1314 1316 c.commit = self._get_commit_or_redirect(commit_id, redirect_after=False)
1315 1317 if c.commit is None:
1316 1318 c.commit = EmptyCommit(alias=self.rhodecode_vcs_repo.alias)
1317 1319
1318 1320 # calculate redirect URL
1319 1321 if self.rhodecode_vcs_repo.is_empty():
1320 1322 default_redirect_url = h.route_path(
1321 1323 'repo_summary', repo_name=self.db_repo_name)
1322 1324 else:
1323 1325 default_redirect_url = h.route_path(
1324 1326 'repo_commit', repo_name=self.db_repo_name, commit_id='tip')
1325 1327
1326 1328 if self.rhodecode_vcs_repo.is_empty():
1327 1329 # for empty repository we cannot check for current branch, we rely on
1328 1330 # c.commit.branch instead
1329 1331 _branch_name = c.commit.branch
1330 1332 is_head = True
1331 1333 else:
1332 1334 _branch_name, _sha_commit_id, is_head = \
1333 1335 self._is_valid_head(commit_id, self.rhodecode_vcs_repo)
1334 1336
1335 1337 self.forbid_non_head(is_head, f_path, commit_id=commit_id)
1336 1338 self.check_branch_permission(_branch_name, commit_id=commit_id)
1337 1339
1338 1340 c.default_message = (_('Added file via RhodeCode Enterprise'))
1339 1341 c.f_path = f_path
1340 1342
1341 1343 r_post = self.request.POST
1342 1344 message = r_post.get('message') or c.default_message
1343 1345 filename = r_post.get('filename')
1344 1346 unix_mode = 0
1345 1347 content = convert_line_endings(r_post.get('content', ''), unix_mode)
1346 1348
1347 1349 if not filename:
1348 1350 # If there's no commit, redirect to repo summary
1349 1351 if type(c.commit) is EmptyCommit:
1350 1352 redirect_url = h.route_path(
1351 1353 'repo_summary', repo_name=self.db_repo_name)
1352 1354 else:
1353 1355 redirect_url = default_redirect_url
1354 1356 h.flash(_('No filename specified'), category='warning')
1355 1357 raise HTTPFound(redirect_url)
1356 1358
1357 1359 root_path = f_path
1358 1360 pure_path = self.create_pure_path(root_path, filename)
1359 1361 node_path = safe_unicode(bytes(pure_path).lstrip('/'))
1360 1362
1361 1363 author = self._rhodecode_db_user.full_contact
1362 1364 nodes = {
1363 1365 node_path: {
1364 1366 'content': content
1365 1367 }
1366 1368 }
1367 1369
1368 1370 try:
1369 1371
1370 1372 commit = ScmModel().create_nodes(
1371 1373 user=self._rhodecode_db_user.user_id,
1372 1374 repo=self.db_repo,
1373 1375 message=message,
1374 1376 nodes=nodes,
1375 1377 parent_commit=c.commit,
1376 1378 author=author,
1377 1379 )
1378 1380
1379 1381 h.flash(_('Successfully committed new file `{}`').format(
1380 1382 h.escape(node_path)), category='success')
1381 1383
1382 1384 default_redirect_url = h.route_path(
1383 1385 'repo_commit', repo_name=self.db_repo_name, commit_id=commit.raw_id)
1384 1386
1385 1387 except NonRelativePathError:
1386 1388 log.exception('Non Relative path found')
1387 1389 h.flash(_('The location specified must be a relative path and must not '
1388 1390 'contain .. in the path'), category='warning')
1389 1391 raise HTTPFound(default_redirect_url)
1390 1392 except (NodeError, NodeAlreadyExistsError) as e:
1391 1393 h.flash(_(h.escape(e)), category='error')
1392 1394 except Exception:
1393 1395 log.exception('Error occurred during commit')
1394 1396 h.flash(_('Error occurred during commit'), category='error')
1395 1397
1396 1398 raise HTTPFound(default_redirect_url)
1397 1399
1398 1400 @LoginRequired()
1399 1401 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1400 1402 @CSRFRequired()
1401 1403 @view_config(
1402 1404 route_name='repo_files_upload_file', request_method='POST',
1403 1405 renderer='json_ext')
1404 1406 def repo_files_upload_file(self):
1405 1407 _ = self.request.translate
1406 1408 c = self.load_default_context()
1407 1409 commit_id, f_path = self._get_commit_and_path()
1408 1410
1409 1411 self._ensure_not_locked()
1410 1412
1411 1413 c.commit = self._get_commit_or_redirect(commit_id, redirect_after=False)
1412 1414 if c.commit is None:
1413 1415 c.commit = EmptyCommit(alias=self.rhodecode_vcs_repo.alias)
1414 1416
1415 1417 # calculate redirect URL
1416 1418 if self.rhodecode_vcs_repo.is_empty():
1417 1419 default_redirect_url = h.route_path(
1418 1420 'repo_summary', repo_name=self.db_repo_name)
1419 1421 else:
1420 1422 default_redirect_url = h.route_path(
1421 1423 'repo_commit', repo_name=self.db_repo_name, commit_id='tip')
1422 1424
1423 1425 if self.rhodecode_vcs_repo.is_empty():
1424 1426 # for empty repository we cannot check for current branch, we rely on
1425 1427 # c.commit.branch instead
1426 1428 _branch_name = c.commit.branch
1427 1429 is_head = True
1428 1430 else:
1429 1431 _branch_name, _sha_commit_id, is_head = \
1430 1432 self._is_valid_head(commit_id, self.rhodecode_vcs_repo)
1431 1433
1432 1434 error = self.forbid_non_head(is_head, f_path, json_mode=True)
1433 1435 if error:
1434 1436 return {
1435 1437 'error': error,
1436 1438 'redirect_url': default_redirect_url
1437 1439 }
1438 1440 error = self.check_branch_permission(_branch_name, json_mode=True)
1439 1441 if error:
1440 1442 return {
1441 1443 'error': error,
1442 1444 'redirect_url': default_redirect_url
1443 1445 }
1444 1446
1445 1447 c.default_message = (_('Uploaded file via RhodeCode Enterprise'))
1446 1448 c.f_path = f_path
1447 1449
1448 1450 r_post = self.request.POST
1449 1451
1450 1452 message = c.default_message
1451 1453 user_message = r_post.getall('message')
1452 1454 if isinstance(user_message, list) and user_message:
1453 1455 # we take the first from duplicated results if it's not empty
1454 1456 message = user_message[0] if user_message[0] else message
1455 1457
1456 1458 nodes = {}
1457 1459
1458 1460 for file_obj in r_post.getall('files_upload') or []:
1459 1461 content = file_obj.file
1460 1462 filename = file_obj.filename
1461 1463
1462 1464 root_path = f_path
1463 1465 pure_path = self.create_pure_path(root_path, filename)
1464 1466 node_path = safe_unicode(bytes(pure_path).lstrip('/'))
1465 1467
1466 1468 nodes[node_path] = {
1467 1469 'content': content
1468 1470 }
1469 1471
1470 1472 if not nodes:
1471 1473 error = 'missing files'
1472 1474 return {
1473 1475 'error': error,
1474 1476 'redirect_url': default_redirect_url
1475 1477 }
1476 1478
1477 1479 author = self._rhodecode_db_user.full_contact
1478 1480
1479 1481 try:
1480 1482 commit = ScmModel().create_nodes(
1481 1483 user=self._rhodecode_db_user.user_id,
1482 1484 repo=self.db_repo,
1483 1485 message=message,
1484 1486 nodes=nodes,
1485 1487 parent_commit=c.commit,
1486 1488 author=author,
1487 1489 )
1488 1490 if len(nodes) == 1:
1489 1491 flash_message = _('Successfully committed {} new files').format(len(nodes))
1490 1492 else:
1491 1493 flash_message = _('Successfully committed 1 new file')
1492 1494
1493 1495 h.flash(flash_message, category='success')
1494 1496
1495 1497 default_redirect_url = h.route_path(
1496 1498 'repo_commit', repo_name=self.db_repo_name, commit_id=commit.raw_id)
1497 1499
1498 1500 except NonRelativePathError:
1499 1501 log.exception('Non Relative path found')
1500 1502 error = _('The location specified must be a relative path and must not '
1501 1503 'contain .. in the path')
1502 1504 h.flash(error, category='warning')
1503 1505
1504 1506 return {
1505 1507 'error': error,
1506 1508 'redirect_url': default_redirect_url
1507 1509 }
1508 1510 except (NodeError, NodeAlreadyExistsError) as e:
1509 1511 error = h.escape(e)
1510 1512 h.flash(error, category='error')
1511 1513
1512 1514 return {
1513 1515 'error': error,
1514 1516 'redirect_url': default_redirect_url
1515 1517 }
1516 1518 except Exception:
1517 1519 log.exception('Error occurred during commit')
1518 1520 error = _('Error occurred during commit')
1519 1521 h.flash(error, category='error')
1520 1522 return {
1521 1523 'error': error,
1522 1524 'redirect_url': default_redirect_url
1523 1525 }
1524 1526
1525 1527 return {
1526 1528 'error': None,
1527 1529 'redirect_url': default_redirect_url
1528 1530 }
@@ -1,151 +1,167 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%namespace name="base" file="/base/base.mako"/>
3 3 %if c.repo_commits:
4 4 <table class="rctable repo_summary table_disp">
5 5 <tr>
6 6
7 7 <th class="status"></th>
8 8 <th>${_('Commit')}</th>
9 9 <th>${_('Commit message')}</th>
10 10 <th>${_('Age')}</th>
11 11 <th>${_('Author')}</th>
12 12 <th colspan="2">${_('Refs')}</th>
13 13 </tr>
14 14
15 15 ## to speed up lookups cache some functions before the loop
16 16 <%
17 17 active_patterns = h.get_active_pattern_entries(c.repo_name)
18 18 urlify_commit_message = h.partial(h.urlify_commit_message, active_pattern_entries=active_patterns)
19 19 %>
20 20 %for cnt,cs in enumerate(c.repo_commits):
21 21 <tr class="parity${cnt%2}">
22 22
23 23 <td class="td-status">
24 24 %if c.statuses.get(cs.raw_id):
25 25 <div class="changeset-status-ico shortlog">
26 26 %if c.statuses.get(cs.raw_id)[2]:
27 27 <a class="tooltip" title="${_('Commit status: %s\nClick to open associated pull request #%s') % (c.statuses.get(cs.raw_id)[0], c.statuses.get(cs.raw_id)[2])}" href="${h.route_path('pullrequest_show',repo_name=c.statuses.get(cs.raw_id)[3],pull_request_id=c.statuses.get(cs.raw_id)[2])}">
28 28 <div class="${'flag_status {}'.format(c.statuses.get(cs.raw_id)[0])}"></div>
29 29 </a>
30 30 %else:
31 31 <a class="tooltip" title="${_('Commit status: {}').format(h.commit_status_lbl(c.statuses.get(cs.raw_id)[0]))}" href="${h.route_path('repo_commit',repo_name=c.repo_name,commit_id=cs.raw_id,_anchor='comment-%s' % c.comments[cs.raw_id][0].comment_id)}">
32 32 <div class="${'flag_status {}'.format(c.statuses.get(cs.raw_id)[0])}"></div>
33 33 </a>
34 34 %endif
35 35 </div>
36 36 %else:
37 37 <div class="tooltip flag_status not_reviewed" title="${_('Commit status: Not Reviewed')}"></div>
38 38 %endif
39 39 </td>
40 40 <td class="td-commit">
41 41 <code>
42 42 <a href="${h.route_path('repo_commit', repo_name=c.repo_name, commit_id=cs.raw_id)}">${h.show_id(cs)}</a>
43 43 <i class="tooltip icon-clipboard clipboard-action" data-clipboard-text="${cs.raw_id}" title="${_('Copy the full commit id')}"></i>
44 44 </code>
45 45 </td>
46 46
47 47 <td class="td-description mid">
48 48 <div class="log-container truncate-wrap">
49 49 <div class="message truncate" id="c-${cs.raw_id}">${urlify_commit_message(cs.message, c.repo_name)}</div>
50 50 </div>
51 51 </td>
52 52
53 53 <td class="td-time">
54 54 ${h.age_component(cs.date)}
55 55 </td>
56 56 <td class="td-user author">
57 57 ${base.gravatar_with_user(cs.author)}
58 58 </td>
59 59
60 60 <td class="td-tags">
61 61 <div class="autoexpand">
62 62 %if h.is_hg(c.rhodecode_repo):
63 63 %for book in cs.bookmarks:
64 64 <span class="booktag tag" title="${h.tooltip(_('Bookmark %s') % book)}">
65 65 <a href="${h.route_path('repo_files:default_path',repo_name=c.repo_name,commit_id=cs.raw_id, _query=dict(at=book))}"><i class="icon-bookmark"></i>${h.shorter(book)}</a>
66 66 </span>
67 67 %endfor
68 68 %endif
69 69 ## tags
70 70 %for tag in cs.tags:
71 71 <span class="tagtag tag" title="${h.tooltip(_('Tag %s') % tag)}">
72 72 <a href="${h.route_path('repo_files:default_path',repo_name=c.repo_name,commit_id=cs.raw_id, _query=dict(at=tag))}"><i class="icon-tag"></i>${h.shorter(tag)}</a>
73 73 </span>
74 74 %endfor
75 75
76 76 ## branch
77 77 %if cs.branch:
78 78 <span class="branchtag tag" title="${h.tooltip(_('Branch %s') % cs.branch)}">
79 79 <a href="${h.route_path('repo_commits',repo_name=c.repo_name,_query=dict(branch=cs.branch))}"><i class="icon-code-fork"></i>${h.shorter(cs.branch)}</a>
80 80 </span>
81 81 %endif
82 82 </div>
83 83 </td>
84 84 <td class="td-comments">
85 85 <% cs_comments = c.comments.get(cs.raw_id,[]) %>
86 86 % if cs_comments:
87 87 <a title="${_('Commit has comments')}" href="${h.route_path('repo_commit',repo_name=c.repo_name,commit_id=cs.raw_id,_anchor='comment-%s' % cs_comments[0].comment_id)}">
88 88 <i class="icon-comment"></i> ${len(cs_comments)}
89 89 </a>
90 90 % else:
91 91 <i class="icon-comment"></i> ${len(cs_comments)}
92 92 % endif
93 93 </td>
94 94 </tr>
95 95 %endfor
96 96
97 97 </table>
98 98
99 99 <script type="text/javascript">
100 100 $(document).pjax('#shortlog_data .pager_link','#shortlog_data', {timeout: 5000, scrollTo: false, push: false});
101 101 $(document).on('pjax:success', function(){ timeagoActivate(); });
102 102 $(document).on('pjax:timeout', function(event) {
103 103 // Prevent default timeout redirection behavior
104 104 event.preventDefault()
105 105 })
106 106
107 107 </script>
108 108
109 109 <div class="pagination-wh pagination-left">
110 110 ${c.repo_commits.pager('$link_previous ~2~ $link_next')}
111 111 </div>
112 112 %else:
113 113
114 114 %if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name):
115 115 <div class="quick_start">
116 116 <div class="fieldset">
117 117 <p><b>${_('Add or upload files directly via RhodeCode:')}</b></p>
118 118 <div class="pull-left">
119 119 <a href="${h.route_path('repo_files_add_file',repo_name=c.repo_name,commit_id=0, f_path='')}" class="btn btn-default">${_('Add New File')}</a>
120 120 </div>
121 121 <div class="pull-left">
122 122 <a href="${h.route_path('repo_files_upload_file',repo_name=c.repo_name,commit_id=0, f_path='')}" class="btn btn-default">${_('Upload New File')}</a>
123 123 </div>
124 124 %endif
125 125 </div>
126 126
127 %if not h.is_svn(c.rhodecode_repo):
128 <div class="fieldset">
129 <p><b>${_('Push new repo:')}</b></p>
130 <pre>
131 ${c.rhodecode_repo.alias} clone ${c.clone_repo_url}
132 ${c.rhodecode_repo.alias} add README # add first file
133 ${c.rhodecode_repo.alias} commit -m "Initial" # commit with message
134 ${c.rhodecode_repo.alias} push ${'origin master' if h.is_git(c.rhodecode_repo) else ''} # push changes back
127 <div class="fieldset">
128 <p><b>${_('Push new repo:')}</b></p>
129 <pre>
130 %if h.is_git(c.rhodecode_repo):
131 git clone ${c.clone_repo_url}
132 git add README # add first file
133 git commit -m "Initial commit" # commit with message
134 git remote add origin ${c.clone_repo_url}
135 git push -u origin master # push changes back to default master branch
136 %elif h.is_hg(c.rhodecode_repo):
137 hg clone ${c.clone_repo_url}
138 hg add README # add first file
139 hg commit -m "Initial commit" # commit with message
140 hg push ${c.clone_repo_url}
141 %elif h.is_svn(c.rhodecode_repo):
142 svn co ${c.clone_repo_url}
143 svn add README # add first file
144 svn commit -m "Initial commit"
145 svn commit # send changes back to the server
146 %endif
135 147 </pre>
136 </div>
148 </div>
137 149
138 <div class="fieldset">
139 <p><b>${_('Existing repository?')}</b></p>
140 <pre>
141 %if h.is_git(c.rhodecode_repo):
150 <div class="fieldset">
151 <p><b>${_('Existing repository?')}</b></p>
152 <pre>
153 %if h.is_git(c.rhodecode_repo):
142 154 git remote add origin ${c.clone_repo_url}
143 155 git push -u origin master
144 %else:
156 %elif h.is_hg(c.rhodecode_repo):
145 157 hg push ${c.clone_repo_url}
146 %endif
158 %elif h.is_svn(c.rhodecode_repo):
159 svn co ${c.clone_repo_url}
160 %endif
147 161 </pre>
148 </div>
149 %endif
162
163 </div>
164
165
150 166 </div>
151 167 %endif
General Comments 0
You need to be logged in to leave comments. Login now