##// END OF EJS Templates
files: fixed the repo switcher at flag beeing nor persistens...
dan -
r4294:d4e644bf default
parent child Browse files
Show More
@@ -1,1555 +1,1568 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 109 message = _('Cannot modify file. '
110 110 'Given commit `{}` is not head of a branch.').format(commit_id)
111 111 h.flash(message, category='warning')
112 112
113 113 if json_mode:
114 114 return message
115 115
116 116 files_url = h.route_path(
117 117 'repo_files', repo_name=self.db_repo_name, commit_id=commit_id,
118 118 f_path=f_path)
119 119 raise HTTPFound(files_url)
120 120
121 121 def check_branch_permission(self, branch_name, commit_id='tip', json_mode=False):
122 122 _ = self.request.translate
123 123
124 124 rule, branch_perm = self._rhodecode_user.get_rule_and_branch_permission(
125 125 self.db_repo_name, branch_name)
126 126 if branch_perm and branch_perm not in ['branch.push', 'branch.push_force']:
127 127 message = _('Branch `{}` changes forbidden by rule {}.').format(
128 128 branch_name, rule)
129 129 h.flash(message, 'warning')
130 130
131 131 if json_mode:
132 132 return message
133 133
134 134 files_url = h.route_path(
135 135 'repo_files:default_path', repo_name=self.db_repo_name, commit_id=commit_id)
136 136
137 137 raise HTTPFound(files_url)
138 138
139 139 def _get_commit_and_path(self):
140 140 default_commit_id = self.db_repo.landing_rev[1]
141 141 default_f_path = '/'
142 142
143 143 commit_id = self.request.matchdict.get(
144 144 'commit_id', default_commit_id)
145 145 f_path = self._get_f_path(self.request.matchdict, default_f_path)
146 146 return commit_id, f_path
147 147
148 148 def _get_default_encoding(self, c):
149 149 enc_list = getattr(c, 'default_encodings', [])
150 150 return enc_list[0] if enc_list else 'UTF-8'
151 151
152 152 def _get_commit_or_redirect(self, commit_id, redirect_after=True):
153 153 """
154 154 This is a safe way to get commit. If an error occurs it redirects to
155 155 tip with proper message
156 156
157 157 :param commit_id: id of commit to fetch
158 158 :param redirect_after: toggle redirection
159 159 """
160 160 _ = self.request.translate
161 161
162 162 try:
163 163 return self.rhodecode_vcs_repo.get_commit(commit_id)
164 164 except EmptyRepositoryError:
165 165 if not redirect_after:
166 166 return None
167 167
168 168 _url = h.route_path(
169 169 'repo_files_add_file',
170 170 repo_name=self.db_repo_name, commit_id=0, f_path='')
171 171
172 172 if h.HasRepoPermissionAny(
173 173 'repository.write', 'repository.admin')(self.db_repo_name):
174 174 add_new = h.link_to(
175 175 _('Click here to add a new file.'), _url, class_="alert-link")
176 176 else:
177 177 add_new = ""
178 178
179 179 h.flash(h.literal(
180 180 _('There are no files yet. %s') % add_new), category='warning')
181 181 raise HTTPFound(
182 182 h.route_path('repo_summary', repo_name=self.db_repo_name))
183 183
184 184 except (CommitDoesNotExistError, LookupError):
185 185 msg = _('No such commit exists for this repository')
186 186 h.flash(msg, category='error')
187 187 raise HTTPNotFound()
188 188 except RepositoryError as e:
189 189 h.flash(safe_str(h.escape(e)), category='error')
190 190 raise HTTPNotFound()
191 191
192 192 def _get_filenode_or_redirect(self, commit_obj, path):
193 193 """
194 194 Returns file_node, if error occurs or given path is directory,
195 195 it'll redirect to top level path
196 196 """
197 197 _ = self.request.translate
198 198
199 199 try:
200 200 file_node = commit_obj.get_node(path)
201 201 if file_node.is_dir():
202 202 raise RepositoryError('The given path is a directory')
203 203 except CommitDoesNotExistError:
204 204 log.exception('No such commit exists for this repository')
205 205 h.flash(_('No such commit exists for this repository'), category='error')
206 206 raise HTTPNotFound()
207 207 except RepositoryError as e:
208 208 log.warning('Repository error while fetching filenode `%s`. Err:%s', path, e)
209 209 h.flash(safe_str(h.escape(e)), category='error')
210 210 raise HTTPNotFound()
211 211
212 212 return file_node
213 213
214 214 def _is_valid_head(self, commit_id, repo):
215 215 branch_name = sha_commit_id = ''
216 216 is_head = False
217 217 log.debug('Checking if commit_id `%s` is a head for %s.', commit_id, repo)
218 218
219 219 for _branch_name, branch_commit_id in repo.branches.items():
220 220 # simple case we pass in branch name, it's a HEAD
221 221 if commit_id == _branch_name:
222 222 is_head = True
223 223 branch_name = _branch_name
224 224 sha_commit_id = branch_commit_id
225 225 break
226 226 # case when we pass in full sha commit_id, which is a head
227 227 elif commit_id == branch_commit_id:
228 228 is_head = True
229 229 branch_name = _branch_name
230 230 sha_commit_id = branch_commit_id
231 231 break
232 232
233 233 if h.is_svn(repo) and not repo.is_empty():
234 234 # Note: Subversion only has one head.
235 235 if commit_id == repo.get_commit(commit_idx=-1).raw_id:
236 236 is_head = True
237 237 return branch_name, sha_commit_id, is_head
238 238
239 239 # checked branches, means we only need to try to get the branch/commit_sha
240 240 if not repo.is_empty():
241 241 commit = repo.get_commit(commit_id=commit_id)
242 242 if commit:
243 243 branch_name = commit.branch
244 244 sha_commit_id = commit.raw_id
245 245
246 246 return branch_name, sha_commit_id, is_head
247 247
248 def _get_tree_at_commit(self, c, commit_id, f_path, full_load=False):
248 def _get_tree_at_commit(self, c, commit_id, f_path, full_load=False, at_rev=None):
249 249
250 250 repo_id = self.db_repo.repo_id
251 251 force_recache = self.get_recache_flag()
252 252
253 253 cache_seconds = safe_int(
254 254 rhodecode.CONFIG.get('rc_cache.cache_repo.expiration_time'))
255 255 cache_on = not force_recache and cache_seconds > 0
256 256 log.debug(
257 257 'Computing FILE TREE for repo_id %s commit_id `%s` and path `%s`'
258 258 'with caching: %s[TTL: %ss]' % (
259 259 repo_id, commit_id, f_path, cache_on, cache_seconds or 0))
260 260
261 261 cache_namespace_uid = 'cache_repo.{}'.format(repo_id)
262 262 region = rc_cache.get_or_create_region('cache_repo', cache_namespace_uid)
263 263
264 264 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid,
265 265 condition=cache_on)
266 def compute_file_tree(ver, repo_id, commit_id, f_path, full_load):
266 def compute_file_tree(ver, repo_id, commit_id, f_path, full_load, at_rev):
267 267 log.debug('Generating cached file tree at ver:%s for repo_id: %s, %s, %s',
268 268 ver, repo_id, commit_id, f_path)
269 269
270 270 c.full_load = full_load
271 271 return render(
272 272 'rhodecode:templates/files/files_browser_tree.mako',
273 self._get_template_context(c), self.request)
273 self._get_template_context(c), self.request, at_rev)
274 274
275 275 return compute_file_tree(
276 rc_cache.FILE_TREE_CACHE_VER, self.db_repo.repo_id, commit_id, f_path, full_load)
276 rc_cache.FILE_TREE_CACHE_VER, self.db_repo.repo_id, commit_id,
277 f_path, full_load, at_rev)
277 278
278 279 def _get_archive_spec(self, fname):
279 280 log.debug('Detecting archive spec for: `%s`', fname)
280 281
281 282 fileformat = None
282 283 ext = None
283 284 content_type = None
284 285 for a_type, content_type, extension in settings.ARCHIVE_SPECS:
285 286
286 287 if fname.endswith(extension):
287 288 fileformat = a_type
288 289 log.debug('archive is of type: %s', fileformat)
289 290 ext = extension
290 291 break
291 292
292 293 if not fileformat:
293 294 raise ValueError()
294 295
295 296 # left over part of whole fname is the commit
296 297 commit_id = fname[:-len(ext)]
297 298
298 299 return commit_id, ext, fileformat, content_type
299 300
300 301 def create_pure_path(self, *parts):
301 302 # Split paths and sanitize them, removing any ../ etc
302 303 sanitized_path = [
303 304 x for x in pathlib2.PurePath(*parts).parts
304 305 if x not in ['.', '..']]
305 306
306 307 pure_path = pathlib2.PurePath(*sanitized_path)
307 308 return pure_path
308 309
309 310 def _is_lf_enabled(self, target_repo):
310 311 lf_enabled = False
311 312
312 313 lf_key_for_vcs_map = {
313 314 'hg': 'extensions_largefiles',
314 315 'git': 'vcs_git_lfs_enabled'
315 316 }
316 317
317 318 lf_key_for_vcs = lf_key_for_vcs_map.get(target_repo.repo_type)
318 319
319 320 if lf_key_for_vcs:
320 321 lf_enabled = self._get_repo_setting(target_repo, lf_key_for_vcs)
321 322
322 323 return lf_enabled
323 324
324 325 @LoginRequired()
325 326 @HasRepoPermissionAnyDecorator(
326 327 'repository.read', 'repository.write', 'repository.admin')
327 328 @view_config(
328 329 route_name='repo_archivefile', request_method='GET',
329 330 renderer=None)
330 331 def repo_archivefile(self):
331 332 # archive cache config
332 333 from rhodecode import CONFIG
333 334 _ = self.request.translate
334 335 self.load_default_context()
335 336 default_at_path = '/'
336 337 fname = self.request.matchdict['fname']
337 338 subrepos = self.request.GET.get('subrepos') == 'true'
338 339 at_path = self.request.GET.get('at_path') or default_at_path
339 340
340 341 if not self.db_repo.enable_downloads:
341 342 return Response(_('Downloads disabled'))
342 343
343 344 try:
344 345 commit_id, ext, fileformat, content_type = \
345 346 self._get_archive_spec(fname)
346 347 except ValueError:
347 348 return Response(_('Unknown archive type for: `{}`').format(
348 349 h.escape(fname)))
349 350
350 351 try:
351 352 commit = self.rhodecode_vcs_repo.get_commit(commit_id)
352 353 except CommitDoesNotExistError:
353 354 return Response(_('Unknown commit_id {}').format(
354 355 h.escape(commit_id)))
355 356 except EmptyRepositoryError:
356 357 return Response(_('Empty repository'))
357 358
358 359 try:
359 360 at_path = commit.get_node(at_path).path or default_at_path
360 361 except Exception:
361 362 return Response(_('No node at path {} for this repository').format(at_path))
362 363
363 364 path_sha = sha1(at_path)[:8]
364 365
365 366 # original backward compat name of archive
366 367 clean_name = safe_str(self.db_repo_name.replace('/', '_'))
367 368 short_sha = safe_str(commit.short_id)
368 369
369 370 if at_path == default_at_path:
370 371 archive_name = '{}-{}{}{}'.format(
371 372 clean_name,
372 373 '-sub' if subrepos else '',
373 374 short_sha,
374 375 ext)
375 376 # custom path and new name
376 377 else:
377 378 archive_name = '{}-{}{}-{}{}'.format(
378 379 clean_name,
379 380 '-sub' if subrepos else '',
380 381 short_sha,
381 382 path_sha,
382 383 ext)
383 384
384 385 use_cached_archive = False
385 386 archive_cache_enabled = CONFIG.get(
386 387 'archive_cache_dir') and not self.request.GET.get('no_cache')
387 388 cached_archive_path = None
388 389
389 390 if archive_cache_enabled:
390 391 # check if we it's ok to write
391 392 if not os.path.isdir(CONFIG['archive_cache_dir']):
392 393 os.makedirs(CONFIG['archive_cache_dir'])
393 394 cached_archive_path = os.path.join(
394 395 CONFIG['archive_cache_dir'], archive_name)
395 396 if os.path.isfile(cached_archive_path):
396 397 log.debug('Found cached archive in %s', cached_archive_path)
397 398 fd, archive = None, cached_archive_path
398 399 use_cached_archive = True
399 400 else:
400 401 log.debug('Archive %s is not yet cached', archive_name)
401 402
402 403 if not use_cached_archive:
403 404 # generate new archive
404 405 fd, archive = tempfile.mkstemp()
405 406 log.debug('Creating new temp archive in %s', archive)
406 407 try:
407 408 commit.archive_repo(archive, kind=fileformat, subrepos=subrepos,
408 409 archive_at_path=at_path)
409 410 except ImproperArchiveTypeError:
410 411 return _('Unknown archive type')
411 412 if archive_cache_enabled:
412 413 # if we generated the archive and we have cache enabled
413 414 # let's use this for future
414 415 log.debug('Storing new archive in %s', cached_archive_path)
415 416 shutil.move(archive, cached_archive_path)
416 417 archive = cached_archive_path
417 418
418 419 # store download action
419 420 audit_logger.store_web(
420 421 'repo.archive.download', action_data={
421 422 'user_agent': self.request.user_agent,
422 423 'archive_name': archive_name,
423 424 'archive_spec': fname,
424 425 'archive_cached': use_cached_archive},
425 426 user=self._rhodecode_user,
426 427 repo=self.db_repo,
427 428 commit=True
428 429 )
429 430
430 431 def get_chunked_archive(archive_path):
431 432 with open(archive_path, 'rb') as stream:
432 433 while True:
433 434 data = stream.read(16 * 1024)
434 435 if not data:
435 436 if fd: # fd means we used temporary file
436 437 os.close(fd)
437 438 if not archive_cache_enabled:
438 439 log.debug('Destroying temp archive %s', archive_path)
439 440 os.remove(archive_path)
440 441 break
441 442 yield data
442 443
443 444 response = Response(app_iter=get_chunked_archive(archive))
444 445 response.content_disposition = str(
445 446 'attachment; filename=%s' % archive_name)
446 447 response.content_type = str(content_type)
447 448
448 449 return response
449 450
450 451 def _get_file_node(self, commit_id, f_path):
451 452 if commit_id not in ['', None, 'None', '0' * 12, '0' * 40]:
452 453 commit = self.rhodecode_vcs_repo.get_commit(commit_id=commit_id)
453 454 try:
454 455 node = commit.get_node(f_path)
455 456 if node.is_dir():
456 457 raise NodeError('%s path is a %s not a file'
457 458 % (node, type(node)))
458 459 except NodeDoesNotExistError:
459 460 commit = EmptyCommit(
460 461 commit_id=commit_id,
461 462 idx=commit.idx,
462 463 repo=commit.repository,
463 464 alias=commit.repository.alias,
464 465 message=commit.message,
465 466 author=commit.author,
466 467 date=commit.date)
467 468 node = FileNode(f_path, '', commit=commit)
468 469 else:
469 470 commit = EmptyCommit(
470 471 repo=self.rhodecode_vcs_repo,
471 472 alias=self.rhodecode_vcs_repo.alias)
472 473 node = FileNode(f_path, '', commit=commit)
473 474 return node
474 475
475 476 @LoginRequired()
476 477 @HasRepoPermissionAnyDecorator(
477 478 'repository.read', 'repository.write', 'repository.admin')
478 479 @view_config(
479 480 route_name='repo_files_diff', request_method='GET',
480 481 renderer=None)
481 482 def repo_files_diff(self):
482 483 c = self.load_default_context()
483 484 f_path = self._get_f_path(self.request.matchdict)
484 485 diff1 = self.request.GET.get('diff1', '')
485 486 diff2 = self.request.GET.get('diff2', '')
486 487
487 488 path1, diff1 = parse_path_ref(diff1, default_path=f_path)
488 489
489 490 ignore_whitespace = str2bool(self.request.GET.get('ignorews'))
490 491 line_context = self.request.GET.get('context', 3)
491 492
492 493 if not any((diff1, diff2)):
493 494 h.flash(
494 495 'Need query parameter "diff1" or "diff2" to generate a diff.',
495 496 category='error')
496 497 raise HTTPBadRequest()
497 498
498 499 c.action = self.request.GET.get('diff')
499 500 if c.action not in ['download', 'raw']:
500 501 compare_url = h.route_path(
501 502 'repo_compare',
502 503 repo_name=self.db_repo_name,
503 504 source_ref_type='rev',
504 505 source_ref=diff1,
505 506 target_repo=self.db_repo_name,
506 507 target_ref_type='rev',
507 508 target_ref=diff2,
508 509 _query=dict(f_path=f_path))
509 510 # redirect to new view if we render diff
510 511 raise HTTPFound(compare_url)
511 512
512 513 try:
513 514 node1 = self._get_file_node(diff1, path1)
514 515 node2 = self._get_file_node(diff2, f_path)
515 516 except (RepositoryError, NodeError):
516 517 log.exception("Exception while trying to get node from repository")
517 518 raise HTTPFound(
518 519 h.route_path('repo_files', repo_name=self.db_repo_name,
519 520 commit_id='tip', f_path=f_path))
520 521
521 522 if all(isinstance(node.commit, EmptyCommit)
522 523 for node in (node1, node2)):
523 524 raise HTTPNotFound()
524 525
525 526 c.commit_1 = node1.commit
526 527 c.commit_2 = node2.commit
527 528
528 529 if c.action == 'download':
529 530 _diff = diffs.get_gitdiff(node1, node2,
530 531 ignore_whitespace=ignore_whitespace,
531 532 context=line_context)
532 533 diff = diffs.DiffProcessor(_diff, format='gitdiff')
533 534
534 535 response = Response(self.path_filter.get_raw_patch(diff))
535 536 response.content_type = 'text/plain'
536 537 response.content_disposition = (
537 538 'attachment; filename=%s_%s_vs_%s.diff' % (f_path, diff1, diff2)
538 539 )
539 540 charset = self._get_default_encoding(c)
540 541 if charset:
541 542 response.charset = charset
542 543 return response
543 544
544 545 elif c.action == 'raw':
545 546 _diff = diffs.get_gitdiff(node1, node2,
546 547 ignore_whitespace=ignore_whitespace,
547 548 context=line_context)
548 549 diff = diffs.DiffProcessor(_diff, format='gitdiff')
549 550
550 551 response = Response(self.path_filter.get_raw_patch(diff))
551 552 response.content_type = 'text/plain'
552 553 charset = self._get_default_encoding(c)
553 554 if charset:
554 555 response.charset = charset
555 556 return response
556 557
557 558 # in case we ever end up here
558 559 raise HTTPNotFound()
559 560
560 561 @LoginRequired()
561 562 @HasRepoPermissionAnyDecorator(
562 563 'repository.read', 'repository.write', 'repository.admin')
563 564 @view_config(
564 565 route_name='repo_files_diff_2way_redirect', request_method='GET',
565 566 renderer=None)
566 567 def repo_files_diff_2way_redirect(self):
567 568 """
568 569 Kept only to make OLD links work
569 570 """
570 571 f_path = self._get_f_path_unchecked(self.request.matchdict)
571 572 diff1 = self.request.GET.get('diff1', '')
572 573 diff2 = self.request.GET.get('diff2', '')
573 574
574 575 if not any((diff1, diff2)):
575 576 h.flash(
576 577 'Need query parameter "diff1" or "diff2" to generate a diff.',
577 578 category='error')
578 579 raise HTTPBadRequest()
579 580
580 581 compare_url = h.route_path(
581 582 'repo_compare',
582 583 repo_name=self.db_repo_name,
583 584 source_ref_type='rev',
584 585 source_ref=diff1,
585 586 target_ref_type='rev',
586 587 target_ref=diff2,
587 588 _query=dict(f_path=f_path, diffmode='sideside',
588 589 target_repo=self.db_repo_name,))
589 590 raise HTTPFound(compare_url)
590 591
591 592 @LoginRequired()
592 593 @HasRepoPermissionAnyDecorator(
593 594 'repository.read', 'repository.write', 'repository.admin')
594 595 @view_config(
595 596 route_name='repo_files', request_method='GET',
596 597 renderer=None)
597 598 @view_config(
598 599 route_name='repo_files:default_path', request_method='GET',
599 600 renderer=None)
600 601 @view_config(
601 602 route_name='repo_files:default_commit', request_method='GET',
602 603 renderer=None)
603 604 @view_config(
604 605 route_name='repo_files:rendered', request_method='GET',
605 606 renderer=None)
606 607 @view_config(
607 608 route_name='repo_files:annotated', request_method='GET',
608 609 renderer=None)
609 610 def repo_files(self):
610 611 c = self.load_default_context()
611 612
612 613 view_name = getattr(self.request.matched_route, 'name', None)
613 614
614 615 c.annotate = view_name == 'repo_files:annotated'
615 616 # default is false, but .rst/.md files later are auto rendered, we can
616 617 # overwrite auto rendering by setting this GET flag
617 618 c.renderer = view_name == 'repo_files:rendered' or \
618 619 not self.request.GET.get('no-render', False)
619 620
620 # redirect to given commit_id from form if given
621 get_commit_id = self.request.GET.get('at_rev', None)
622 if get_commit_id:
623 self._get_commit_or_redirect(get_commit_id)
621 commit_id, f_path = self._get_commit_and_path()
624 622
625 commit_id, f_path = self._get_commit_and_path()
626 623 c.commit = self._get_commit_or_redirect(commit_id)
627 624 c.branch = self.request.GET.get('branch', None)
628 625 c.f_path = f_path
626 at_rev = self.request.GET.get('at')
629 627
630 628 # prev link
631 629 try:
632 630 prev_commit = c.commit.prev(c.branch)
633 631 c.prev_commit = prev_commit
634 632 c.url_prev = h.route_path(
635 633 'repo_files', repo_name=self.db_repo_name,
636 634 commit_id=prev_commit.raw_id, f_path=f_path)
637 635 if c.branch:
638 636 c.url_prev += '?branch=%s' % c.branch
639 637 except (CommitDoesNotExistError, VCSError):
640 638 c.url_prev = '#'
641 639 c.prev_commit = EmptyCommit()
642 640
643 641 # next link
644 642 try:
645 643 next_commit = c.commit.next(c.branch)
646 644 c.next_commit = next_commit
647 645 c.url_next = h.route_path(
648 646 'repo_files', repo_name=self.db_repo_name,
649 647 commit_id=next_commit.raw_id, f_path=f_path)
650 648 if c.branch:
651 649 c.url_next += '?branch=%s' % c.branch
652 650 except (CommitDoesNotExistError, VCSError):
653 651 c.url_next = '#'
654 652 c.next_commit = EmptyCommit()
655 653
656 654 # files or dirs
657 655 try:
658 656 c.file = c.commit.get_node(f_path)
659 657 c.file_author = True
660 658 c.file_tree = ''
661 659
662 660 # load file content
663 661 if c.file.is_file():
664 662 c.lf_node = {}
665 663
666 664 has_lf_enabled = self._is_lf_enabled(self.db_repo)
667 665 if has_lf_enabled:
668 666 c.lf_node = c.file.get_largefile_node()
669 667
670 668 c.file_source_page = 'true'
671 669 c.file_last_commit = c.file.last_commit
672 670
673 671 c.file_size_too_big = c.file.size > c.visual.cut_off_limit_file
674 672
675 673 if not (c.file_size_too_big or c.file.is_binary):
676 674 if c.annotate: # annotation has precedence over renderer
677 675 c.annotated_lines = filenode_as_annotated_lines_tokens(
678 676 c.file
679 677 )
680 678 else:
681 679 c.renderer = (
682 680 c.renderer and h.renderer_from_filename(c.file.path)
683 681 )
684 682 if not c.renderer:
685 683 c.lines = filenode_as_lines_tokens(c.file)
686 684
687 685 _branch_name, _sha_commit_id, is_head = self._is_valid_head(
688 686 commit_id, self.rhodecode_vcs_repo)
689 687 c.on_branch_head = is_head
690 688
691 689 branch = c.commit.branch if (
692 690 c.commit.branch and '/' not in c.commit.branch) else None
693 691 c.branch_or_raw_id = branch or c.commit.raw_id
694 692 c.branch_name = c.commit.branch or h.short_id(c.commit.raw_id)
695 693
696 694 author = c.file_last_commit.author
697 695 c.authors = [[
698 696 h.email(author),
699 697 h.person(author, 'username_or_name_or_email'),
700 698 1
701 699 ]]
702 700
703 701 else: # load tree content at path
704 702 c.file_source_page = 'false'
705 703 c.authors = []
706 704 # this loads a simple tree without metadata to speed things up
707 705 # later via ajax we call repo_nodetree_full and fetch whole
708 c.file_tree = self._get_tree_at_commit(c, c.commit.raw_id, f_path)
706 c.file_tree = self._get_tree_at_commit(c, c.commit.raw_id, f_path, at_rev=at_rev)
709 707
710 708 c.readme_data, c.readme_file = \
711 709 self._get_readme_data(self.db_repo, c.visual.default_renderer,
712 710 c.commit.raw_id, f_path)
713 711
714 712 except RepositoryError as e:
715 713 h.flash(safe_str(h.escape(e)), category='error')
716 714 raise HTTPNotFound()
717 715
718 716 if self.request.environ.get('HTTP_X_PJAX'):
719 717 html = render('rhodecode:templates/files/files_pjax.mako',
720 718 self._get_template_context(c), self.request)
721 719 else:
722 720 html = render('rhodecode:templates/files/files.mako',
723 721 self._get_template_context(c), self.request)
724 722 return Response(html)
725 723
726 724 @HasRepoPermissionAnyDecorator(
727 725 'repository.read', 'repository.write', 'repository.admin')
728 726 @view_config(
729 727 route_name='repo_files:annotated_previous', request_method='GET',
730 728 renderer=None)
731 729 def repo_files_annotated_previous(self):
732 730 self.load_default_context()
733 731
734 732 commit_id, f_path = self._get_commit_and_path()
735 733 commit = self._get_commit_or_redirect(commit_id)
736 734 prev_commit_id = commit.raw_id
737 735 line_anchor = self.request.GET.get('line_anchor')
738 736 is_file = False
739 737 try:
740 738 _file = commit.get_node(f_path)
741 739 is_file = _file.is_file()
742 740 except (NodeDoesNotExistError, CommitDoesNotExistError, VCSError):
743 741 pass
744 742
745 743 if is_file:
746 744 history = commit.get_path_history(f_path)
747 745 prev_commit_id = history[1].raw_id \
748 746 if len(history) > 1 else prev_commit_id
749 747 prev_url = h.route_path(
750 748 'repo_files:annotated', repo_name=self.db_repo_name,
751 749 commit_id=prev_commit_id, f_path=f_path,
752 750 _anchor='L{}'.format(line_anchor))
753 751
754 752 raise HTTPFound(prev_url)
755 753
756 754 @LoginRequired()
757 755 @HasRepoPermissionAnyDecorator(
758 756 'repository.read', 'repository.write', 'repository.admin')
759 757 @view_config(
760 758 route_name='repo_nodetree_full', request_method='GET',
761 759 renderer=None, xhr=True)
762 760 @view_config(
763 761 route_name='repo_nodetree_full:default_path', request_method='GET',
764 762 renderer=None, xhr=True)
765 763 def repo_nodetree_full(self):
766 764 """
767 765 Returns rendered html of file tree that contains commit date,
768 766 author, commit_id for the specified combination of
769 767 repo, commit_id and file path
770 768 """
771 769 c = self.load_default_context()
772 770
773 771 commit_id, f_path = self._get_commit_and_path()
774 772 commit = self._get_commit_or_redirect(commit_id)
775 773 try:
776 774 dir_node = commit.get_node(f_path)
777 775 except RepositoryError as e:
778 776 return Response('error: {}'.format(h.escape(safe_str(e))))
779 777
780 778 if dir_node.is_file():
781 779 return Response('')
782 780
783 781 c.file = dir_node
784 782 c.commit = commit
783 at_rev = self.request.GET.get('at')
785 784
786 785 html = self._get_tree_at_commit(
787 c, commit.raw_id, dir_node.path, full_load=True)
786 c, commit.raw_id, dir_node.path, full_load=True, at_rev=at_rev)
788 787
789 788 return Response(html)
790 789
791 790 def _get_attachement_headers(self, f_path):
792 791 f_name = safe_str(f_path.split(Repository.NAME_SEP)[-1])
793 792 safe_path = f_name.replace('"', '\\"')
794 793 encoded_path = urllib.quote(f_name)
795 794
796 795 return "attachment; " \
797 796 "filename=\"{}\"; " \
798 797 "filename*=UTF-8\'\'{}".format(safe_path, encoded_path)
799 798
800 799 @LoginRequired()
801 800 @HasRepoPermissionAnyDecorator(
802 801 'repository.read', 'repository.write', 'repository.admin')
803 802 @view_config(
804 803 route_name='repo_file_raw', request_method='GET',
805 804 renderer=None)
806 805 def repo_file_raw(self):
807 806 """
808 807 Action for show as raw, some mimetypes are "rendered",
809 808 those include images, icons.
810 809 """
811 810 c = self.load_default_context()
812 811
813 812 commit_id, f_path = self._get_commit_and_path()
814 813 commit = self._get_commit_or_redirect(commit_id)
815 814 file_node = self._get_filenode_or_redirect(commit, f_path)
816 815
817 816 raw_mimetype_mapping = {
818 817 # map original mimetype to a mimetype used for "show as raw"
819 818 # you can also provide a content-disposition to override the
820 819 # default "attachment" disposition.
821 820 # orig_type: (new_type, new_dispo)
822 821
823 822 # show images inline:
824 823 # Do not re-add SVG: it is unsafe and permits XSS attacks. One can
825 824 # for example render an SVG with javascript inside or even render
826 825 # HTML.
827 826 'image/x-icon': ('image/x-icon', 'inline'),
828 827 'image/png': ('image/png', 'inline'),
829 828 'image/gif': ('image/gif', 'inline'),
830 829 'image/jpeg': ('image/jpeg', 'inline'),
831 830 'application/pdf': ('application/pdf', 'inline'),
832 831 }
833 832
834 833 mimetype = file_node.mimetype
835 834 try:
836 835 mimetype, disposition = raw_mimetype_mapping[mimetype]
837 836 except KeyError:
838 837 # we don't know anything special about this, handle it safely
839 838 if file_node.is_binary:
840 839 # do same as download raw for binary files
841 840 mimetype, disposition = 'application/octet-stream', 'attachment'
842 841 else:
843 842 # do not just use the original mimetype, but force text/plain,
844 843 # otherwise it would serve text/html and that might be unsafe.
845 844 # Note: underlying vcs library fakes text/plain mimetype if the
846 845 # mimetype can not be determined and it thinks it is not
847 846 # binary.This might lead to erroneous text display in some
848 847 # cases, but helps in other cases, like with text files
849 848 # without extension.
850 849 mimetype, disposition = 'text/plain', 'inline'
851 850
852 851 if disposition == 'attachment':
853 852 disposition = self._get_attachement_headers(f_path)
854 853
855 854 stream_content = file_node.stream_bytes()
856 855
857 856 response = Response(app_iter=stream_content)
858 857 response.content_disposition = disposition
859 858 response.content_type = mimetype
860 859
861 860 charset = self._get_default_encoding(c)
862 861 if charset:
863 862 response.charset = charset
864 863
865 864 return response
866 865
867 866 @LoginRequired()
868 867 @HasRepoPermissionAnyDecorator(
869 868 'repository.read', 'repository.write', 'repository.admin')
870 869 @view_config(
871 870 route_name='repo_file_download', request_method='GET',
872 871 renderer=None)
873 872 @view_config(
874 873 route_name='repo_file_download:legacy', request_method='GET',
875 874 renderer=None)
876 875 def repo_file_download(self):
877 876 c = self.load_default_context()
878 877
879 878 commit_id, f_path = self._get_commit_and_path()
880 879 commit = self._get_commit_or_redirect(commit_id)
881 880 file_node = self._get_filenode_or_redirect(commit, f_path)
882 881
883 882 if self.request.GET.get('lf'):
884 883 # only if lf get flag is passed, we download this file
885 884 # as LFS/Largefile
886 885 lf_node = file_node.get_largefile_node()
887 886 if lf_node:
888 887 # overwrite our pointer with the REAL large-file
889 888 file_node = lf_node
890 889
891 890 disposition = self._get_attachement_headers(f_path)
892 891
893 892 stream_content = file_node.stream_bytes()
894 893
895 894 response = Response(app_iter=stream_content)
896 895 response.content_disposition = disposition
897 896 response.content_type = file_node.mimetype
898 897
899 898 charset = self._get_default_encoding(c)
900 899 if charset:
901 900 response.charset = charset
902 901
903 902 return response
904 903
905 904 def _get_nodelist_at_commit(self, repo_name, repo_id, commit_id, f_path):
906 905
907 906 cache_seconds = safe_int(
908 907 rhodecode.CONFIG.get('rc_cache.cache_repo.expiration_time'))
909 908 cache_on = cache_seconds > 0
910 909 log.debug(
911 910 'Computing FILE SEARCH for repo_id %s commit_id `%s` and path `%s`'
912 911 'with caching: %s[TTL: %ss]' % (
913 912 repo_id, commit_id, f_path, cache_on, cache_seconds or 0))
914 913
915 914 cache_namespace_uid = 'cache_repo.{}'.format(repo_id)
916 915 region = rc_cache.get_or_create_region('cache_repo', cache_namespace_uid)
917 916
918 917 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid,
919 918 condition=cache_on)
920 919 def compute_file_search(repo_id, commit_id, f_path):
921 920 log.debug('Generating cached nodelist for repo_id:%s, %s, %s',
922 921 repo_id, commit_id, f_path)
923 922 try:
924 923 _d, _f = ScmModel().get_quick_filter_nodes(repo_name, commit_id, f_path)
925 924 except (RepositoryError, CommitDoesNotExistError, Exception) as e:
926 925 log.exception(safe_str(e))
927 926 h.flash(safe_str(h.escape(e)), category='error')
928 927 raise HTTPFound(h.route_path(
929 928 'repo_files', repo_name=self.db_repo_name,
930 929 commit_id='tip', f_path='/'))
931 930
932 931 return _d + _f
933 932
934 933 result = compute_file_search(self.db_repo.repo_id, commit_id, f_path)
935 934 return filter(lambda n: self.path_filter.path_access_allowed(n['name']), result)
936 935
937 936 @LoginRequired()
938 937 @HasRepoPermissionAnyDecorator(
939 938 'repository.read', 'repository.write', 'repository.admin')
940 939 @view_config(
941 940 route_name='repo_files_nodelist', request_method='GET',
942 941 renderer='json_ext', xhr=True)
943 942 def repo_nodelist(self):
944 943 self.load_default_context()
945 944
946 945 commit_id, f_path = self._get_commit_and_path()
947 946 commit = self._get_commit_or_redirect(commit_id)
948 947
949 948 metadata = self._get_nodelist_at_commit(
950 949 self.db_repo_name, self.db_repo.repo_id, commit.raw_id, f_path)
951 950 return {'nodes': metadata}
952 951
953 952 def _create_references(self, branches_or_tags, symbolic_reference, f_path, ref_type):
954 953 items = []
955 954 for name, commit_id in branches_or_tags.items():
956 955 sym_ref = symbolic_reference(commit_id, name, f_path, ref_type)
957 956 items.append((sym_ref, name, ref_type))
958 957 return items
959 958
960 959 def _symbolic_reference(self, commit_id, name, f_path, ref_type):
961 960 return commit_id
962 961
963 962 def _symbolic_reference_svn(self, commit_id, name, f_path, ref_type):
964 963 return commit_id
965 964
966 965 # NOTE(dan): old code we used in "diff" mode compare
967 966 new_f_path = vcspath.join(name, f_path)
968 967 return u'%s@%s' % (new_f_path, commit_id)
969 968
970 969 def _get_node_history(self, commit_obj, f_path, commits=None):
971 970 """
972 971 get commit history for given node
973 972
974 973 :param commit_obj: commit to calculate history
975 974 :param f_path: path for node to calculate history for
976 975 :param commits: if passed don't calculate history and take
977 976 commits defined in this list
978 977 """
979 978 _ = self.request.translate
980 979
981 980 # calculate history based on tip
982 981 tip = self.rhodecode_vcs_repo.get_commit()
983 982 if commits is None:
984 983 pre_load = ["author", "branch"]
985 984 try:
986 985 commits = tip.get_path_history(f_path, pre_load=pre_load)
987 986 except (NodeDoesNotExistError, CommitError):
988 987 # this node is not present at tip!
989 988 commits = commit_obj.get_path_history(f_path, pre_load=pre_load)
990 989
991 990 history = []
992 991 commits_group = ([], _("Changesets"))
993 992 for commit in commits:
994 993 branch = ' (%s)' % commit.branch if commit.branch else ''
995 994 n_desc = 'r%s:%s%s' % (commit.idx, commit.short_id, branch)
996 995 commits_group[0].append((commit.raw_id, n_desc, 'sha'))
997 996 history.append(commits_group)
998 997
999 998 symbolic_reference = self._symbolic_reference
1000 999
1001 1000 if self.rhodecode_vcs_repo.alias == 'svn':
1002 1001 adjusted_f_path = RepoFilesView.adjust_file_path_for_svn(
1003 1002 f_path, self.rhodecode_vcs_repo)
1004 1003 if adjusted_f_path != f_path:
1005 1004 log.debug(
1006 1005 'Recognized svn tag or branch in file "%s", using svn '
1007 1006 'specific symbolic references', f_path)
1008 1007 f_path = adjusted_f_path
1009 1008 symbolic_reference = self._symbolic_reference_svn
1010 1009
1011 1010 branches = self._create_references(
1012 1011 self.rhodecode_vcs_repo.branches, symbolic_reference, f_path, 'branch')
1013 1012 branches_group = (branches, _("Branches"))
1014 1013
1015 1014 tags = self._create_references(
1016 1015 self.rhodecode_vcs_repo.tags, symbolic_reference, f_path, 'tag')
1017 1016 tags_group = (tags, _("Tags"))
1018 1017
1019 1018 history.append(branches_group)
1020 1019 history.append(tags_group)
1021 1020
1022 1021 return history, commits
1023 1022
1024 1023 @LoginRequired()
1025 1024 @HasRepoPermissionAnyDecorator(
1026 1025 'repository.read', 'repository.write', 'repository.admin')
1027 1026 @view_config(
1028 1027 route_name='repo_file_history', request_method='GET',
1029 1028 renderer='json_ext')
1030 1029 def repo_file_history(self):
1031 1030 self.load_default_context()
1032 1031
1033 1032 commit_id, f_path = self._get_commit_and_path()
1034 1033 commit = self._get_commit_or_redirect(commit_id)
1035 1034 file_node = self._get_filenode_or_redirect(commit, f_path)
1036 1035
1037 1036 if file_node.is_file():
1038 1037 file_history, _hist = self._get_node_history(commit, f_path)
1039 1038
1040 1039 res = []
1041 for obj in file_history:
1040 for section_items, section in file_history:
1041 items = []
1042 for obj_id, obj_text, obj_type in section_items:
1043 at_rev = ''
1044 if obj_type in ['branch', 'bookmark', 'tag']:
1045 at_rev = obj_text
1046 entry = {
1047 'id': obj_id,
1048 'text': obj_text,
1049 'type': obj_type,
1050 'at_rev': at_rev
1051 }
1052
1053 items.append(entry)
1054
1042 1055 res.append({
1043 'text': obj[1],
1044 'children': [{'id': o[0], 'text': o[1], 'type': o[2]} for o in obj[0]]
1056 'text': section,
1057 'children': items
1045 1058 })
1046 1059
1047 1060 data = {
1048 1061 'more': False,
1049 1062 'results': res
1050 1063 }
1051 1064 return data
1052 1065
1053 1066 log.warning('Cannot fetch history for directory')
1054 1067 raise HTTPBadRequest()
1055 1068
1056 1069 @LoginRequired()
1057 1070 @HasRepoPermissionAnyDecorator(
1058 1071 'repository.read', 'repository.write', 'repository.admin')
1059 1072 @view_config(
1060 1073 route_name='repo_file_authors', request_method='GET',
1061 1074 renderer='rhodecode:templates/files/file_authors_box.mako')
1062 1075 def repo_file_authors(self):
1063 1076 c = self.load_default_context()
1064 1077
1065 1078 commit_id, f_path = self._get_commit_and_path()
1066 1079 commit = self._get_commit_or_redirect(commit_id)
1067 1080 file_node = self._get_filenode_or_redirect(commit, f_path)
1068 1081
1069 1082 if not file_node.is_file():
1070 1083 raise HTTPBadRequest()
1071 1084
1072 1085 c.file_last_commit = file_node.last_commit
1073 1086 if self.request.GET.get('annotate') == '1':
1074 1087 # use _hist from annotation if annotation mode is on
1075 1088 commit_ids = set(x[1] for x in file_node.annotate)
1076 1089 _hist = (
1077 1090 self.rhodecode_vcs_repo.get_commit(commit_id)
1078 1091 for commit_id in commit_ids)
1079 1092 else:
1080 1093 _f_history, _hist = self._get_node_history(commit, f_path)
1081 1094 c.file_author = False
1082 1095
1083 1096 unique = collections.OrderedDict()
1084 1097 for commit in _hist:
1085 1098 author = commit.author
1086 1099 if author not in unique:
1087 1100 unique[commit.author] = [
1088 1101 h.email(author),
1089 1102 h.person(author, 'username_or_name_or_email'),
1090 1103 1 # counter
1091 1104 ]
1092 1105
1093 1106 else:
1094 1107 # increase counter
1095 1108 unique[commit.author][2] += 1
1096 1109
1097 1110 c.authors = [val for val in unique.values()]
1098 1111
1099 1112 return self._get_template_context(c)
1100 1113
1101 1114 @LoginRequired()
1102 1115 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1103 1116 @view_config(
1104 1117 route_name='repo_files_remove_file', request_method='GET',
1105 1118 renderer='rhodecode:templates/files/files_delete.mako')
1106 1119 def repo_files_remove_file(self):
1107 1120 _ = self.request.translate
1108 1121 c = self.load_default_context()
1109 1122 commit_id, f_path = self._get_commit_and_path()
1110 1123
1111 1124 self._ensure_not_locked()
1112 1125 _branch_name, _sha_commit_id, is_head = \
1113 1126 self._is_valid_head(commit_id, self.rhodecode_vcs_repo)
1114 1127
1115 1128 self.forbid_non_head(is_head, f_path)
1116 1129 self.check_branch_permission(_branch_name)
1117 1130
1118 1131 c.commit = self._get_commit_or_redirect(commit_id)
1119 1132 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1120 1133
1121 1134 c.default_message = _(
1122 1135 'Deleted file {} via RhodeCode Enterprise').format(f_path)
1123 1136 c.f_path = f_path
1124 1137
1125 1138 return self._get_template_context(c)
1126 1139
1127 1140 @LoginRequired()
1128 1141 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1129 1142 @CSRFRequired()
1130 1143 @view_config(
1131 1144 route_name='repo_files_delete_file', request_method='POST',
1132 1145 renderer=None)
1133 1146 def repo_files_delete_file(self):
1134 1147 _ = self.request.translate
1135 1148
1136 1149 c = self.load_default_context()
1137 1150 commit_id, f_path = self._get_commit_and_path()
1138 1151
1139 1152 self._ensure_not_locked()
1140 1153 _branch_name, _sha_commit_id, is_head = \
1141 1154 self._is_valid_head(commit_id, self.rhodecode_vcs_repo)
1142 1155
1143 1156 self.forbid_non_head(is_head, f_path)
1144 1157 self.check_branch_permission(_branch_name)
1145 1158
1146 1159 c.commit = self._get_commit_or_redirect(commit_id)
1147 1160 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1148 1161
1149 1162 c.default_message = _(
1150 1163 'Deleted file {} via RhodeCode Enterprise').format(f_path)
1151 1164 c.f_path = f_path
1152 1165 node_path = f_path
1153 1166 author = self._rhodecode_db_user.full_contact
1154 1167 message = self.request.POST.get('message') or c.default_message
1155 1168 try:
1156 1169 nodes = {
1157 1170 node_path: {
1158 1171 'content': ''
1159 1172 }
1160 1173 }
1161 1174 ScmModel().delete_nodes(
1162 1175 user=self._rhodecode_db_user.user_id, repo=self.db_repo,
1163 1176 message=message,
1164 1177 nodes=nodes,
1165 1178 parent_commit=c.commit,
1166 1179 author=author,
1167 1180 )
1168 1181
1169 1182 h.flash(
1170 1183 _('Successfully deleted file `{}`').format(
1171 1184 h.escape(f_path)), category='success')
1172 1185 except Exception:
1173 1186 log.exception('Error during commit operation')
1174 1187 h.flash(_('Error occurred during commit'), category='error')
1175 1188 raise HTTPFound(
1176 1189 h.route_path('repo_commit', repo_name=self.db_repo_name,
1177 1190 commit_id='tip'))
1178 1191
1179 1192 @LoginRequired()
1180 1193 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1181 1194 @view_config(
1182 1195 route_name='repo_files_edit_file', request_method='GET',
1183 1196 renderer='rhodecode:templates/files/files_edit.mako')
1184 1197 def repo_files_edit_file(self):
1185 1198 _ = self.request.translate
1186 1199 c = self.load_default_context()
1187 1200 commit_id, f_path = self._get_commit_and_path()
1188 1201
1189 1202 self._ensure_not_locked()
1190 1203 _branch_name, _sha_commit_id, is_head = \
1191 1204 self._is_valid_head(commit_id, self.rhodecode_vcs_repo)
1192 1205
1193 1206 self.forbid_non_head(is_head, f_path, commit_id=commit_id)
1194 1207 self.check_branch_permission(_branch_name, commit_id=commit_id)
1195 1208
1196 1209 c.commit = self._get_commit_or_redirect(commit_id)
1197 1210 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1198 1211
1199 1212 if c.file.is_binary:
1200 1213 files_url = h.route_path(
1201 1214 'repo_files',
1202 1215 repo_name=self.db_repo_name,
1203 1216 commit_id=c.commit.raw_id, f_path=f_path)
1204 1217 raise HTTPFound(files_url)
1205 1218
1206 1219 c.default_message = _('Edited file {} via RhodeCode Enterprise').format(f_path)
1207 1220 c.f_path = f_path
1208 1221
1209 1222 return self._get_template_context(c)
1210 1223
1211 1224 @LoginRequired()
1212 1225 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1213 1226 @CSRFRequired()
1214 1227 @view_config(
1215 1228 route_name='repo_files_update_file', request_method='POST',
1216 1229 renderer=None)
1217 1230 def repo_files_update_file(self):
1218 1231 _ = self.request.translate
1219 1232 c = self.load_default_context()
1220 1233 commit_id, f_path = self._get_commit_and_path()
1221 1234
1222 1235 self._ensure_not_locked()
1223 1236
1224 1237 c.commit = self._get_commit_or_redirect(commit_id)
1225 1238 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1226 1239
1227 1240 if c.file.is_binary:
1228 1241 raise HTTPFound(h.route_path('repo_files', repo_name=self.db_repo_name,
1229 1242 commit_id=c.commit.raw_id, f_path=f_path))
1230 1243
1231 1244 _branch_name, _sha_commit_id, is_head = \
1232 1245 self._is_valid_head(commit_id, self.rhodecode_vcs_repo)
1233 1246
1234 1247 self.forbid_non_head(is_head, f_path, commit_id=commit_id)
1235 1248 self.check_branch_permission(_branch_name, commit_id=commit_id)
1236 1249
1237 1250 c.default_message = _('Edited file {} via RhodeCode Enterprise').format(f_path)
1238 1251 c.f_path = f_path
1239 1252
1240 1253 old_content = c.file.content
1241 1254 sl = old_content.splitlines(1)
1242 1255 first_line = sl[0] if sl else ''
1243 1256
1244 1257 r_post = self.request.POST
1245 1258 # line endings: 0 - Unix, 1 - Mac, 2 - DOS
1246 1259 line_ending_mode = detect_mode(first_line, 0)
1247 1260 content = convert_line_endings(r_post.get('content', ''), line_ending_mode)
1248 1261
1249 1262 message = r_post.get('message') or c.default_message
1250 1263 org_node_path = c.file.unicode_path
1251 1264 filename = r_post['filename']
1252 1265
1253 1266 root_path = c.file.dir_path
1254 1267 pure_path = self.create_pure_path(root_path, filename)
1255 1268 node_path = safe_unicode(bytes(pure_path))
1256 1269
1257 1270 default_redirect_url = h.route_path('repo_commit', repo_name=self.db_repo_name,
1258 1271 commit_id=commit_id)
1259 1272 if content == old_content and node_path == org_node_path:
1260 1273 h.flash(_('No changes detected on {}').format(h.escape(org_node_path)),
1261 1274 category='warning')
1262 1275 raise HTTPFound(default_redirect_url)
1263 1276
1264 1277 try:
1265 1278 mapping = {
1266 1279 org_node_path: {
1267 1280 'org_filename': org_node_path,
1268 1281 'filename': node_path,
1269 1282 'content': content,
1270 1283 'lexer': '',
1271 1284 'op': 'mod',
1272 1285 'mode': c.file.mode
1273 1286 }
1274 1287 }
1275 1288
1276 1289 commit = ScmModel().update_nodes(
1277 1290 user=self._rhodecode_db_user.user_id,
1278 1291 repo=self.db_repo,
1279 1292 message=message,
1280 1293 nodes=mapping,
1281 1294 parent_commit=c.commit,
1282 1295 )
1283 1296
1284 1297 h.flash(_('Successfully committed changes to file `{}`').format(
1285 1298 h.escape(f_path)), category='success')
1286 1299 default_redirect_url = h.route_path(
1287 1300 'repo_commit', repo_name=self.db_repo_name, commit_id=commit.raw_id)
1288 1301
1289 1302 except Exception:
1290 1303 log.exception('Error occurred during commit')
1291 1304 h.flash(_('Error occurred during commit'), category='error')
1292 1305
1293 1306 raise HTTPFound(default_redirect_url)
1294 1307
1295 1308 @LoginRequired()
1296 1309 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1297 1310 @view_config(
1298 1311 route_name='repo_files_add_file', request_method='GET',
1299 1312 renderer='rhodecode:templates/files/files_add.mako')
1300 1313 @view_config(
1301 1314 route_name='repo_files_upload_file', request_method='GET',
1302 1315 renderer='rhodecode:templates/files/files_upload.mako')
1303 1316 def repo_files_add_file(self):
1304 1317 _ = self.request.translate
1305 1318 c = self.load_default_context()
1306 1319 commit_id, f_path = self._get_commit_and_path()
1307 1320
1308 1321 self._ensure_not_locked()
1309 1322
1310 1323 c.commit = self._get_commit_or_redirect(commit_id, redirect_after=False)
1311 1324 if c.commit is None:
1312 1325 c.commit = EmptyCommit(alias=self.rhodecode_vcs_repo.alias)
1313 1326
1314 1327 if self.rhodecode_vcs_repo.is_empty():
1315 1328 # for empty repository we cannot check for current branch, we rely on
1316 1329 # c.commit.branch instead
1317 1330 _branch_name, _sha_commit_id, is_head = c.commit.branch, '', True
1318 1331 else:
1319 1332 _branch_name, _sha_commit_id, is_head = \
1320 1333 self._is_valid_head(commit_id, self.rhodecode_vcs_repo)
1321 1334
1322 1335 self.forbid_non_head(is_head, f_path, commit_id=commit_id)
1323 1336 self.check_branch_permission(_branch_name, commit_id=commit_id)
1324 1337
1325 1338 c.default_message = (_('Added file via RhodeCode Enterprise'))
1326 1339 c.f_path = f_path.lstrip('/') # ensure not relative path
1327 1340
1328 1341 return self._get_template_context(c)
1329 1342
1330 1343 @LoginRequired()
1331 1344 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1332 1345 @CSRFRequired()
1333 1346 @view_config(
1334 1347 route_name='repo_files_create_file', request_method='POST',
1335 1348 renderer=None)
1336 1349 def repo_files_create_file(self):
1337 1350 _ = self.request.translate
1338 1351 c = self.load_default_context()
1339 1352 commit_id, f_path = self._get_commit_and_path()
1340 1353
1341 1354 self._ensure_not_locked()
1342 1355
1343 1356 c.commit = self._get_commit_or_redirect(commit_id, redirect_after=False)
1344 1357 if c.commit is None:
1345 1358 c.commit = EmptyCommit(alias=self.rhodecode_vcs_repo.alias)
1346 1359
1347 1360 # calculate redirect URL
1348 1361 if self.rhodecode_vcs_repo.is_empty():
1349 1362 default_redirect_url = h.route_path(
1350 1363 'repo_summary', repo_name=self.db_repo_name)
1351 1364 else:
1352 1365 default_redirect_url = h.route_path(
1353 1366 'repo_commit', repo_name=self.db_repo_name, commit_id='tip')
1354 1367
1355 1368 if self.rhodecode_vcs_repo.is_empty():
1356 1369 # for empty repository we cannot check for current branch, we rely on
1357 1370 # c.commit.branch instead
1358 1371 _branch_name, _sha_commit_id, is_head = c.commit.branch, '', True
1359 1372 else:
1360 1373 _branch_name, _sha_commit_id, is_head = \
1361 1374 self._is_valid_head(commit_id, self.rhodecode_vcs_repo)
1362 1375
1363 1376 self.forbid_non_head(is_head, f_path, commit_id=commit_id)
1364 1377 self.check_branch_permission(_branch_name, commit_id=commit_id)
1365 1378
1366 1379 c.default_message = (_('Added file via RhodeCode Enterprise'))
1367 1380 c.f_path = f_path
1368 1381
1369 1382 r_post = self.request.POST
1370 1383 message = r_post.get('message') or c.default_message
1371 1384 filename = r_post.get('filename')
1372 1385 unix_mode = 0
1373 1386 content = convert_line_endings(r_post.get('content', ''), unix_mode)
1374 1387
1375 1388 if not filename:
1376 1389 # If there's no commit, redirect to repo summary
1377 1390 if type(c.commit) is EmptyCommit:
1378 1391 redirect_url = h.route_path(
1379 1392 'repo_summary', repo_name=self.db_repo_name)
1380 1393 else:
1381 1394 redirect_url = default_redirect_url
1382 1395 h.flash(_('No filename specified'), category='warning')
1383 1396 raise HTTPFound(redirect_url)
1384 1397
1385 1398 root_path = f_path
1386 1399 pure_path = self.create_pure_path(root_path, filename)
1387 1400 node_path = safe_unicode(bytes(pure_path).lstrip('/'))
1388 1401
1389 1402 author = self._rhodecode_db_user.full_contact
1390 1403 nodes = {
1391 1404 node_path: {
1392 1405 'content': content
1393 1406 }
1394 1407 }
1395 1408
1396 1409 try:
1397 1410
1398 1411 commit = ScmModel().create_nodes(
1399 1412 user=self._rhodecode_db_user.user_id,
1400 1413 repo=self.db_repo,
1401 1414 message=message,
1402 1415 nodes=nodes,
1403 1416 parent_commit=c.commit,
1404 1417 author=author,
1405 1418 )
1406 1419
1407 1420 h.flash(_('Successfully committed new file `{}`').format(
1408 1421 h.escape(node_path)), category='success')
1409 1422
1410 1423 default_redirect_url = h.route_path(
1411 1424 'repo_commit', repo_name=self.db_repo_name, commit_id=commit.raw_id)
1412 1425
1413 1426 except NonRelativePathError:
1414 1427 log.exception('Non Relative path found')
1415 1428 h.flash(_('The location specified must be a relative path and must not '
1416 1429 'contain .. in the path'), category='warning')
1417 1430 raise HTTPFound(default_redirect_url)
1418 1431 except (NodeError, NodeAlreadyExistsError) as e:
1419 1432 h.flash(_(h.escape(e)), category='error')
1420 1433 except Exception:
1421 1434 log.exception('Error occurred during commit')
1422 1435 h.flash(_('Error occurred during commit'), category='error')
1423 1436
1424 1437 raise HTTPFound(default_redirect_url)
1425 1438
1426 1439 @LoginRequired()
1427 1440 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1428 1441 @CSRFRequired()
1429 1442 @view_config(
1430 1443 route_name='repo_files_upload_file', request_method='POST',
1431 1444 renderer='json_ext')
1432 1445 def repo_files_upload_file(self):
1433 1446 _ = self.request.translate
1434 1447 c = self.load_default_context()
1435 1448 commit_id, f_path = self._get_commit_and_path()
1436 1449
1437 1450 self._ensure_not_locked()
1438 1451
1439 1452 c.commit = self._get_commit_or_redirect(commit_id, redirect_after=False)
1440 1453 if c.commit is None:
1441 1454 c.commit = EmptyCommit(alias=self.rhodecode_vcs_repo.alias)
1442 1455
1443 1456 # calculate redirect URL
1444 1457 if self.rhodecode_vcs_repo.is_empty():
1445 1458 default_redirect_url = h.route_path(
1446 1459 'repo_summary', repo_name=self.db_repo_name)
1447 1460 else:
1448 1461 default_redirect_url = h.route_path(
1449 1462 'repo_commit', repo_name=self.db_repo_name, commit_id='tip')
1450 1463
1451 1464 if self.rhodecode_vcs_repo.is_empty():
1452 1465 # for empty repository we cannot check for current branch, we rely on
1453 1466 # c.commit.branch instead
1454 1467 _branch_name, _sha_commit_id, is_head = c.commit.branch, '', True
1455 1468 else:
1456 1469 _branch_name, _sha_commit_id, is_head = \
1457 1470 self._is_valid_head(commit_id, self.rhodecode_vcs_repo)
1458 1471
1459 1472 error = self.forbid_non_head(is_head, f_path, json_mode=True)
1460 1473 if error:
1461 1474 return {
1462 1475 'error': error,
1463 1476 'redirect_url': default_redirect_url
1464 1477 }
1465 1478 error = self.check_branch_permission(_branch_name, json_mode=True)
1466 1479 if error:
1467 1480 return {
1468 1481 'error': error,
1469 1482 'redirect_url': default_redirect_url
1470 1483 }
1471 1484
1472 1485 c.default_message = (_('Uploaded file via RhodeCode Enterprise'))
1473 1486 c.f_path = f_path
1474 1487
1475 1488 r_post = self.request.POST
1476 1489
1477 1490 message = c.default_message
1478 1491 user_message = r_post.getall('message')
1479 1492 if isinstance(user_message, list) and user_message:
1480 1493 # we take the first from duplicated results if it's not empty
1481 1494 message = user_message[0] if user_message[0] else message
1482 1495
1483 1496 nodes = {}
1484 1497
1485 1498 for file_obj in r_post.getall('files_upload') or []:
1486 1499 content = file_obj.file
1487 1500 filename = file_obj.filename
1488 1501
1489 1502 root_path = f_path
1490 1503 pure_path = self.create_pure_path(root_path, filename)
1491 1504 node_path = safe_unicode(bytes(pure_path).lstrip('/'))
1492 1505
1493 1506 nodes[node_path] = {
1494 1507 'content': content
1495 1508 }
1496 1509
1497 1510 if not nodes:
1498 1511 error = 'missing files'
1499 1512 return {
1500 1513 'error': error,
1501 1514 'redirect_url': default_redirect_url
1502 1515 }
1503 1516
1504 1517 author = self._rhodecode_db_user.full_contact
1505 1518
1506 1519 try:
1507 1520 commit = ScmModel().create_nodes(
1508 1521 user=self._rhodecode_db_user.user_id,
1509 1522 repo=self.db_repo,
1510 1523 message=message,
1511 1524 nodes=nodes,
1512 1525 parent_commit=c.commit,
1513 1526 author=author,
1514 1527 )
1515 1528 if len(nodes) == 1:
1516 1529 flash_message = _('Successfully committed {} new files').format(len(nodes))
1517 1530 else:
1518 1531 flash_message = _('Successfully committed 1 new file')
1519 1532
1520 1533 h.flash(flash_message, category='success')
1521 1534
1522 1535 default_redirect_url = h.route_path(
1523 1536 'repo_commit', repo_name=self.db_repo_name, commit_id=commit.raw_id)
1524 1537
1525 1538 except NonRelativePathError:
1526 1539 log.exception('Non Relative path found')
1527 1540 error = _('The location specified must be a relative path and must not '
1528 1541 'contain .. in the path')
1529 1542 h.flash(error, category='warning')
1530 1543
1531 1544 return {
1532 1545 'error': error,
1533 1546 'redirect_url': default_redirect_url
1534 1547 }
1535 1548 except (NodeError, NodeAlreadyExistsError) as e:
1536 1549 error = h.escape(e)
1537 1550 h.flash(error, category='error')
1538 1551
1539 1552 return {
1540 1553 'error': error,
1541 1554 'redirect_url': default_redirect_url
1542 1555 }
1543 1556 except Exception:
1544 1557 log.exception('Error occurred during commit')
1545 1558 error = _('Error occurred during commit')
1546 1559 h.flash(error, category='error')
1547 1560 return {
1548 1561 'error': error,
1549 1562 'redirect_url': default_redirect_url
1550 1563 }
1551 1564
1552 1565 return {
1553 1566 'error': None,
1554 1567 'redirect_url': default_redirect_url
1555 1568 }
@@ -1,81 +1,81 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2015-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 logging
22 22 from dogpile.cache import register_backend
23 23
24 24 register_backend(
25 25 "dogpile.cache.rc.memory_lru", "rhodecode.lib.rc_cache.backends",
26 26 "LRUMemoryBackend")
27 27
28 28 register_backend(
29 29 "dogpile.cache.rc.file_namespace", "rhodecode.lib.rc_cache.backends",
30 30 "FileNamespaceBackend")
31 31
32 32 register_backend(
33 33 "dogpile.cache.rc.redis", "rhodecode.lib.rc_cache.backends",
34 34 "RedisPickleBackend")
35 35
36 36 register_backend(
37 37 "dogpile.cache.rc.redis_msgpack", "rhodecode.lib.rc_cache.backends",
38 38 "RedisMsgPackBackend")
39 39
40 40
41 41 log = logging.getLogger(__name__)
42 42
43 43 from . import region_meta
44 44 from .utils import (
45 45 get_default_cache_settings, backend_key_generator, get_or_create_region,
46 46 clear_cache_namespace, make_region, InvalidationContext,
47 47 FreshRegionCache, ActiveRegionCache)
48 48
49 49
50 FILE_TREE_CACHE_VER = 'v2'
50 FILE_TREE_CACHE_VER = 'v3'
51 51
52 52
53 53 def configure_dogpile_cache(settings):
54 54 cache_dir = settings.get('cache_dir')
55 55 if cache_dir:
56 56 region_meta.dogpile_config_defaults['cache_dir'] = cache_dir
57 57
58 58 rc_cache_data = get_default_cache_settings(settings, prefixes=['rc_cache.'])
59 59
60 60 # inspect available namespaces
61 61 avail_regions = set()
62 62 for key in rc_cache_data.keys():
63 63 namespace_name = key.split('.', 1)[0]
64 64 avail_regions.add(namespace_name)
65 65 log.debug('dogpile: found following cache regions: %s', avail_regions)
66 66
67 67 # register them into namespace
68 68 for region_name in avail_regions:
69 69 new_region = make_region(
70 70 name=region_name,
71 71 function_key_generator=None
72 72 )
73 73
74 74 new_region.configure_from_config(settings, 'rc_cache.{}.'.format(region_name))
75 75 new_region.function_key_generator = backend_key_generator(new_region.actual_backend)
76 76 log.debug('dogpile: registering a new region %s[%s]', region_name, new_region.__dict__)
77 77 region_meta.dogpile_cache_regions[region_name] = new_region
78 78
79 79
80 80 def includeme(config):
81 81 configure_dogpile_cache(config.registry.settings)
@@ -1,519 +1,523 b''
1 1 // # Copyright (C) 2010-2019 RhodeCode GmbH
2 2 // #
3 3 // # This program is free software: you can redistribute it and/or modify
4 4 // # it under the terms of the GNU Affero General Public License, version 3
5 5 // # (only), as published by the Free Software Foundation.
6 6 // #
7 7 // # This program is distributed in the hope that it will be useful,
8 8 // # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 9 // # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 10 // # GNU General Public License for more details.
11 11 // #
12 12 // # You should have received a copy of the GNU Affero General Public License
13 13 // # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 14 // #
15 15 // # This program is dual-licensed. If you wish to learn more about the
16 16 // # RhodeCode Enterprise Edition, including its added features, Support services,
17 17 // # and proprietary license terms, please see https://rhodecode.com/licenses/
18 18
19 19 /**
20 20 * Search file list
21 21 */
22 22
23 23 var NodeFilter = {};
24 24
25 25 var fileBrowserListeners = function (node_list_url, url_base) {
26 26 var $filterInput = $('#node_filter');
27 27 var n_filter = $filterInput.get(0);
28 28
29 29 NodeFilter.filterTimeout = null;
30 30 var nodes = null;
31 31
32 32 NodeFilter.focus = function () {
33 33 $filterInput.focus()
34 34 };
35 35
36 36 NodeFilter.fetchNodes = function (callback) {
37 37 $.ajax(
38 38 {url: node_list_url, headers: {'X-PARTIAL-XHR': true}})
39 39 .done(function (data) {
40 40 nodes = data.nodes;
41 41 if (callback) {
42 42 callback();
43 43 }
44 44 })
45 45 .fail(function (data) {
46 46 console.log('failed to load');
47 47 });
48 48 };
49 49
50 50 NodeFilter.initFilter = function (e) {
51 51 if ($filterInput.hasClass('loading')) {
52 52 return
53 53 }
54 54
55 55 // in case we are already loaded, do nothing
56 56 if (!$filterInput.hasClass('init')) {
57 57 return NodeFilter.handleKey(e);
58 58 }
59 59 var iconLoading = 'icon-spin animate-spin';
60 60 var iconSearch = 'icon-search';
61 61 $('.files-filter-box-path i').removeClass(iconSearch).addClass(iconLoading);
62 62 $filterInput.addClass('loading');
63 63
64 64 var callback = function (org) {
65 65 return function () {
66 66 if ($filterInput.hasClass('init')) {
67 67 $filterInput.removeClass('init');
68 68 $filterInput.removeClass('loading');
69 69 }
70 70 $('.files-filter-box-path i').removeClass(iconLoading).addClass(iconSearch);
71 71
72 72 // auto re-filter if we filled in the input
73 73 if (n_filter.value !== "") {
74 74 NodeFilter.updateFilter(n_filter, e)()
75 75 }
76 76
77 77 }
78 78 };
79 79 // load node data
80 80 NodeFilter.fetchNodes(callback());
81 81
82 82 };
83 83
84 84 NodeFilter.resetFilter = function () {
85 85 $('#tbody').show();
86 86 $('#tbody_filtered').hide();
87 87 $filterInput.val('');
88 88 };
89 89
90 90 NodeFilter.handleKey = function (e) {
91 91 var scrollDown = function (element) {
92 92 var elementBottom = element.offset().top + $(element).outerHeight();
93 93 var windowBottom = window.innerHeight + $(window).scrollTop();
94 94 if (elementBottom > windowBottom) {
95 95 var offset = elementBottom - window.innerHeight;
96 96 $('html,body').scrollTop(offset);
97 97 return false;
98 98 }
99 99 return true;
100 100 };
101 101
102 102 var scrollUp = function (element) {
103 103 if (element.offset().top < $(window).scrollTop()) {
104 104 $('html,body').scrollTop(element.offset().top);
105 105 return false;
106 106 }
107 107 return true;
108 108 };
109 109 var $hlElem = $('.browser-highlight');
110 110
111 111 if (e.keyCode === 40) { // Down
112 112 if ($hlElem.length === 0) {
113 113 $('.browser-result').first().addClass('browser-highlight');
114 114 } else {
115 115 var next = $hlElem.next();
116 116 if (next.length !== 0) {
117 117 $hlElem.removeClass('browser-highlight');
118 118 next.addClass('browser-highlight');
119 119 }
120 120 }
121 121
122 122 if ($hlElem.get(0) !== undefined){
123 123 scrollDown($hlElem);
124 124 }
125 125 }
126 126 if (e.keyCode === 38) { // Up
127 127 e.preventDefault();
128 128 if ($hlElem.length !== 0) {
129 129 var next = $hlElem.prev();
130 130 if (next.length !== 0) {
131 131 $('.browser-highlight').removeClass('browser-highlight');
132 132 next.addClass('browser-highlight');
133 133 }
134 134 }
135 135
136 136 if ($hlElem.get(0) !== undefined){
137 137 scrollUp($hlElem);
138 138 }
139 139
140 140 }
141 141 if (e.keyCode === 13) { // Enter
142 142 if ($('.browser-highlight').length !== 0) {
143 143 var url = $('.browser-highlight').find('.match-link').attr('href');
144 144 window.location = url;
145 145 }
146 146 }
147 147 if (e.keyCode === 27) { // Esc
148 148 NodeFilter.resetFilter();
149 149 $('html,body').scrollTop(0);
150 150 }
151 151
152 152 var capture_keys = [
153 153 40, // ArrowDown
154 154 38, // ArrowUp
155 155 39, // ArrowRight
156 156 37, // ArrowLeft
157 157 13, // Enter
158 158 27 // Esc
159 159 ];
160 160
161 161 if ($.inArray(e.keyCode, capture_keys) === -1) {
162 162 clearTimeout(NodeFilter.filterTimeout);
163 163 NodeFilter.filterTimeout = setTimeout(NodeFilter.updateFilter(n_filter, e), 200);
164 164 }
165 165
166 166 };
167 167
168 168 NodeFilter.fuzzy_match = function (filepath, query) {
169 169 var highlight = [];
170 170 var order = 0;
171 171 for (var i = 0; i < query.length; i++) {
172 172 var match_position = filepath.indexOf(query[i]);
173 173 if (match_position !== -1) {
174 174 var prev_match_position = highlight[highlight.length - 1];
175 175 if (prev_match_position === undefined) {
176 176 highlight.push(match_position);
177 177 } else {
178 178 var current_match_position = prev_match_position + match_position + 1;
179 179 highlight.push(current_match_position);
180 180 order = order + current_match_position - prev_match_position;
181 181 }
182 182 filepath = filepath.substring(match_position + 1);
183 183 } else {
184 184 return false;
185 185 }
186 186 }
187 187 return {
188 188 'order': order,
189 189 'highlight': highlight
190 190 };
191 191 };
192 192
193 193 NodeFilter.sortPredicate = function (a, b) {
194 194 if (a.order < b.order) return -1;
195 195 if (a.order > b.order) return 1;
196 196 if (a.filepath < b.filepath) return -1;
197 197 if (a.filepath > b.filepath) return 1;
198 198 return 0;
199 199 };
200 200
201 201 NodeFilter.updateFilter = function (elem, e) {
202 202 return function () {
203 203 // Reset timeout
204 204 NodeFilter.filterTimeout = null;
205 205 var query = elem.value.toLowerCase();
206 206 var match = [];
207 207 var matches_max = 20;
208 208 if (query !== "") {
209 209 var results = [];
210 210 for (var k = 0; k < nodes.length; k++) {
211 211 var result = NodeFilter.fuzzy_match(
212 212 nodes[k].name.toLowerCase(), query);
213 213 if (result) {
214 214 result.type = nodes[k].type;
215 215 result.filepath = nodes[k].name;
216 216 results.push(result);
217 217 }
218 218 }
219 219 results = results.sort(NodeFilter.sortPredicate);
220 220 var limit = matches_max;
221 221 if (results.length < matches_max) {
222 222 limit = results.length;
223 223 }
224 224 for (var i = 0; i < limit; i++) {
225 225 if (query && results.length > 0) {
226 226 var n = results[i].filepath;
227 227 var t = results[i].type;
228 228 var n_hl = n.split("");
229 229 var pos = results[i].highlight;
230 230 for (var j = 0; j < pos.length; j++) {
231 231 n_hl[pos[j]] = "<em>" + n_hl[pos[j]] + "</em>";
232 232 }
233 233 n_hl = n_hl.join("");
234 234 var new_url = url_base.replace('__FPATH__', n);
235 235
236 236 var typeObj = {
237 237 dir: 'icon-directory browser-dir',
238 238 file: 'icon-file-text browser-file'
239 239 };
240 240
241 241 var typeIcon = '<i class="{0}"></i>'.format(typeObj[t]);
242 242 match.push('<tr class="browser-result"><td><a class="match-link" href="{0}">{1}{2}</a></td><td colspan="5"></td></tr>'.format(new_url, typeIcon, n_hl));
243 243 }
244 244 }
245 245 if (results.length > limit) {
246 246 var truncated_count = results.length - matches_max;
247 247 if (truncated_count === 1) {
248 248 match.push('<tr><td>{0} {1}</td><td colspan="5"></td></tr>'.format(truncated_count, _gettext('truncated result')));
249 249 } else {
250 250 match.push('<tr><td>{0} {1}</td><td colspan="5"></td></tr>'.format(truncated_count, _gettext('truncated results')));
251 251 }
252 252 }
253 253 }
254 254 if (query !== "") {
255 255 $('#tbody').hide();
256 256 $('#tbody_filtered').show();
257 257
258 258 if (match.length === 0) {
259 259 match.push('<tr><td>{0}</td><td colspan="5"></td></tr>'.format(_gettext('No matching files')));
260 260 }
261 261 $('#tbody_filtered').html(match.join(""));
262 262 } else {
263 263 $('#tbody').show();
264 264 $('#tbody_filtered').hide();
265 265 }
266 266
267 267 };
268 268 };
269 269
270 270 };
271 271
272 272 var getIdentNode = function(n){
273 273 // iterate through nodes until matched interesting node
274 274 if (typeof n === 'undefined'){
275 275 return -1;
276 276 }
277 277 if(typeof n.id !== "undefined" && n.id.match('L[0-9]+')){
278 278 return n;
279 279 }
280 280 else{
281 281 return getIdentNode(n.parentNode);
282 282 }
283 283 };
284 284
285 285 var getSelectionLink = function(e) {
286 286 // get selection from start/to nodes
287 287 if (typeof window.getSelection !== "undefined") {
288 288 s = window.getSelection();
289 289
290 290 from = getIdentNode(s.anchorNode);
291 291 till = getIdentNode(s.focusNode);
292 292
293 293 f_int = parseInt(from.id.replace('L',''));
294 294 t_int = parseInt(till.id.replace('L',''));
295 295
296 296 if (f_int > t_int){
297 297 // highlight from bottom
298 298 offset = -35;
299 299 ranges = [t_int,f_int];
300 300 }
301 301 else{
302 302 // highligth from top
303 303 offset = 35;
304 304 ranges = [f_int,t_int];
305 305 }
306 306 // if we select more than 2 lines
307 307 if (ranges[0] !== ranges[1]){
308 308 if($('#linktt').length === 0){
309 309 hl_div = document.createElement('div');
310 310 hl_div.id = 'linktt';
311 311 }
312 312 hl_div.innerHTML = '';
313 313
314 314 anchor = '#L'+ranges[0]+'-'+ranges[1];
315 315 var link = document.createElement('a');
316 316 link.href = location.href.substring(0,location.href.indexOf('#'))+anchor;
317 317 link.innerHTML = _gettext('Selection link');
318 318 hl_div.appendChild(link);
319 319 $('#codeblock').append(hl_div);
320 320
321 321 var xy = $(till).offset();
322 322 $('#linktt').addClass('hl-tip-box tip-box');
323 323 $('#linktt').offset({top: xy.top + offset, left: xy.left});
324 324 $('#linktt').css('visibility','visible');
325 325 }
326 326 else{
327 327 $('#linktt').css('visibility','hidden');
328 328 }
329 329 }
330 330 };
331 331
332 332 var getFileState = function() {
333 333 // relies on a global set filesUrlData
334 334 var f_path = filesUrlData['f_path'];
335 335 var commit_id = filesUrlData['commit_id'];
336 336
337 337 var url_params = {
338 338 repo_name: templateContext.repo_name,
339 339 commit_id: commit_id,
340 340 f_path:'__FPATH__'
341 341 };
342 342 if (atRef !== '') {
343 343 url_params['at'] = atRef
344 344 }
345 345
346 346 var _url_base = pyroutes.url('repo_files', url_params);
347 347 var _node_list_url = pyroutes.url('repo_files_nodelist',
348 348 {repo_name: templateContext.repo_name,
349 349 commit_id: commit_id, f_path: f_path});
350 350
351 351 return {
352 352 f_path: f_path,
353 353 commit_id: commit_id,
354 354 node_list_url: _node_list_url,
355 355 url_base: _url_base
356 356 };
357 357 };
358 358
359 359 var getFilesMetadata = function() {
360 360 // relies on metadataRequest global state
361 361 if (metadataRequest && metadataRequest.readyState != 4) {
362 362 metadataRequest.abort();
363 363 }
364 364
365 365 if ($('#file-tree-wrapper').hasClass('full-load')) {
366 366 // in case our HTML wrapper has full-load class we don't
367 367 // trigger the async load of metadata
368 368 return false;
369 369 }
370 370
371 371 var state = getFileState();
372 372 var url_data = {
373 373 'repo_name': templateContext.repo_name,
374 374 'commit_id': state.commit_id,
375 'f_path': state.f_path
375 'f_path': state.f_path,
376 376 };
377 377
378 if (atRef !== '') {
379 url_data['at'] = atRef
380 }
381
378 382 var url = pyroutes.url('repo_nodetree_full', url_data);
379 383
380 384 metadataRequest = $.ajax({url: url});
381 385
382 386 metadataRequest.done(function(data) {
383 387 $('#file-tree').html(data);
384 388 timeagoActivate();
385 389 tooltipActivate();
386 390 });
387 391 metadataRequest.fail(function (data, textStatus, errorThrown) {
388 392 if (data.status != 0) {
389 393 alert("Error while fetching metadata.\nError code {0} ({1}).Please consider reloading the page".format(data.status,data.statusText));
390 394 }
391 395 });
392 396 };
393 397
394 398 // show more authors
395 399 var showAuthors = function(elem, annotate) {
396 400 var state = getFileState('callbacks');
397 401
398 402 var url = pyroutes.url('repo_file_authors',
399 403 {'repo_name': templateContext.repo_name,
400 404 'commit_id': state.commit_id, 'f_path': state.f_path});
401 405
402 406 $.pjax({
403 407 url: url,
404 408 data: 'annotate={0}'.format(annotate),
405 409 container: '#file_authors',
406 410 push: false,
407 411 timeout: 5000
408 412 }).complete(function(){
409 413 $(elem).hide();
410 414 $('#file_authors_title').html(_gettext('All Authors'));
411 415 tooltipActivate();
412 416 })
413 417 };
414 418
415 419
416 420 (function (mod) {
417 421
418 422 if (typeof exports == "object" && typeof module == "object") {
419 423 // CommonJS
420 424 module.exports = mod();
421 425 } else {
422 426 // Plain browser env
423 427 (this || window).FileEditor = mod();
424 428 }
425 429
426 430 })(function () {
427 431 "use strict";
428 432
429 433 function FileEditor(textAreaElement, options) {
430 434 if (!(this instanceof FileEditor)) {
431 435 return new FileEditor(textAreaElement, options);
432 436 }
433 437 // bind the element instance to our Form
434 438 var te = $(textAreaElement).get(0);
435 439 if (te !== undefined) {
436 440 te.FileEditor = this;
437 441 }
438 442
439 443 this.modes_select = '#set_mode';
440 444 this.filename_selector = '#filename';
441 445 this.commit_btn_selector = '#commit_btn';
442 446 this.line_wrap_selector = '#line_wrap';
443 447 this.editor_preview_selector = '#editor_preview';
444 448
445 449 if (te !== undefined) {
446 450 this.cm = initCodeMirror(textAreaElement, null, false);
447 451 }
448 452
449 453 // FUNCTIONS and helpers
450 454 var self = this;
451 455
452 456 this.submitHandler = function() {
453 457 $(self.commit_btn_selector).on('click', function(e) {
454 458
455 459 var filename = $(self.filename_selector).val();
456 460 if (filename === "") {
457 461 alert("Missing filename");
458 462 e.preventDefault();
459 463 }
460 464
461 465 var button = $(this);
462 466 if (button.hasClass('clicked')) {
463 467 button.attr('disabled', true);
464 468 } else {
465 469 button.addClass('clicked');
466 470 }
467 471 });
468 472 };
469 473 this.submitHandler();
470 474
471 475 // on select line wraps change the editor
472 476 this.lineWrapHandler = function () {
473 477 $(self.line_wrap_selector).on('change', function (e) {
474 478 var selected = e.currentTarget;
475 479 var line_wraps = {'on': true, 'off': false}[selected.value];
476 480 setCodeMirrorLineWrap(self.cm, line_wraps)
477 481 });
478 482 };
479 483 this.lineWrapHandler();
480 484
481 485
482 486 this.showPreview = function () {
483 487
484 488 var _text = self.cm.getValue();
485 489 var _file_path = $(self.filename_selector).val();
486 490 if (_text && _file_path) {
487 491 $('.show-preview').addClass('active');
488 492 $('.show-editor').removeClass('active');
489 493
490 494 $(self.editor_preview_selector).show();
491 495 $(self.cm.getWrapperElement()).hide();
492 496
493 497
494 498 var post_data = {'text': _text, 'file_path': _file_path, 'csrf_token': CSRF_TOKEN};
495 499 $(self.editor_preview_selector).html(_gettext('Loading ...'));
496 500
497 501 var url = pyroutes.url('file_preview');
498 502
499 503 ajaxPOST(url, post_data, function (o) {
500 504 $(self.editor_preview_selector).html(o);
501 505 })
502 506 }
503 507
504 508 };
505 509
506 510 this.showEditor = function () {
507 511 $(self.editor_preview_selector).hide();
508 512 $('.show-editor').addClass('active');
509 513 $('.show-preview').removeClass('active');
510 514
511 515 $(self.cm.getWrapperElement()).show();
512 516 };
513 517
514 518
515 519 }
516 520
517 521 return FileEditor;
518 522 });
519 523
@@ -1,44 +1,59 b''
1 <%def name="refs(commit)">
2 ## Build a cache of refs for selector
1 <%def name="refs(commit, at_rev=None)">
2
3 ## Build a cache of refs for selector, based on this the files ref selector gets pre-selected values
3 4 <script>
4 fileTreeRefs = {
5 fileTreeRefs = {}
6 </script>
5 7
6 }
7 </script>
8 % if h.is_svn(c.rhodecode_repo):
9 ## since SVN doesn't have an commit<->refs association, we simply inject it
10 ## based on our at_rev marker
11 % if at_rev and at_rev.startswith('branches/'):
12 <%
13 commit.branch = at_rev
14 %>
15 % endif
16 % if at_rev and at_rev.startswith('tags/'):
17 <%
18 commit.tags.append(at_rev)
19 %>
20 % endif
21
22 % endif
8 23
9 24 %if commit.merge:
10 25 <span class="mergetag tag">
11 26 <i class="icon-merge">${_('merge')}</i>
12 27 </span>
13 28 %endif
14 29
15 30 %if h.is_hg(c.rhodecode_repo):
16 31 %for book in commit.bookmarks:
17 32 <span class="booktag tag" title="${h.tooltip(_('Bookmark %s') % book)}">
18 33 <a href="${h.route_path('repo_files:default_path',repo_name=c.repo_name,commit_id=commit.raw_id,_query=dict(at=book))}"><i class="icon-bookmark"></i>${h.shorter(book)}</a>
19 34 </span>
20 35 <script>
21 36 fileTreeRefs["${book}"] = {raw_id: "${commit.raw_id}", type:"book", text: "${book}"};
22 37 </script>
23 38 %endfor
24 39 %endif
25 40
26 41 %for tag in commit.tags:
27 42 <span class="tagtag tag" title="${h.tooltip(_('Tag %s') % tag)}">
28 43 <a href="${h.route_path('repo_files:default_path',repo_name=c.repo_name,commit_id=commit.raw_id,_query=dict(at=tag))}"><i class="icon-tag"></i>${tag}</a>
29 44 </span>
30 45 <script>
31 46 fileTreeRefs["${tag}"] = {raw_id: "${commit.raw_id}", type:"tag", text: "${tag}"};
32 47 </script>
33 48 %endfor
34 49
35 50 %if commit.branch:
36 51 <span class="branchtag tag" title="${h.tooltip(_('Branch %s') % commit.branch)}">
37 52 <a href="${h.route_path('repo_files:default_path',repo_name=c.repo_name,commit_id=commit.raw_id,_query=dict(at=commit.branch))}"><i class="icon-code-fork"></i>${h.shorter(commit.branch)}</a>
38 53 </span>
39 54 <script>
40 55 fileTreeRefs["${commit.branch}"] = {raw_id: "${commit.raw_id}", type:"branch", text: "${commit.branch}"};
41 56 </script>
42 57 %endif
43 58
44 59 </%def>
@@ -1,372 +1,379 b''
1 1 <%inherit file="/base/base.mako"/>
2 2
3 3 <%def name="title(*args)">
4 4 ${_('{} Files').format(c.repo_name)}
5 5 %if hasattr(c,'file'):
6 6 &middot; ${(h.safe_unicode(c.file.path) or '\\')}
7 7 %endif
8 8
9 9 %if c.rhodecode_name:
10 10 &middot; ${h.branding(c.rhodecode_name)}
11 11 %endif
12 12 </%def>
13 13
14 14 <%def name="breadcrumbs_links()">
15 15 ${_('Files')}
16 16 %if c.file:
17 17 @ ${h.show_id(c.commit)}
18 18 %endif
19 19 </%def>
20 20
21 21 <%def name="menu_bar_nav()">
22 22 ${self.menu_items(active='repositories')}
23 23 </%def>
24 24
25 25 <%def name="menu_bar_subnav()">
26 26 ${self.repo_menu(active='files')}
27 27 </%def>
28 28
29 29 <%def name="main()">
30 30 <script type="text/javascript">
31 31 var fileSourcePage = ${c.file_source_page};
32 32 var atRef = '${request.GET.get('at', '')}';
33 33
34 34 // global state for fetching metadata
35 35 metadataRequest = null;
36 36
37 37 // global metadata about URL
38 38 filesUrlData = ${h.files_url_data(request)|n};
39 39 </script>
40 40
41 41 <div>
42 42 <div>
43 43 <%include file='files_pjax.mako'/>
44 44 </div>
45 45 </div>
46 46
47 47 <script type="text/javascript">
48 48
49 49 var initFileJS = function () {
50 50 var state = getFileState();
51 51
52 52 // select code link event
53 53 $("#hlcode").mouseup(getSelectionLink);
54 54
55 55 // file history select2 used for history of file, and switch to
56 56 var initialCommitData = {
57 57 at_ref: atRef,
58 58 id: null,
59 59 text: '${c.commit.raw_id}',
60 60 type: 'sha',
61 61 raw_id: '${c.commit.raw_id}',
62 62 idx: ${c.commit.idx},
63 63 files_url: null,
64 64 };
65 65
66 66 // check if we have ref info.
67 67 var selectedRef = fileTreeRefs[atRef];
68 68 if (selectedRef !== undefined) {
69 69 $.extend(initialCommitData, selectedRef)
70 70 }
71 71
72 72 var loadUrl = pyroutes.url('repo_file_history', {'repo_name': templateContext.repo_name, 'commit_id': state.commit_id,'f_path': state.f_path});
73 73 var cacheKey = '__SINGLE_FILE_REFS__';
74 74 var cachedDataSource = {};
75 75
76 76 var loadRefsData = function (query) {
77 77 $.ajax({
78 78 url: loadUrl,
79 79 data: {},
80 80 dataType: 'json',
81 81 type: 'GET',
82 82 success: function (data) {
83 83 cachedDataSource[cacheKey] = data;
84 84 query.callback({results: data.results});
85 85 }
86 86 });
87 87 };
88 88
89 89 var feedRefsData = function (query, cachedData) {
90 90 var data = {results: []};
91 91 //filter results
92 92 $.each(cachedData.results, function () {
93 93 var section = this.text;
94 94 var children = [];
95 95 $.each(this.children, function () {
96 96 if (query.term.length === 0 || this.text.toUpperCase().indexOf(query.term.toUpperCase()) >= 0) {
97 97 children.push(this)
98 98 }
99 99 });
100 100 data.results.push({
101 101 'text': section,
102 102 'children': children
103 103 })
104 104 });
105 105
106 106 query.callback(data);
107 107 };
108 108
109 109 var select2FileHistorySwitcher = function (targetElement, loadUrl, initialData) {
110 110 var formatResult = function (result, container, query) {
111 111 return formatSelect2SelectionRefs(result);
112 112 };
113 113
114 114 var formatSelection = function (data, container) {
115 115 var commit_ref = data;
116 116
117 117 var tmpl = '';
118 118 if (commit_ref.type === 'sha') {
119 119 tmpl = (commit_ref.raw_id || "").substr(0,8);
120 120 } else if (commit_ref.type === 'branch') {
121 121 tmpl = tmpl.concat('<i class="icon-branch"></i> ');
122 122 tmpl = tmpl.concat(escapeHtml(commit_ref.text));
123 123 } else if (commit_ref.type === 'tag') {
124 124 tmpl = tmpl.concat('<i class="icon-tag"></i> ');
125 125 tmpl = tmpl.concat(escapeHtml(commit_ref.text));
126 126 } else if (commit_ref.type === 'book') {
127 127 tmpl = tmpl.concat('<i class="icon-bookmark"></i> ');
128 128 tmpl = tmpl.concat(escapeHtml(commit_ref.text));
129 129 }
130 130 var idx = commit_ref.idx || 0;
131 131 if (idx !== 0) {
132 132 tmpl = tmpl.concat('<span class="select-index-number">r{0}</span>'.format(idx));
133 133 }
134 134 return tmpl
135 135 };
136 136
137 137 $(targetElement).select2({
138 138 dropdownAutoWidth: true,
139 139 width: "resolve",
140 140 containerCssClass: "drop-menu",
141 141 dropdownCssClass: "drop-menu-dropdown",
142 142 query: function(query) {
143 143 var cachedData = cachedDataSource[cacheKey];
144 144 if (cachedData) {
145 145 feedRefsData(query, cachedData)
146 146 } else {
147 147 loadRefsData(query)
148 148 }
149 149 },
150 150 initSelection: function(element, callback) {
151 151 callback(initialData);
152 152 },
153 153 formatResult: formatResult,
154 154 formatSelection: formatSelection
155 155 });
156 156
157 157 };
158 158
159 159 select2FileHistorySwitcher('#file_refs_filter', loadUrl, initialCommitData);
160 160
161 // switcher for files
161 162 $('#file_refs_filter').on('change', function(e) {
162 163 var data = $('#file_refs_filter').select2('data');
163 164 var commit_id = data.id;
165 var params = {
166 'repo_name': templateContext.repo_name,
167 'commit_id': commit_id,
168 'f_path': state.f_path
169 };
170
171 if(data.at_rev !== undefined && data.at_rev !== "") {
172 params['at'] = data.at_rev;
173 }
164 174
165 175 if ("${c.annotate}" === "True") {
166 var url = pyroutes.url('repo_files:annotated',
167 {'repo_name': templateContext.repo_name,
168 'commit_id': commit_id, 'f_path': state.f_path});
176 var url = pyroutes.url('repo_files:annotated', params);
169 177 } else {
170 var url = pyroutes.url('repo_files',
171 {'repo_name': templateContext.repo_name,
172 'commit_id': commit_id, 'f_path': state.f_path});
178 var url = pyroutes.url('repo_files', params);
173 179 }
174 180 window.location = url;
175 181
176 182 });
177 183
178 184 // load file short history
179 185 $('#file_history_overview').on('click', function(e) {
180 186 e.preventDefault();
181 187 path = state.f_path;
182 188 if (path.indexOf("#") >= 0) {
183 189 path = path.slice(0, path.indexOf("#"));
184 190 }
185 191 var url = pyroutes.url('repo_commits_file',
186 192 {'repo_name': templateContext.repo_name,
187 193 'commit_id': state.commit_id, 'f_path': path, 'limit': 6});
188 194 $('#file_history_container').show();
189 195 $('#file_history_container').html('<div class="file-history-inner">{0}</div>'.format(_gettext('Loading ...')));
190 196
191 197 $.pjax({
192 198 url: url,
193 199 container: '#file_history_container',
194 200 push: false,
195 201 timeout: 5000
196 202 }).complete(function () {
197 203 tooltipActivate();
198 204 });
199 205 });
200 206
201 207 };
202 208
203 209 var initTreeJS = function () {
204 210 var state = getFileState();
205 211 getFilesMetadata();
206 212
207 213 // fuzzy file filter
208 214 fileBrowserListeners(state.node_list_url, state.url_base);
209 215
210 216 // switch to widget
211 217 var initialCommitData = {
212 218 at_ref: atRef,
213 219 id: null,
214 220 text: '${c.commit.raw_id}',
215 221 type: 'sha',
216 222 raw_id: '${c.commit.raw_id}',
217 223 idx: ${c.commit.idx},
218 224 files_url: null,
219 225 };
220 226
221 227 // check if we have ref info.
222 228 var selectedRef = fileTreeRefs[atRef];
223 229 if (selectedRef !== undefined) {
224 230 $.extend(initialCommitData, selectedRef)
225 231 }
226 232
227 233 var loadUrl = pyroutes.url('repo_refs_data', {'repo_name': templateContext.repo_name});
228 234 var cacheKey = '__ALL_FILE_REFS__';
229 235 var cachedDataSource = {};
230 236
231 237 var loadRefsData = function (query) {
232 238 $.ajax({
233 239 url: loadUrl,
234 240 data: {},
235 241 dataType: 'json',
236 242 type: 'GET',
237 243 success: function (data) {
238 244 cachedDataSource[cacheKey] = data;
239 245 query.callback({results: data.results});
240 246 }
241 247 });
242 248 };
243 249
244 250 var feedRefsData = function (query, cachedData) {
245 251 var data = {results: []};
246 252 //filter results
247 253 $.each(cachedData.results, function () {
248 254 var section = this.text;
249 255 var children = [];
250 256 $.each(this.children, function () {
251 257 if (query.term.length === 0 || this.text.toUpperCase().indexOf(query.term.toUpperCase()) >= 0) {
252 258 children.push(this)
253 259 }
254 260 });
255 261 data.results.push({
256 262 'text': section,
257 263 'children': children
258 264 })
259 265 });
260 266
261 267 //push the typed in commit idx
262 268 if (!isNaN(query.term)) {
263 269 var files_url = pyroutes.url('repo_files',
264 270 {'repo_name': templateContext.repo_name,
265 271 'commit_id': query.term, 'f_path': state.f_path});
266 272
267 273 data.results.push({
268 274 'text': _gettext('go to numeric commit'),
269 275 'children': [{
270 276 at_ref: null,
271 277 id: null,
272 278 text: 'r{0}'.format(query.term),
273 279 type: 'sha',
274 280 raw_id: query.term,
275 281 idx: query.term,
276 282 files_url: files_url,
277 283 }]
278 284 });
279 285 }
280 286 query.callback(data);
281 287 };
282 288
283 289 var select2RefFileSwitcher = function (targetElement, loadUrl, initialData) {
284 290 var formatResult = function (result, container, query) {
285 291 return formatSelect2SelectionRefs(result);
286 292 };
287 293
288 294 var formatSelection = function (data, container) {
289 295 var commit_ref = data;
290 296
291 297 var tmpl = '';
292 298 if (commit_ref.type === 'sha') {
293 299 tmpl = (commit_ref.raw_id || "").substr(0,8);
294 300 } else if (commit_ref.type === 'branch') {
295 301 tmpl = tmpl.concat('<i class="icon-branch"></i> ');
296 302 tmpl = tmpl.concat(escapeHtml(commit_ref.text));
297 303 } else if (commit_ref.type === 'tag') {
298 304 tmpl = tmpl.concat('<i class="icon-tag"></i> ');
299 305 tmpl = tmpl.concat(escapeHtml(commit_ref.text));
300 306 } else if (commit_ref.type === 'book') {
301 307 tmpl = tmpl.concat('<i class="icon-bookmark"></i> ');
302 308 tmpl = tmpl.concat(escapeHtml(commit_ref.text));
303 309 }
304 310
305 311 var idx = commit_ref.idx || 0;
306 312 if (idx !== 0) {
307 313 tmpl = tmpl.concat('<span class="select-index-number">r{0}</span>'.format(idx));
308 314 }
309 315 return tmpl
310 316 };
311 317
312 318 $(targetElement).select2({
313 319 dropdownAutoWidth: true,
314 320 width: "resolve",
315 321 containerCssClass: "drop-menu",
316 322 dropdownCssClass: "drop-menu-dropdown",
317 323 query: function(query) {
318 324
319 325 var cachedData = cachedDataSource[cacheKey];
320 326 if (cachedData) {
321 327 feedRefsData(query, cachedData)
322 328 } else {
323 329 loadRefsData(query)
324 330 }
325 331 },
326 332 initSelection: function(element, callback) {
327 333 callback(initialData);
328 334 },
329 335 formatResult: formatResult,
330 336 formatSelection: formatSelection
331 337 });
332 338
333 339 };
334 340
335 341 select2RefFileSwitcher('#refs_filter', loadUrl, initialCommitData);
336 342
343 // switcher for file tree
337 344 $('#refs_filter').on('change', function(e) {
338 345 var data = $('#refs_filter').select2('data');
339 346 window.location = data.files_url
340 347 });
341 348
342 349 };
343 350
344 351 $(document).ready(function() {
345 352 timeagoActivate();
346 353 tooltipActivate();
347 354
348 355 if ($('#trimmed_message_box').height() < 50) {
349 356 $('#message_expand').hide();
350 357 }
351 358
352 359 $('#message_expand').on('click', function(e) {
353 360 $('#trimmed_message_box').css('max-height', 'none');
354 361 $(this).hide();
355 362 });
356 363
357 364 if (fileSourcePage) {
358 365 initFileJS()
359 366 } else {
360 367 initTreeJS()
361 368 }
362 369
363 370 var search_GET = "${request.GET.get('search','')}";
364 371 if (search_GET === "1") {
365 372 NodeFilter.initFilter();
366 373 NodeFilter.focus();
367 374 }
368 375 });
369 376
370 377 </script>
371 378
372 379 </%def> No newline at end of file
@@ -1,96 +1,95 b''
1 1 <%namespace name="base" file="/base/base.mako"/>
2 2
3 3 <%
4 4 if request.GET.get('at'):
5 5 query={'at': request.GET.get('at')}
6 6 else:
7 7 query=None
8 8 %>
9 9 <div id="file-tree-wrapper" class="browser-body ${('full-load' if c.full_load else '')}">
10 10 <table class="code-browser rctable repo_summary">
11 11 <thead>
12 12 <tr>
13 13 <th>${_('Name')}</th>
14 14 <th>${_('Size')}</th>
15 15 <th>${_('Modified')}</th>
16 16 <th>${_('Last Commit')}</th>
17 17 <th>${_('Author')}</th>
18 18 </tr>
19 19 </thead>
20 20
21 21 <tbody id="tbody">
22 22 <tr>
23 23 <td colspan="5">
24 24 ${h.files_breadcrumbs(c.repo_name,c.commit.raw_id,c.file.path, request.GET.get('at'), limit_items=True)}
25 25 </td>
26 26 </tr>
27 27
28 28 <% has_files = False %>
29 29 % for cnt,node in enumerate(c.file):
30 30 <% has_files = True %>
31 31 <tr class="parity${(cnt % 2)}">
32 32 <td class="td-componentname">
33 33 % if node.is_submodule():
34 34 <span class="submodule-dir">
35 35 % if node.url.startswith('http://') or node.url.startswith('https://'):
36 36 <a href="${node.url}">
37 37 <i class="icon-directory browser-dir"></i>${node.name}
38 38 </a>
39 39 % else:
40 40 <i class="icon-directory browser-dir"></i>${node.name}
41 41 % endif
42 42 </span>
43 43 % else:
44
45 44 <a href="${h.route_path('repo_files',repo_name=c.repo_name,commit_id=c.commit.raw_id,f_path=h.safe_unicode(node.path), _query=query)}">
46 45 <i class="${('icon-file-text browser-file' if node.is_file() else 'icon-directory browser-dir')}"></i>${node.name}
47 46 </a>
48 47 % endif
49 48 </td>
50 49 %if node.is_file():
51 50 <td class="td-size" data-attr-name="size">
52 51 % if c.full_load:
53 52 <span data-size="${node.size}">${h.format_byte_size_binary(node.size)}</span>
54 53 % else:
55 54 ${_('Loading ...')}
56 55 % endif
57 56 </td>
58 57 <td class="td-time" data-attr-name="modified_at">
59 58 % if c.full_load:
60 59 <span data-date="${node.last_commit.date}">${h.age_component(node.last_commit.date)}</span>
61 60 % endif
62 61 </td>
63 62 <td class="td-hash" data-attr-name="commit_id">
64 63 % if c.full_load:
65 64 <div class="tooltip-hovercard" data-hovercard-alt="${node.last_commit.message}" data-hovercard-url="${h.route_path('hovercard_repo_commit', repo_name=c.repo_name, commit_id=node.last_commit.raw_id)}">
66 65 <pre data-commit-id="${node.last_commit.raw_id}">r${node.last_commit.idx}:${node.last_commit.short_id}</pre>
67 66 </div>
68 67 % endif
69 68 </td>
70 69 <td class="td-user" data-attr-name="author">
71 70 % if c.full_load:
72 71 <span data-author="${node.last_commit.author}">${h.gravatar_with_user(request, node.last_commit.author, tooltip=True)|n}</span>
73 72 % endif
74 73 </td>
75 74 %else:
76 75 <td></td>
77 76 <td></td>
78 77 <td></td>
79 78 <td></td>
80 79 %endif
81 80 </tr>
82 81 % endfor
83 82
84 83 % if not has_files:
85 84 <tr>
86 85 <td colspan="5">
87 86 ##empty-dir mostly SVN
88 87 &nbsp;
89 88 </td>
90 89 </tr>
91 90 % endif
92 91
93 92 </tbody>
94 93 <tbody id="tbody_filtered"></tbody>
95 94 </table>
96 95 </div>
@@ -1,65 +1,65 b''
1 1 <%namespace name="base" file="/base/base.mako"/>
2 2 <%namespace name="file_base" file="/files/base.mako"/>
3 3
4 4 <div class="summary">
5 5 <div class="fieldset">
6 6 <div class="left-content">
7 7 <%
8 8 rc_user = h.discover_user(c.commit.author_email)
9 9 %>
10 10 <div class="left-content-avatar">
11 11 ${base.gravatar(c.file_last_commit.author_email, 30, tooltip=(True if rc_user else False), user=rc_user)}
12 12 </div>
13 13
14 14 <div class="left-content-message">
15 15 <div class="fieldset collapsable-content no-hide" data-toggle="summary-details">
16 16 <div class="commit truncate-wrap">${h.urlify_commit_message(h.chop_at_smart(c.commit.message, '\n', suffix_if_chopped='...'), c.repo_name)}</div>
17 17 </div>
18 18
19 19 <div class="fieldset collapsable-content" data-toggle="summary-details">
20 20 <div class="commit">${h.urlify_commit_message(c.commit.message,c.repo_name)}</div>
21 21 </div>
22 22
23 23 <div class="fieldset" data-toggle="summary-details">
24 24 <div class="" id="file_authors">
25 25 ## loads single author, or ALL
26 26 <%include file='file_authors_box.mako'/>
27 27 </div>
28 28 </div>
29 29 </div>
30 30
31 31 <div class="fieldset collapsable-content" data-toggle="summary-details">
32 32 <div class="left-label-summary-files">
33 33 <p>${_('File last commit')}:</p>
34 34 <div class="right-label-summary">
35 35 <code><a href="${h.route_path('repo_commit',repo_name=c.repo_name,commit_id=c.file_last_commit.raw_id)}">${h.show_id(c.file_last_commit)}</a></code>
36 ${file_base.refs(c.file_last_commit)}
36 ${file_base.refs(c.file_last_commit)}
37 37 </div>
38 38 </div>
39 39 </div>
40 40 </div>
41 41
42 42 <div class="right-content">
43 43 <div data-toggle="summary-details">
44 44 <div class="tags tags-main">
45 45 <code>
46 46 <a href="${h.route_path('repo_commit',repo_name=c.repo_name,commit_id=c.commit.raw_id)}">${h.show_id(c.commit)}</a>
47 47 </code>
48 48 <i class="tooltip icon-clipboard clipboard-action" data-clipboard-text="${c.commit.raw_id}" title="${_('Copy the full commit id')}"></i>
49 ${file_base.refs(c.commit)}
49 ${file_base.refs(c.commit, request.GET.get('at'))}
50 50 </div>
51 51 </div>
52 52 </div>
53 53
54 54 <div class="clear-fix"></div>
55 55
56 56 <div class="btn-collapse" data-toggle="summary-details">
57 57 ${_('Show More')}
58 58 </div>
59 59
60 60 </div>
61 61 </div>
62 62
63 63 <script>
64 64 collapsableContent();
65 65 </script>
@@ -1,49 +1,50 b''
1 1 <%namespace name="base" file="/base/base.mako"/>
2 2 <%namespace name="file_base" file="/files/base.mako"/>
3 3
4 4 <div class="summary">
5 5 <div class="fieldset">
6 6 <div class="left-content">
7 7 <%
8 8 rc_user = h.discover_user(c.commit.author_email)
9 9 %>
10 10 <div class="left-content-avatar">
11 11 ${base.gravatar(c.commit.author_email, 30, tooltip=(True if rc_user else False), user=rc_user)}
12 12 </div>
13 13
14 14 <div class="left-content-message">
15 15 <div class="fieldset collapsable-content no-hide" data-toggle="summary-details">
16 16 <div class="commit truncate-wrap">${h.urlify_commit_message(h.chop_at_smart(c.commit.message, '\n', suffix_if_chopped='...'), c.repo_name)}</div>
17 17 </div>
18 18
19 19 <div class="fieldset collapsable-content" data-toggle="summary-details">
20 20 <div class="commit">${h.urlify_commit_message(c.commit.message,c.repo_name)}</div>
21 21 </div>
22 22
23 23 <div class="fieldset clear-fix">
24 24 <span class="commit-author">${h.link_to_user(c.commit.author)}</span><span class="commit-date"> - ${h.age_component(c.commit.date)}</span>
25 25 </div>
26 26 </div>
27 27 </div>
28 28
29 29 <div class="right-content">
30 30 <div class="tags">
31 31 <code>
32 32 <a href="${h.route_path('repo_commit',repo_name=c.repo_name,commit_id=c.commit.raw_id)}">${h.show_id(c.commit)}</a>
33 33 </code>
34 34
35 35 <i class="tooltip icon-clipboard clipboard-action" data-clipboard-text="${c.commit.raw_id}" title="${_('Copy the full commit id')}"></i>
36 ${file_base.refs(c.commit)}
36 ${file_base.refs(c.commit, request.GET.get('at'))}
37
37 38 </div>
38 39 </div>
39 40
40 41 <div class="clear-fix"></div>
41 42
42 43 <div class="btn-collapse" data-toggle="summary-details">
43 44 ${_('Show More')}
44 45 </div>
45 46 </div>
46 47 </div>
47 48 <script>
48 49 collapsableContent();
49 50 </script>
General Comments 0
You need to be logged in to leave comments. Login now