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