##// END OF EJS Templates
files: fix loading of source files events via pjax.
marcink -
r1128:f8436858 default
parent child Browse files
Show More
@@ -1,1125 +1,1127 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2016 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 """
22 22 Files controller for RhodeCode Enterprise
23 23 """
24 24
25 25 import itertools
26 26 import logging
27 27 import os
28 28 import shutil
29 29 import tempfile
30 30
31 31 from pylons import request, response, tmpl_context as c, url
32 32 from pylons.i18n.translation import _
33 33 from pylons.controllers.util import redirect
34 34 from webob.exc import HTTPNotFound, HTTPBadRequest
35 35
36 36 from rhodecode.controllers.utils import parse_path_ref
37 37 from rhodecode.lib import diffs, helpers as h, caches
38 38 from rhodecode.lib.compat import OrderedDict
39 39 from rhodecode.lib.codeblocks import (
40 40 filenode_as_lines_tokens, filenode_as_annotated_lines_tokens)
41 41 from rhodecode.lib.utils import jsonify, action_logger
42 42 from rhodecode.lib.utils2 import (
43 43 convert_line_endings, detect_mode, safe_str, str2bool)
44 44 from rhodecode.lib.auth import (
45 45 LoginRequired, HasRepoPermissionAnyDecorator, CSRFRequired, XHRRequired)
46 46 from rhodecode.lib.base import BaseRepoController, render
47 47 from rhodecode.lib.vcs import path as vcspath
48 48 from rhodecode.lib.vcs.backends.base import EmptyCommit
49 49 from rhodecode.lib.vcs.conf import settings
50 50 from rhodecode.lib.vcs.exceptions import (
51 51 RepositoryError, CommitDoesNotExistError, EmptyRepositoryError,
52 52 ImproperArchiveTypeError, VCSError, NodeAlreadyExistsError,
53 53 NodeDoesNotExistError, CommitError, NodeError)
54 54 from rhodecode.lib.vcs.nodes import FileNode
55 55
56 56 from rhodecode.model.repo import RepoModel
57 57 from rhodecode.model.scm import ScmModel
58 58 from rhodecode.model.db import Repository
59 59
60 60 from rhodecode.controllers.changeset import (
61 61 _ignorews_url, _context_url, get_line_ctx, get_ignore_ws)
62 62 from rhodecode.lib.exceptions import NonRelativePathError
63 63
64 64 log = logging.getLogger(__name__)
65 65
66 66
67 67 class FilesController(BaseRepoController):
68 68
69 69 def __before__(self):
70 70 super(FilesController, self).__before__()
71 71 c.cut_off_limit = self.cut_off_limit_file
72 72
73 73 def _get_default_encoding(self):
74 74 enc_list = getattr(c, 'default_encodings', [])
75 75 return enc_list[0] if enc_list else 'UTF-8'
76 76
77 77 def __get_commit_or_redirect(self, commit_id, repo_name,
78 78 redirect_after=True):
79 79 """
80 80 This is a safe way to get commit. If an error occurs it redirects to
81 81 tip with proper message
82 82
83 83 :param commit_id: id of commit to fetch
84 84 :param repo_name: repo name to redirect after
85 85 :param redirect_after: toggle redirection
86 86 """
87 87 try:
88 88 return c.rhodecode_repo.get_commit(commit_id)
89 89 except EmptyRepositoryError:
90 90 if not redirect_after:
91 91 return None
92 92 url_ = url('files_add_home',
93 93 repo_name=c.repo_name,
94 94 revision=0, f_path='', anchor='edit')
95 95 if h.HasRepoPermissionAny(
96 96 'repository.write', 'repository.admin')(c.repo_name):
97 97 add_new = h.link_to(
98 98 _('Click here to add a new file.'),
99 99 url_, class_="alert-link")
100 100 else:
101 101 add_new = ""
102 102 h.flash(h.literal(
103 103 _('There are no files yet. %s') % add_new), category='warning')
104 104 redirect(h.url('summary_home', repo_name=repo_name))
105 105 except (CommitDoesNotExistError, LookupError):
106 106 msg = _('No such commit exists for this repository')
107 107 h.flash(msg, category='error')
108 108 raise HTTPNotFound()
109 109 except RepositoryError as e:
110 110 h.flash(safe_str(e), category='error')
111 111 raise HTTPNotFound()
112 112
113 113 def __get_filenode_or_redirect(self, repo_name, commit, path):
114 114 """
115 115 Returns file_node, if error occurs or given path is directory,
116 116 it'll redirect to top level path
117 117
118 118 :param repo_name: repo_name
119 119 :param commit: given commit
120 120 :param path: path to lookup
121 121 """
122 122 try:
123 123 file_node = commit.get_node(path)
124 124 if file_node.is_dir():
125 125 raise RepositoryError('The given path is a directory')
126 126 except CommitDoesNotExistError:
127 127 msg = _('No such commit exists for this repository')
128 128 log.exception(msg)
129 129 h.flash(msg, category='error')
130 130 raise HTTPNotFound()
131 131 except RepositoryError as e:
132 132 h.flash(safe_str(e), category='error')
133 133 raise HTTPNotFound()
134 134
135 135 return file_node
136 136
137 137 def __get_tree_cache_manager(self, repo_name, namespace_type):
138 138 _namespace = caches.get_repo_namespace_key(namespace_type, repo_name)
139 139 return caches.get_cache_manager('repo_cache_long', _namespace)
140 140
141 141 def _get_tree_at_commit(self, repo_name, commit_id, f_path,
142 142 full_load=False, force=False):
143 143 def _cached_tree():
144 144 log.debug('Generating cached file tree for %s, %s, %s',
145 145 repo_name, commit_id, f_path)
146 146 c.full_load = full_load
147 147 return render('files/files_browser_tree.html')
148 148
149 149 cache_manager = self.__get_tree_cache_manager(
150 150 repo_name, caches.FILE_TREE)
151 151
152 152 cache_key = caches.compute_key_from_params(
153 153 repo_name, commit_id, f_path)
154 154
155 155 if force:
156 156 # we want to force recompute of caches
157 157 cache_manager.remove_value(cache_key)
158 158
159 159 return cache_manager.get(cache_key, createfunc=_cached_tree)
160 160
161 161 def _get_nodelist_at_commit(self, repo_name, commit_id, f_path):
162 162 def _cached_nodes():
163 163 log.debug('Generating cached nodelist for %s, %s, %s',
164 164 repo_name, commit_id, f_path)
165 165 _d, _f = ScmModel().get_nodes(
166 166 repo_name, commit_id, f_path, flat=False)
167 167 return _d + _f
168 168
169 169 cache_manager = self.__get_tree_cache_manager(
170 170 repo_name, caches.FILE_SEARCH_TREE_META)
171 171
172 172 cache_key = caches.compute_key_from_params(
173 173 repo_name, commit_id, f_path)
174 174 return cache_manager.get(cache_key, createfunc=_cached_nodes)
175 175
176 176 @LoginRequired()
177 177 @HasRepoPermissionAnyDecorator(
178 178 'repository.read', 'repository.write', 'repository.admin')
179 179 def index(
180 180 self, repo_name, revision, f_path, annotate=False, rendered=False):
181 181 commit_id = revision
182 182
183 183 # redirect to given commit_id from form if given
184 184 get_commit_id = request.GET.get('at_rev', None)
185 185 if get_commit_id:
186 186 self.__get_commit_or_redirect(get_commit_id, repo_name)
187 187
188 188 c.commit = self.__get_commit_or_redirect(commit_id, repo_name)
189 189 c.branch = request.GET.get('branch', None)
190 190 c.f_path = f_path
191 191 c.annotate = annotate
192 192 # default is false, but .rst/.md files later are autorendered, we can
193 193 # overwrite autorendering by setting this GET flag
194 194 c.renderer = rendered or not request.GET.get('no-render', False)
195 195
196 196 # prev link
197 197 try:
198 198 prev_commit = c.commit.prev(c.branch)
199 199 c.prev_commit = prev_commit
200 200 c.url_prev = url('files_home', repo_name=c.repo_name,
201 201 revision=prev_commit.raw_id, f_path=f_path)
202 202 if c.branch:
203 203 c.url_prev += '?branch=%s' % c.branch
204 204 except (CommitDoesNotExistError, VCSError):
205 205 c.url_prev = '#'
206 206 c.prev_commit = EmptyCommit()
207 207
208 208 # next link
209 209 try:
210 210 next_commit = c.commit.next(c.branch)
211 211 c.next_commit = next_commit
212 212 c.url_next = url('files_home', repo_name=c.repo_name,
213 213 revision=next_commit.raw_id, f_path=f_path)
214 214 if c.branch:
215 215 c.url_next += '?branch=%s' % c.branch
216 216 except (CommitDoesNotExistError, VCSError):
217 217 c.url_next = '#'
218 218 c.next_commit = EmptyCommit()
219 219
220 220 # files or dirs
221 221 try:
222 222 c.file = c.commit.get_node(f_path)
223 223 c.file_author = True
224 224 c.file_tree = ''
225 225 if c.file.is_file():
226 c.file_source_page = 'true'
226 227 c.file_last_commit = c.file.last_commit
227 228 if c.file.size < self.cut_off_limit_file:
228 229 if c.annotate: # annotation has precedence over renderer
229 230 c.annotated_lines = filenode_as_annotated_lines_tokens(
230 231 c.file
231 232 )
232 233 else:
233 234 c.renderer = (
234 235 c.renderer and h.renderer_from_filename(c.file.path)
235 236 )
236 237 if not c.renderer:
237 238 c.lines = filenode_as_lines_tokens(c.file)
238 239
239 240 c.on_branch_head = self._is_valid_head(
240 241 commit_id, c.rhodecode_repo)
241 242 c.branch_or_raw_id = c.commit.branch or c.commit.raw_id
242 243
243 244 author = c.file_last_commit.author
244 245 c.authors = [(h.email(author),
245 246 h.person(author, 'username_or_name_or_email'))]
246 247 else:
248 c.file_source_page = 'false'
247 249 c.authors = []
248 250 c.file_tree = self._get_tree_at_commit(
249 251 repo_name, c.commit.raw_id, f_path)
250 252
251 253 except RepositoryError as e:
252 254 h.flash(safe_str(e), category='error')
253 255 raise HTTPNotFound()
254 256
255 257 if request.environ.get('HTTP_X_PJAX'):
256 258 return render('files/files_pjax.html')
257 259
258 260 return render('files/files.html')
259 261
260 262 @LoginRequired()
261 263 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
262 264 'repository.admin')
263 265 @jsonify
264 266 def history(self, repo_name, revision, f_path):
265 267 commit = self.__get_commit_or_redirect(revision, repo_name)
266 268 f_path = f_path
267 269 _file = commit.get_node(f_path)
268 270 if _file.is_file():
269 271 file_history, _hist = self._get_node_history(commit, f_path)
270 272
271 273 res = []
272 274 for obj in file_history:
273 275 res.append({
274 276 'text': obj[1],
275 277 'children': [{'id': o[0], 'text': o[1]} for o in obj[0]]
276 278 })
277 279
278 280 data = {
279 281 'more': False,
280 282 'results': res
281 283 }
282 284 return data
283 285
284 286 @LoginRequired()
285 287 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
286 288 'repository.admin')
287 289 def authors(self, repo_name, revision, f_path):
288 290 commit = self.__get_commit_or_redirect(revision, repo_name)
289 291 file_node = commit.get_node(f_path)
290 292 if file_node.is_file():
291 293 c.file_last_commit = file_node.last_commit
292 294 if request.GET.get('annotate') == '1':
293 295 # use _hist from annotation if annotation mode is on
294 296 commit_ids = set(x[1] for x in file_node.annotate)
295 297 _hist = (
296 298 c.rhodecode_repo.get_commit(commit_id)
297 299 for commit_id in commit_ids)
298 300 else:
299 301 _f_history, _hist = self._get_node_history(commit, f_path)
300 302 c.file_author = False
301 303 c.authors = []
302 304 for author in set(commit.author for commit in _hist):
303 305 c.authors.append((
304 306 h.email(author),
305 307 h.person(author, 'username_or_name_or_email')))
306 308 return render('files/file_authors_box.html')
307 309
308 310 @LoginRequired()
309 311 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
310 312 'repository.admin')
311 313 def rawfile(self, repo_name, revision, f_path):
312 314 """
313 315 Action for download as raw
314 316 """
315 317 commit = self.__get_commit_or_redirect(revision, repo_name)
316 318 file_node = self.__get_filenode_or_redirect(repo_name, commit, f_path)
317 319
318 320 response.content_disposition = 'attachment; filename=%s' % \
319 321 safe_str(f_path.split(Repository.NAME_SEP)[-1])
320 322
321 323 response.content_type = file_node.mimetype
322 324 charset = self._get_default_encoding()
323 325 if charset:
324 326 response.charset = charset
325 327
326 328 return file_node.content
327 329
328 330 @LoginRequired()
329 331 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
330 332 'repository.admin')
331 333 def raw(self, repo_name, revision, f_path):
332 334 """
333 335 Action for show as raw, some mimetypes are "rendered",
334 336 those include images, icons.
335 337 """
336 338 commit = self.__get_commit_or_redirect(revision, repo_name)
337 339 file_node = self.__get_filenode_or_redirect(repo_name, commit, f_path)
338 340
339 341 raw_mimetype_mapping = {
340 342 # map original mimetype to a mimetype used for "show as raw"
341 343 # you can also provide a content-disposition to override the
342 344 # default "attachment" disposition.
343 345 # orig_type: (new_type, new_dispo)
344 346
345 347 # show images inline:
346 348 # Do not re-add SVG: it is unsafe and permits XSS attacks. One can
347 349 # for example render an SVG with javascript inside or even render
348 350 # HTML.
349 351 'image/x-icon': ('image/x-icon', 'inline'),
350 352 'image/png': ('image/png', 'inline'),
351 353 'image/gif': ('image/gif', 'inline'),
352 354 'image/jpeg': ('image/jpeg', 'inline'),
353 355 }
354 356
355 357 mimetype = file_node.mimetype
356 358 try:
357 359 mimetype, dispo = raw_mimetype_mapping[mimetype]
358 360 except KeyError:
359 361 # we don't know anything special about this, handle it safely
360 362 if file_node.is_binary:
361 363 # do same as download raw for binary files
362 364 mimetype, dispo = 'application/octet-stream', 'attachment'
363 365 else:
364 366 # do not just use the original mimetype, but force text/plain,
365 367 # otherwise it would serve text/html and that might be unsafe.
366 368 # Note: underlying vcs library fakes text/plain mimetype if the
367 369 # mimetype can not be determined and it thinks it is not
368 370 # binary.This might lead to erroneous text display in some
369 371 # cases, but helps in other cases, like with text files
370 372 # without extension.
371 373 mimetype, dispo = 'text/plain', 'inline'
372 374
373 375 if dispo == 'attachment':
374 376 dispo = 'attachment; filename=%s' % safe_str(
375 377 f_path.split(os.sep)[-1])
376 378
377 379 response.content_disposition = dispo
378 380 response.content_type = mimetype
379 381 charset = self._get_default_encoding()
380 382 if charset:
381 383 response.charset = charset
382 384 return file_node.content
383 385
384 386 @CSRFRequired()
385 387 @LoginRequired()
386 388 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
387 389 def delete(self, repo_name, revision, f_path):
388 390 commit_id = revision
389 391
390 392 repo = c.rhodecode_db_repo
391 393 if repo.enable_locking and repo.locked[0]:
392 394 h.flash(_('This repository has been locked by %s on %s')
393 395 % (h.person_by_id(repo.locked[0]),
394 396 h.format_date(h.time_to_datetime(repo.locked[1]))),
395 397 'warning')
396 398 return redirect(h.url('files_home',
397 399 repo_name=repo_name, revision='tip'))
398 400
399 401 if not self._is_valid_head(commit_id, repo.scm_instance()):
400 402 h.flash(_('You can only delete files with revision '
401 403 'being a valid branch '), category='warning')
402 404 return redirect(h.url('files_home',
403 405 repo_name=repo_name, revision='tip',
404 406 f_path=f_path))
405 407
406 408 c.commit = self.__get_commit_or_redirect(commit_id, repo_name)
407 409 c.file = self.__get_filenode_or_redirect(repo_name, c.commit, f_path)
408 410
409 411 c.default_message = _(
410 412 'Deleted file %s via RhodeCode Enterprise') % (f_path)
411 413 c.f_path = f_path
412 414 node_path = f_path
413 415 author = c.rhodecode_user.full_contact
414 416 message = request.POST.get('message') or c.default_message
415 417 try:
416 418 nodes = {
417 419 node_path: {
418 420 'content': ''
419 421 }
420 422 }
421 423 self.scm_model.delete_nodes(
422 424 user=c.rhodecode_user.user_id, repo=c.rhodecode_db_repo,
423 425 message=message,
424 426 nodes=nodes,
425 427 parent_commit=c.commit,
426 428 author=author,
427 429 )
428 430
429 431 h.flash(_('Successfully deleted file %s') % f_path,
430 432 category='success')
431 433 except Exception:
432 434 msg = _('Error occurred during commit')
433 435 log.exception(msg)
434 436 h.flash(msg, category='error')
435 437 return redirect(url('changeset_home',
436 438 repo_name=c.repo_name, revision='tip'))
437 439
438 440 @LoginRequired()
439 441 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
440 442 def delete_home(self, repo_name, revision, f_path):
441 443 commit_id = revision
442 444
443 445 repo = c.rhodecode_db_repo
444 446 if repo.enable_locking and repo.locked[0]:
445 447 h.flash(_('This repository has been locked by %s on %s')
446 448 % (h.person_by_id(repo.locked[0]),
447 449 h.format_date(h.time_to_datetime(repo.locked[1]))),
448 450 'warning')
449 451 return redirect(h.url('files_home',
450 452 repo_name=repo_name, revision='tip'))
451 453
452 454 if not self._is_valid_head(commit_id, repo.scm_instance()):
453 455 h.flash(_('You can only delete files with revision '
454 456 'being a valid branch '), category='warning')
455 457 return redirect(h.url('files_home',
456 458 repo_name=repo_name, revision='tip',
457 459 f_path=f_path))
458 460
459 461 c.commit = self.__get_commit_or_redirect(commit_id, repo_name)
460 462 c.file = self.__get_filenode_or_redirect(repo_name, c.commit, f_path)
461 463
462 464 c.default_message = _(
463 465 'Deleted file %s via RhodeCode Enterprise') % (f_path)
464 466 c.f_path = f_path
465 467
466 468 return render('files/files_delete.html')
467 469
468 470 @CSRFRequired()
469 471 @LoginRequired()
470 472 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
471 473 def edit(self, repo_name, revision, f_path):
472 474 commit_id = revision
473 475
474 476 repo = c.rhodecode_db_repo
475 477 if repo.enable_locking and repo.locked[0]:
476 478 h.flash(_('This repository has been locked by %s on %s')
477 479 % (h.person_by_id(repo.locked[0]),
478 480 h.format_date(h.time_to_datetime(repo.locked[1]))),
479 481 'warning')
480 482 return redirect(h.url('files_home',
481 483 repo_name=repo_name, revision='tip'))
482 484
483 485 if not self._is_valid_head(commit_id, repo.scm_instance()):
484 486 h.flash(_('You can only edit files with revision '
485 487 'being a valid branch '), category='warning')
486 488 return redirect(h.url('files_home',
487 489 repo_name=repo_name, revision='tip',
488 490 f_path=f_path))
489 491
490 492 c.commit = self.__get_commit_or_redirect(commit_id, repo_name)
491 493 c.file = self.__get_filenode_or_redirect(repo_name, c.commit, f_path)
492 494
493 495 if c.file.is_binary:
494 496 return redirect(url('files_home', repo_name=c.repo_name,
495 497 revision=c.commit.raw_id, f_path=f_path))
496 498 c.default_message = _(
497 499 'Edited file %s via RhodeCode Enterprise') % (f_path)
498 500 c.f_path = f_path
499 501 old_content = c.file.content
500 502 sl = old_content.splitlines(1)
501 503 first_line = sl[0] if sl else ''
502 504
503 505 # modes: 0 - Unix, 1 - Mac, 2 - DOS
504 506 mode = detect_mode(first_line, 0)
505 507 content = convert_line_endings(request.POST.get('content', ''), mode)
506 508
507 509 message = request.POST.get('message') or c.default_message
508 510 org_f_path = c.file.unicode_path
509 511 filename = request.POST['filename']
510 512 org_filename = c.file.name
511 513
512 514 if content == old_content and filename == org_filename:
513 515 h.flash(_('No changes'), category='warning')
514 516 return redirect(url('changeset_home', repo_name=c.repo_name,
515 517 revision='tip'))
516 518 try:
517 519 mapping = {
518 520 org_f_path: {
519 521 'org_filename': org_f_path,
520 522 'filename': os.path.join(c.file.dir_path, filename),
521 523 'content': content,
522 524 'lexer': '',
523 525 'op': 'mod',
524 526 }
525 527 }
526 528
527 529 ScmModel().update_nodes(
528 530 user=c.rhodecode_user.user_id,
529 531 repo=c.rhodecode_db_repo,
530 532 message=message,
531 533 nodes=mapping,
532 534 parent_commit=c.commit,
533 535 )
534 536
535 537 h.flash(_('Successfully committed to %s') % f_path,
536 538 category='success')
537 539 except Exception:
538 540 msg = _('Error occurred during commit')
539 541 log.exception(msg)
540 542 h.flash(msg, category='error')
541 543 return redirect(url('changeset_home',
542 544 repo_name=c.repo_name, revision='tip'))
543 545
544 546 @LoginRequired()
545 547 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
546 548 def edit_home(self, repo_name, revision, f_path):
547 549 commit_id = revision
548 550
549 551 repo = c.rhodecode_db_repo
550 552 if repo.enable_locking and repo.locked[0]:
551 553 h.flash(_('This repository has been locked by %s on %s')
552 554 % (h.person_by_id(repo.locked[0]),
553 555 h.format_date(h.time_to_datetime(repo.locked[1]))),
554 556 'warning')
555 557 return redirect(h.url('files_home',
556 558 repo_name=repo_name, revision='tip'))
557 559
558 560 if not self._is_valid_head(commit_id, repo.scm_instance()):
559 561 h.flash(_('You can only edit files with revision '
560 562 'being a valid branch '), category='warning')
561 563 return redirect(h.url('files_home',
562 564 repo_name=repo_name, revision='tip',
563 565 f_path=f_path))
564 566
565 567 c.commit = self.__get_commit_or_redirect(commit_id, repo_name)
566 568 c.file = self.__get_filenode_or_redirect(repo_name, c.commit, f_path)
567 569
568 570 if c.file.is_binary:
569 571 return redirect(url('files_home', repo_name=c.repo_name,
570 572 revision=c.commit.raw_id, f_path=f_path))
571 573 c.default_message = _(
572 574 'Edited file %s via RhodeCode Enterprise') % (f_path)
573 575 c.f_path = f_path
574 576
575 577 return render('files/files_edit.html')
576 578
577 579 def _is_valid_head(self, commit_id, repo):
578 580 # check if commit is a branch identifier- basically we cannot
579 581 # create multiple heads via file editing
580 582 valid_heads = repo.branches.keys() + repo.branches.values()
581 583
582 584 if h.is_svn(repo) and not repo.is_empty():
583 585 # Note: Subversion only has one head, we add it here in case there
584 586 # is no branch matched.
585 587 valid_heads.append(repo.get_commit(commit_idx=-1).raw_id)
586 588
587 589 # check if commit is a branch name or branch hash
588 590 return commit_id in valid_heads
589 591
590 592 @CSRFRequired()
591 593 @LoginRequired()
592 594 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
593 595 def add(self, repo_name, revision, f_path):
594 596 repo = Repository.get_by_repo_name(repo_name)
595 597 if repo.enable_locking and repo.locked[0]:
596 598 h.flash(_('This repository has been locked by %s on %s')
597 599 % (h.person_by_id(repo.locked[0]),
598 600 h.format_date(h.time_to_datetime(repo.locked[1]))),
599 601 'warning')
600 602 return redirect(h.url('files_home',
601 603 repo_name=repo_name, revision='tip'))
602 604
603 605 r_post = request.POST
604 606
605 607 c.commit = self.__get_commit_or_redirect(
606 608 revision, repo_name, redirect_after=False)
607 609 if c.commit is None:
608 610 c.commit = EmptyCommit(alias=c.rhodecode_repo.alias)
609 611 c.default_message = (_('Added file via RhodeCode Enterprise'))
610 612 c.f_path = f_path
611 613 unix_mode = 0
612 614 content = convert_line_endings(r_post.get('content', ''), unix_mode)
613 615
614 616 message = r_post.get('message') or c.default_message
615 617 filename = r_post.get('filename')
616 618 location = r_post.get('location', '') # dir location
617 619 file_obj = r_post.get('upload_file', None)
618 620
619 621 if file_obj is not None and hasattr(file_obj, 'filename'):
620 622 filename = file_obj.filename
621 623 content = file_obj.file
622 624
623 625 if hasattr(content, 'file'):
624 626 # non posix systems store real file under file attr
625 627 content = content.file
626 628
627 629 # If there's no commit, redirect to repo summary
628 630 if type(c.commit) is EmptyCommit:
629 631 redirect_url = "summary_home"
630 632 else:
631 633 redirect_url = "changeset_home"
632 634
633 635 if not filename:
634 636 h.flash(_('No filename'), category='warning')
635 637 return redirect(url(redirect_url, repo_name=c.repo_name,
636 638 revision='tip'))
637 639
638 640 # extract the location from filename,
639 641 # allows using foo/bar.txt syntax to create subdirectories
640 642 subdir_loc = filename.rsplit('/', 1)
641 643 if len(subdir_loc) == 2:
642 644 location = os.path.join(location, subdir_loc[0])
643 645
644 646 # strip all crap out of file, just leave the basename
645 647 filename = os.path.basename(filename)
646 648 node_path = os.path.join(location, filename)
647 649 author = c.rhodecode_user.full_contact
648 650
649 651 try:
650 652 nodes = {
651 653 node_path: {
652 654 'content': content
653 655 }
654 656 }
655 657 self.scm_model.create_nodes(
656 658 user=c.rhodecode_user.user_id,
657 659 repo=c.rhodecode_db_repo,
658 660 message=message,
659 661 nodes=nodes,
660 662 parent_commit=c.commit,
661 663 author=author,
662 664 )
663 665
664 666 h.flash(_('Successfully committed to %s') % node_path,
665 667 category='success')
666 668 except NonRelativePathError as e:
667 669 h.flash(_(
668 670 'The location specified must be a relative path and must not '
669 671 'contain .. in the path'), category='warning')
670 672 return redirect(url('changeset_home', repo_name=c.repo_name,
671 673 revision='tip'))
672 674 except (NodeError, NodeAlreadyExistsError) as e:
673 675 h.flash(_(e), category='error')
674 676 except Exception:
675 677 msg = _('Error occurred during commit')
676 678 log.exception(msg)
677 679 h.flash(msg, category='error')
678 680 return redirect(url('changeset_home',
679 681 repo_name=c.repo_name, revision='tip'))
680 682
681 683 @LoginRequired()
682 684 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
683 685 def add_home(self, repo_name, revision, f_path):
684 686
685 687 repo = Repository.get_by_repo_name(repo_name)
686 688 if repo.enable_locking and repo.locked[0]:
687 689 h.flash(_('This repository has been locked by %s on %s')
688 690 % (h.person_by_id(repo.locked[0]),
689 691 h.format_date(h.time_to_datetime(repo.locked[1]))),
690 692 'warning')
691 693 return redirect(h.url('files_home',
692 694 repo_name=repo_name, revision='tip'))
693 695
694 696 c.commit = self.__get_commit_or_redirect(
695 697 revision, repo_name, redirect_after=False)
696 698 if c.commit is None:
697 699 c.commit = EmptyCommit(alias=c.rhodecode_repo.alias)
698 700 c.default_message = (_('Added file via RhodeCode Enterprise'))
699 701 c.f_path = f_path
700 702
701 703 return render('files/files_add.html')
702 704
703 705 @LoginRequired()
704 706 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
705 707 'repository.admin')
706 708 def archivefile(self, repo_name, fname):
707 709 fileformat = None
708 710 commit_id = None
709 711 ext = None
710 712 subrepos = request.GET.get('subrepos') == 'true'
711 713
712 714 for a_type, ext_data in settings.ARCHIVE_SPECS.items():
713 715 archive_spec = fname.split(ext_data[1])
714 716 if len(archive_spec) == 2 and archive_spec[1] == '':
715 717 fileformat = a_type or ext_data[1]
716 718 commit_id = archive_spec[0]
717 719 ext = ext_data[1]
718 720
719 721 dbrepo = RepoModel().get_by_repo_name(repo_name)
720 722 if not dbrepo.enable_downloads:
721 723 return _('Downloads disabled')
722 724
723 725 try:
724 726 commit = c.rhodecode_repo.get_commit(commit_id)
725 727 content_type = settings.ARCHIVE_SPECS[fileformat][0]
726 728 except CommitDoesNotExistError:
727 729 return _('Unknown revision %s') % commit_id
728 730 except EmptyRepositoryError:
729 731 return _('Empty repository')
730 732 except KeyError:
731 733 return _('Unknown archive type')
732 734
733 735 # archive cache
734 736 from rhodecode import CONFIG
735 737
736 738 archive_name = '%s-%s%s%s' % (
737 739 safe_str(repo_name.replace('/', '_')),
738 740 '-sub' if subrepos else '',
739 741 safe_str(commit.short_id), ext)
740 742
741 743 use_cached_archive = False
742 744 archive_cache_enabled = CONFIG.get(
743 745 'archive_cache_dir') and not request.GET.get('no_cache')
744 746
745 747 if archive_cache_enabled:
746 748 # check if we it's ok to write
747 749 if not os.path.isdir(CONFIG['archive_cache_dir']):
748 750 os.makedirs(CONFIG['archive_cache_dir'])
749 751 cached_archive_path = os.path.join(
750 752 CONFIG['archive_cache_dir'], archive_name)
751 753 if os.path.isfile(cached_archive_path):
752 754 log.debug('Found cached archive in %s', cached_archive_path)
753 755 fd, archive = None, cached_archive_path
754 756 use_cached_archive = True
755 757 else:
756 758 log.debug('Archive %s is not yet cached', archive_name)
757 759
758 760 if not use_cached_archive:
759 761 # generate new archive
760 762 fd, archive = tempfile.mkstemp()
761 763 log.debug('Creating new temp archive in %s' % (archive,))
762 764 try:
763 765 commit.archive_repo(archive, kind=fileformat, subrepos=subrepos)
764 766 except ImproperArchiveTypeError:
765 767 return _('Unknown archive type')
766 768 if archive_cache_enabled:
767 769 # if we generated the archive and we have cache enabled
768 770 # let's use this for future
769 771 log.debug('Storing new archive in %s' % (cached_archive_path,))
770 772 shutil.move(archive, cached_archive_path)
771 773 archive = cached_archive_path
772 774
773 775 def get_chunked_archive(archive):
774 776 with open(archive, 'rb') as stream:
775 777 while True:
776 778 data = stream.read(16 * 1024)
777 779 if not data:
778 780 if fd: # fd means we used temporary file
779 781 os.close(fd)
780 782 if not archive_cache_enabled:
781 783 log.debug('Destroying temp archive %s', archive)
782 784 os.remove(archive)
783 785 break
784 786 yield data
785 787
786 788 # store download action
787 789 action_logger(user=c.rhodecode_user,
788 790 action='user_downloaded_archive:%s' % archive_name,
789 791 repo=repo_name, ipaddr=self.ip_addr, commit=True)
790 792 response.content_disposition = str(
791 793 'attachment; filename=%s' % archive_name)
792 794 response.content_type = str(content_type)
793 795
794 796 return get_chunked_archive(archive)
795 797
796 798 @LoginRequired()
797 799 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
798 800 'repository.admin')
799 801 def diff(self, repo_name, f_path):
800 802 ignore_whitespace = request.GET.get('ignorews') == '1'
801 803 line_context = request.GET.get('context', 3)
802 804 diff1 = request.GET.get('diff1', '')
803 805
804 806 path1, diff1 = parse_path_ref(diff1, default_path=f_path)
805 807
806 808 diff2 = request.GET.get('diff2', '')
807 809 c.action = request.GET.get('diff')
808 810 c.no_changes = diff1 == diff2
809 811 c.f_path = f_path
810 812 c.big_diff = False
811 813 c.ignorews_url = _ignorews_url
812 814 c.context_url = _context_url
813 815 c.changes = OrderedDict()
814 816 c.changes[diff2] = []
815 817
816 818 if not any((diff1, diff2)):
817 819 h.flash(
818 820 'Need query parameter "diff1" or "diff2" to generate a diff.',
819 821 category='error')
820 822 raise HTTPBadRequest()
821 823
822 824 # special case if we want a show commit_id only, it's impl here
823 825 # to reduce JS and callbacks
824 826
825 827 if request.GET.get('show_rev') and diff1:
826 828 if str2bool(request.GET.get('annotate', 'False')):
827 829 _url = url('files_annotate_home', repo_name=c.repo_name,
828 830 revision=diff1, f_path=path1)
829 831 else:
830 832 _url = url('files_home', repo_name=c.repo_name,
831 833 revision=diff1, f_path=path1)
832 834
833 835 return redirect(_url)
834 836
835 837 try:
836 838 node1 = self._get_file_node(diff1, path1)
837 839 node2 = self._get_file_node(diff2, f_path)
838 840 except (RepositoryError, NodeError):
839 841 log.exception("Exception while trying to get node from repository")
840 842 return redirect(url(
841 843 'files_home', repo_name=c.repo_name, f_path=f_path))
842 844
843 845 if all(isinstance(node.commit, EmptyCommit)
844 846 for node in (node1, node2)):
845 847 raise HTTPNotFound
846 848
847 849 c.commit_1 = node1.commit
848 850 c.commit_2 = node2.commit
849 851
850 852 if c.action == 'download':
851 853 _diff = diffs.get_gitdiff(node1, node2,
852 854 ignore_whitespace=ignore_whitespace,
853 855 context=line_context)
854 856 diff = diffs.DiffProcessor(_diff, format='gitdiff')
855 857
856 858 diff_name = '%s_vs_%s.diff' % (diff1, diff2)
857 859 response.content_type = 'text/plain'
858 860 response.content_disposition = (
859 861 'attachment; filename=%s' % (diff_name,)
860 862 )
861 863 charset = self._get_default_encoding()
862 864 if charset:
863 865 response.charset = charset
864 866 return diff.as_raw()
865 867
866 868 elif c.action == 'raw':
867 869 _diff = diffs.get_gitdiff(node1, node2,
868 870 ignore_whitespace=ignore_whitespace,
869 871 context=line_context)
870 872 diff = diffs.DiffProcessor(_diff, format='gitdiff')
871 873 response.content_type = 'text/plain'
872 874 charset = self._get_default_encoding()
873 875 if charset:
874 876 response.charset = charset
875 877 return diff.as_raw()
876 878
877 879 else:
878 880 fid = h.FID(diff2, node2.path)
879 881 line_context_lcl = get_line_ctx(fid, request.GET)
880 882 ign_whitespace_lcl = get_ignore_ws(fid, request.GET)
881 883
882 884 __, commit1, commit2, diff, st, data = diffs.wrapped_diff(
883 885 filenode_old=node1,
884 886 filenode_new=node2,
885 887 diff_limit=self.cut_off_limit_diff,
886 888 file_limit=self.cut_off_limit_file,
887 889 show_full_diff=request.GET.get('fulldiff'),
888 890 ignore_whitespace=ign_whitespace_lcl,
889 891 line_context=line_context_lcl,)
890 892
891 893 c.lines_added = data['stats']['added'] if data else 0
892 894 c.lines_deleted = data['stats']['deleted'] if data else 0
893 895 c.files = [data]
894 896 c.commit_ranges = [c.commit_1, c.commit_2]
895 897 c.ancestor = None
896 898 c.statuses = []
897 899 c.target_repo = c.rhodecode_db_repo
898 900 c.filename1 = node1.path
899 901 c.filename = node2.path
900 902 c.binary_file = node1.is_binary or node2.is_binary
901 903 operation = data['operation'] if data else ''
902 904
903 905 commit_changes = {
904 906 # TODO: it's passing the old file to the diff to keep the
905 907 # standard but this is not being used for this template,
906 908 # but might need both files in the future or a more standard
907 909 # way to work with that
908 910 'fid': [commit1, commit2, operation,
909 911 c.filename, diff, st, data]
910 912 }
911 913
912 914 c.changes = commit_changes
913 915
914 916 return render('files/file_diff.html')
915 917
916 918 @LoginRequired()
917 919 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
918 920 'repository.admin')
919 921 def diff_2way(self, repo_name, f_path):
920 922 diff1 = request.GET.get('diff1', '')
921 923 diff2 = request.GET.get('diff2', '')
922 924
923 925 nodes = []
924 926 unknown_commits = []
925 927 for commit in [diff1, diff2]:
926 928 try:
927 929 nodes.append(self._get_file_node(commit, f_path))
928 930 except (RepositoryError, NodeError):
929 931 log.exception('%(commit)s does not exist' % {'commit': commit})
930 932 unknown_commits.append(commit)
931 933 h.flash(h.literal(
932 934 _('Commit %(commit)s does not exist.') % {'commit': commit}
933 935 ), category='error')
934 936
935 937 if unknown_commits:
936 938 return redirect(url('files_home', repo_name=c.repo_name,
937 939 f_path=f_path))
938 940
939 941 if all(isinstance(node.commit, EmptyCommit) for node in nodes):
940 942 raise HTTPNotFound
941 943
942 944 node1, node2 = nodes
943 945
944 946 f_gitdiff = diffs.get_gitdiff(node1, node2, ignore_whitespace=False)
945 947 diff_processor = diffs.DiffProcessor(f_gitdiff, format='gitdiff')
946 948 diff_data = diff_processor.prepare()
947 949
948 950 if not diff_data or diff_data[0]['raw_diff'] == '':
949 951 h.flash(h.literal(_('%(file_path)s has not changed '
950 952 'between %(commit_1)s and %(commit_2)s.') % {
951 953 'file_path': f_path,
952 954 'commit_1': node1.commit.id,
953 955 'commit_2': node2.commit.id
954 956 }), category='error')
955 957 return redirect(url('files_home', repo_name=c.repo_name,
956 958 f_path=f_path))
957 959
958 960 c.diff_data = diff_data[0]
959 961 c.FID = h.FID(diff2, node2.path)
960 962 # cleanup some unneeded data
961 963 del c.diff_data['raw_diff']
962 964 del c.diff_data['chunks']
963 965
964 966 c.node1 = node1
965 967 c.commit_1 = node1.commit
966 968 c.node2 = node2
967 969 c.commit_2 = node2.commit
968 970
969 971 return render('files/diff_2way.html')
970 972
971 973 def _get_file_node(self, commit_id, f_path):
972 974 if commit_id not in ['', None, 'None', '0' * 12, '0' * 40]:
973 975 commit = c.rhodecode_repo.get_commit(commit_id=commit_id)
974 976 try:
975 977 node = commit.get_node(f_path)
976 978 if node.is_dir():
977 979 raise NodeError('%s path is a %s not a file'
978 980 % (node, type(node)))
979 981 except NodeDoesNotExistError:
980 982 commit = EmptyCommit(
981 983 commit_id=commit_id,
982 984 idx=commit.idx,
983 985 repo=commit.repository,
984 986 alias=commit.repository.alias,
985 987 message=commit.message,
986 988 author=commit.author,
987 989 date=commit.date)
988 990 node = FileNode(f_path, '', commit=commit)
989 991 else:
990 992 commit = EmptyCommit(
991 993 repo=c.rhodecode_repo,
992 994 alias=c.rhodecode_repo.alias)
993 995 node = FileNode(f_path, '', commit=commit)
994 996 return node
995 997
996 998 def _get_node_history(self, commit, f_path, commits=None):
997 999 """
998 1000 get commit history for given node
999 1001
1000 1002 :param commit: commit to calculate history
1001 1003 :param f_path: path for node to calculate history for
1002 1004 :param commits: if passed don't calculate history and take
1003 1005 commits defined in this list
1004 1006 """
1005 1007 # calculate history based on tip
1006 1008 tip = c.rhodecode_repo.get_commit()
1007 1009 if commits is None:
1008 1010 pre_load = ["author", "branch"]
1009 1011 try:
1010 1012 commits = tip.get_file_history(f_path, pre_load=pre_load)
1011 1013 except (NodeDoesNotExistError, CommitError):
1012 1014 # this node is not present at tip!
1013 1015 commits = commit.get_file_history(f_path, pre_load=pre_load)
1014 1016
1015 1017 history = []
1016 1018 commits_group = ([], _("Changesets"))
1017 1019 for commit in commits:
1018 1020 branch = ' (%s)' % commit.branch if commit.branch else ''
1019 1021 n_desc = 'r%s:%s%s' % (commit.idx, commit.short_id, branch)
1020 1022 commits_group[0].append((commit.raw_id, n_desc,))
1021 1023 history.append(commits_group)
1022 1024
1023 1025 symbolic_reference = self._symbolic_reference
1024 1026
1025 1027 if c.rhodecode_repo.alias == 'svn':
1026 1028 adjusted_f_path = self._adjust_file_path_for_svn(
1027 1029 f_path, c.rhodecode_repo)
1028 1030 if adjusted_f_path != f_path:
1029 1031 log.debug(
1030 1032 'Recognized svn tag or branch in file "%s", using svn '
1031 1033 'specific symbolic references', f_path)
1032 1034 f_path = adjusted_f_path
1033 1035 symbolic_reference = self._symbolic_reference_svn
1034 1036
1035 1037 branches = self._create_references(
1036 1038 c.rhodecode_repo.branches, symbolic_reference, f_path)
1037 1039 branches_group = (branches, _("Branches"))
1038 1040
1039 1041 tags = self._create_references(
1040 1042 c.rhodecode_repo.tags, symbolic_reference, f_path)
1041 1043 tags_group = (tags, _("Tags"))
1042 1044
1043 1045 history.append(branches_group)
1044 1046 history.append(tags_group)
1045 1047
1046 1048 return history, commits
1047 1049
1048 1050 def _adjust_file_path_for_svn(self, f_path, repo):
1049 1051 """
1050 1052 Computes the relative path of `f_path`.
1051 1053
1052 1054 This is mainly based on prefix matching of the recognized tags and
1053 1055 branches in the underlying repository.
1054 1056 """
1055 1057 tags_and_branches = itertools.chain(
1056 1058 repo.branches.iterkeys(),
1057 1059 repo.tags.iterkeys())
1058 1060 tags_and_branches = sorted(tags_and_branches, key=len, reverse=True)
1059 1061
1060 1062 for name in tags_and_branches:
1061 1063 if f_path.startswith(name + '/'):
1062 1064 f_path = vcspath.relpath(f_path, name)
1063 1065 break
1064 1066 return f_path
1065 1067
1066 1068 def _create_references(
1067 1069 self, branches_or_tags, symbolic_reference, f_path):
1068 1070 items = []
1069 1071 for name, commit_id in branches_or_tags.items():
1070 1072 sym_ref = symbolic_reference(commit_id, name, f_path)
1071 1073 items.append((sym_ref, name))
1072 1074 return items
1073 1075
1074 1076 def _symbolic_reference(self, commit_id, name, f_path):
1075 1077 return commit_id
1076 1078
1077 1079 def _symbolic_reference_svn(self, commit_id, name, f_path):
1078 1080 new_f_path = vcspath.join(name, f_path)
1079 1081 return u'%s@%s' % (new_f_path, commit_id)
1080 1082
1081 1083 @LoginRequired()
1082 1084 @XHRRequired()
1083 1085 @HasRepoPermissionAnyDecorator(
1084 1086 'repository.read', 'repository.write', 'repository.admin')
1085 1087 @jsonify
1086 1088 def nodelist(self, repo_name, revision, f_path):
1087 1089 commit = self.__get_commit_or_redirect(revision, repo_name)
1088 1090
1089 1091 metadata = self._get_nodelist_at_commit(
1090 1092 repo_name, commit.raw_id, f_path)
1091 1093 return {'nodes': metadata}
1092 1094
1093 1095 @LoginRequired()
1094 1096 @XHRRequired()
1095 1097 @HasRepoPermissionAnyDecorator(
1096 1098 'repository.read', 'repository.write', 'repository.admin')
1097 1099 def nodetree_full(self, repo_name, commit_id, f_path):
1098 1100 """
1099 1101 Returns rendered html of file tree that contains commit date,
1100 1102 author, revision for the specified combination of
1101 1103 repo, commit_id and file path
1102 1104
1103 1105 :param repo_name: name of the repository
1104 1106 :param commit_id: commit_id of file tree
1105 1107 :param f_path: file path of the requested directory
1106 1108 """
1107 1109
1108 1110 commit = self.__get_commit_or_redirect(commit_id, repo_name)
1109 1111 try:
1110 1112 dir_node = commit.get_node(f_path)
1111 1113 except RepositoryError as e:
1112 1114 return 'error {}'.format(safe_str(e))
1113 1115
1114 1116 if dir_node.is_file():
1115 1117 return ''
1116 1118
1117 1119 c.file = dir_node
1118 1120 c.commit = commit
1119 1121
1120 1122 # using force=True here, make a little trick. We flush the cache and
1121 1123 # compute it using the same key as without full_load, so the fully
1122 1124 # loaded cached tree is now returned instead of partial
1123 1125 return self._get_tree_at_commit(
1124 1126 repo_name, commit.raw_id, dir_node.path, full_load=True,
1125 1127 force=True)
@@ -1,287 +1,286 b''
1 1 <%inherit file="/base/base.html"/>
2 2
3 3 <%def name="title(*args)">
4 4 ${_('%s Files') % 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 <div class="title">
31 31 ${self.repo_page_title(c.rhodecode_db_repo)}
32 32 </div>
33 33
34 34 <div id="pjax-container" class="summary">
35 35 <div id="files_data">
36 36 <%include file='files_pjax.html'/>
37 37 </div>
38 38 </div>
39 39 <script>
40 40 var curState = {
41 41 commit_id: "${c.commit.raw_id}"
42 42 };
43 43
44 44 var getState = function(context) {
45 45 var url = $(location).attr('href');
46 46 var _base_url = '${h.url("files_home",repo_name=c.repo_name,revision='',f_path='')}';
47 47 var _annotate_url = '${h.url("files_annotate_home",repo_name=c.repo_name,revision='',f_path='')}';
48 48 _base_url = _base_url.replace('//', '/');
49 49 _annotate_url = _annotate_url.replace('//', '/');
50 50
51 51 //extract f_path from url.
52 52 var parts = url.split(_base_url);
53 53 if (parts.length != 2) {
54 54 parts = url.split(_annotate_url);
55 55 if (parts.length != 2) {
56 56 var rev = "tip";
57 57 var f_path = "";
58 58 } else {
59 59 var parts2 = parts[1].split('/');
60 60 var rev = parts2.shift(); // pop the first element which is the revision
61 61 var f_path = parts2.join('/');
62 62 }
63 63
64 64 } else {
65 65 var parts2 = parts[1].split('/');
66 66 var rev = parts2.shift(); // pop the first element which is the revision
67 67 var f_path = parts2.join('/');
68 68 }
69 69
70 70 var _node_list_url = pyroutes.url('files_nodelist_home',
71 71 {repo_name: templateContext.repo_name,
72 72 revision: rev, f_path: f_path});
73 73 var _url_base = pyroutes.url('files_home',
74 74 {repo_name: templateContext.repo_name,
75 75 revision: rev, f_path:'__FPATH__'});
76 76 return {
77 77 url: url,
78 78 f_path: f_path,
79 79 rev: rev,
80 80 commit_id: curState.commit_id,
81 81 node_list_url: _node_list_url,
82 82 url_base: _url_base
83 83 };
84 84 };
85 85
86 86 var metadataRequest = null;
87 87 var getFilesMetadata = function() {
88 88 if (metadataRequest && metadataRequest.readyState != 4) {
89 89 metadataRequest.abort();
90 90 }
91 if (source_page) {
91 if (fileSourcePage) {
92 92 return false;
93 93 }
94 94
95 95 if ($('#file-tree-wrapper').hasClass('full-load')) {
96 96 // in case our HTML wrapper has full-load class we don't
97 97 // trigger the async load of metadata
98 98 return false;
99 99 }
100 100
101 101 var state = getState('metadata');
102 102 var url_data = {
103 103 'repo_name': templateContext.repo_name,
104 104 'commit_id': state.commit_id,
105 105 'f_path': state.f_path
106 106 };
107 107
108 108 var url = pyroutes.url('files_nodetree_full', url_data);
109 109
110 110 metadataRequest = $.ajax({url: url});
111 111
112 112 metadataRequest.done(function(data) {
113 113 $('#file-tree').html(data);
114 114 timeagoActivate();
115 115 });
116 116 metadataRequest.fail(function (data, textStatus, errorThrown) {
117 117 console.log(data);
118 118 if (data.status != 0) {
119 119 alert("Error while fetching metadata.\nError code {0} ({1}).Please consider reloading the page".format(data.status,data.statusText));
120 120 }
121 121 });
122 122 };
123 123
124 124 var callbacks = function() {
125 125 var state = getState('callbacks');
126 126 timeagoActivate();
127 127
128 128 // used for history, and switch to
129 129 var initialCommitData = {
130 130 id: null,
131 131 text: '${_("Switch To Commit")}',
132 132 type: 'sha',
133 133 raw_id: null,
134 134 files_url: null
135 135 };
136 136
137 137 if ($('#trimmed_message_box').height() < 50) {
138 138 $('#message_expand').hide();
139 139 }
140 140
141 141 $('#message_expand').on('click', function(e) {
142 142 $('#trimmed_message_box').css('max-height', 'none');
143 143 $(this).hide();
144 144 });
145 145
146
147 if (source_page) {
146 if (fileSourcePage) {
148 147 // variants for with source code, not tree view
149 148
150 149 // select code link event
151 150 $("#hlcode").mouseup(getSelectionLink);
152 151
153 152 // file history select2
154 153 select2FileHistorySwitcher('#diff1', initialCommitData, state);
155 154 $('#diff1').on('change', function(e) {
156 155 $('#diff').removeClass('disabled').removeAttr("disabled");
157 156 $('#show_rev').removeClass('disabled').removeAttr("disabled");
158 157 });
159 158
160 159 // show more authors
161 160 $('#show_authors').on('click', function(e) {
162 161 e.preventDefault();
163 162 var url = pyroutes.url('files_authors_home',
164 163 {'repo_name': templateContext.repo_name,
165 164 'revision': state.rev, 'f_path': state.f_path});
166 165
167 166 $.pjax({
168 167 url: url,
169 168 data: 'annotate=${"1" if c.annotate else "0"}',
170 169 container: '#file_authors',
171 170 push: false,
172 171 timeout: pjaxTimeout
173 172 }).complete(function(){
174 173 $('#show_authors').hide();
175 174 })
176 175 });
177 176
178 177 // load file short history
179 178 $('#file_history_overview').on('click', function(e) {
180 179 e.preventDefault();
181 180 path = state.f_path;
182 181 if (path.indexOf("#") >= 0) {
183 182 path = path.slice(0, path.indexOf("#"));
184 183 }
185 184 var url = pyroutes.url('changelog_file_home',
186 185 {'repo_name': templateContext.repo_name,
187 186 'revision': state.rev, 'f_path': path, 'limit': 6});
188 187 $('#file_history_container').show();
189 188 $('#file_history_container').html('<div class="file-history-inner">{0}</div>'.format(_gettext('Loading ...')));
190 189
191 190 $.pjax({
192 191 url: url,
193 192 container: '#file_history_container',
194 193 push: false,
195 194 timeout: pjaxTimeout
196 195 })
197 196 });
198 197
199 198 }
200 199 else {
201 200 getFilesMetadata();
202 201
203 202 // fuzzy file filter
204 203 fileBrowserListeners(state.node_list_url, state.url_base);
205 204
206 205 // switch to widget
207 206 select2RefSwitcher('#refs_filter', initialCommitData);
208 207 $('#refs_filter').on('change', function(e) {
209 208 var data = $('#refs_filter').select2('data');
210 209 curState.commit_id = data.raw_id;
211 210 $.pjax({url: data.files_url, container: '#pjax-container', timeout: pjaxTimeout});
212 211 });
213 212
214 213 $("#prev_commit_link").on('click', function(e) {
215 214 var data = $(this).data();
216 215 curState.commit_id = data.commitId;
217 216 });
218 217
219 218 $("#next_commit_link").on('click', function(e) {
220 219 var data = $(this).data();
221 220 curState.commit_id = data.commitId;
222 221 });
223 222
224 223 $('#at_rev').on("keypress", function(e) {
225 224 /* ENTER PRESSED */
226 225 if (e.keyCode === 13) {
227 226 var rev = $('#at_rev').val();
228 227 // explicit reload page here. with pjax entering bad input
229 228 // produces not so nice results
230 229 window.location = pyroutes.url('files_home',
231 230 {'repo_name': templateContext.repo_name,
232 231 'revision': rev, 'f_path': state.f_path});
233 232 }
234 233 });
235 234 }
236 235 };
237 236
238 237 var pjaxTimeout = 5000;
239 238
240 239 $(document).pjax(".pjax-link", "#pjax-container", {
241 240 "fragment": "#pjax-content",
242 241 "maxCacheLength": 1000,
243 242 "timeout": pjaxTimeout
244 243 });
245 244
246 245 // define global back/forward states
247 246 var isPjaxPopState = false;
248 247 $(document).on('pjax:popstate', function() {
249 248 isPjaxPopState = true;
250 249 });
251 250
252 251 $(document).on('pjax:end', function(xhr, options) {
253 252 if (isPjaxPopState) {
254 253 isPjaxPopState = false;
255 254 callbacks();
256 255 _NODEFILTER.resetFilter();
257 256 }
258 257
259 258 // run callback for tracking if defined for google analytics etc.
260 259 // this is used to trigger tracking on pjax
261 260 if (typeof window.rhodecode_statechange_callback !== 'undefined') {
262 261 var state = getState('statechange');
263 262 rhodecode_statechange_callback(state.url, null)
264 263 }
265 264 });
266 265
267 266 $(document).on('pjax:success', function(event, xhr, options) {
268 267 if (event.target.id == "file_history_container") {
269 268 $('#file_history_overview').hide();
270 269 $('#file_history_overview_full').show();
271 270 timeagoActivate();
272 271 } else {
273 272 callbacks();
274 273 }
275 274 });
276 275
277 276 $(document).ready(function() {
278 277 callbacks();
279 278 var search_GET = "${request.GET.get('search','')}";
280 279 if (search_GET == "1") {
281 280 _NODEFILTER.initFilter();
282 281 }
283 282 });
284 283
285 284 </script>
286 285
287 286 </%def>
@@ -1,53 +1,49 b''
1 1
2 2 <div id="codeblock" class="browserblock">
3 3 <div class="browser-header">
4 4 <div class="browser-nav">
5 5 ${h.form(h.url.current(), method='GET', id='at_rev_form')}
6 6 <div class="info_box">
7 7 ${h.hidden('refs_filter')}
8 8 <div class="info_box_elem previous">
9 9 <a id="prev_commit_link" data-commit-id="${c.prev_commit.raw_id}" class="pjax-link ${'disabled' if c.url_prev == '#' else ''}" href="${c.url_prev}" title="${_('Previous commit')}"><i class="icon-chevron-left"></i></a>
10 10 </div>
11 11 <div class="info_box_elem">${h.text('at_rev',value=c.commit.revision)}</div>
12 12 <div class="info_box_elem next">
13 13 <a id="next_commit_link" data-commit-id="${c.next_commit.raw_id}" class="pjax-link ${'disabled' if c.url_next == '#' else ''}" href="${c.url_next}" title="${_('Next commit')}"><i class="icon-chevron-right"></i></a>
14 14 </div>
15 15 </div>
16 16 ${h.end_form()}
17 17
18 18 <div id="search_activate_id" class="search_activate">
19 19 <a class="btn btn-default" id="filter_activate" href="javascript:void(0)">${_('Search File List')}</a>
20 20 </div>
21 21 <div id="search_deactivate_id" class="search_activate hidden">
22 22 <a class="btn btn-default" id="filter_deactivate" href="javascript:void(0)">${_('Close File List')}</a>
23 23 </div>
24 24 % if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name):
25 25 <div title="${_('Add New File')}" class="btn btn-primary new-file">
26 26 <a href="${h.url('files_add_home',repo_name=c.repo_name,revision=c.commit.raw_id,f_path=c.f_path, anchor='edit')}">
27 27 ${_('Add File')}</a>
28 28 </div>
29 29 % endif
30 30 </div>
31 31
32 32 <div class="browser-search">
33 33 <div class="node-filter">
34 34 <div class="node_filter_box hidden" id="node_filter_box_loading" >${_('Loading file list...')}</div>
35 35 <div class="node_filter_box hidden" id="node_filter_box" >
36 36 <div class="node-filter-path">${h.get_last_path_part(c.file)}/</div>
37 37 <div class="node-filter-input">
38 38 <input class="init" type="text" name="filter" size="25" id="node_filter" autocomplete="off">
39 39 </div>
40 40 </div>
41 41 </div>
42 42 </div>
43 43 </div>
44 44 ## file tree is computed from caches, and filled in
45 45 <div id="file-tree">
46 46 ${c.file_tree}
47 47 </div>
48 48
49 49 </div>
50
51 <script>
52 var source_page = false;
53 </script>
@@ -1,52 +1,55 b''
1 1 <%def name="title(*args)">
2 2 ${_('%s Files') % c.repo_name}
3 3 %if hasattr(c,'file'):
4 4 &middot; ${h.safe_unicode(c.file.path) or '\\'}
5 5 %endif
6 6
7 7 %if c.rhodecode_name:
8 8 &middot; ${h.branding(c.rhodecode_name)}
9 9 %endif
10 10 </%def>
11 11
12 12 <div id="pjax-content" data-title="${self.title()}">
13 13 <div class="summary-detail">
14 14 <div class="summary-detail-header">
15 15 <div class="breadcrumbs files_location">
16 16 <h4>
17 17 ${_('Location')}: ${h.files_breadcrumbs(c.repo_name,c.commit.raw_id,c.file.path)}
18 18 %if c.annotate:
19 19 - ${_('annotation')}
20 20 %endif
21 21 </h4>
22 22 </div>
23 23 <div class="btn-collapse" data-toggle="summary-details">
24 24 ${_('Show More')}
25 25 </div>
26 26 </div><!--end summary-detail-header-->
27 27
28 28 % if c.file.is_submodule():
29 29 <span class="submodule-dir">Submodule ${h.escape(c.file.name)}</span>
30 30 % elif c.file.is_dir():
31 31 <%include file='file_tree_detail.html'/>
32 32 % else:
33 33 <%include file='files_detail.html'/>
34 34 % endif
35 35
36 36 </div> <!--end summary-detail-->
37
37 <script>
38 // set the pageSource variable
39 var fileSourcePage = ${c.file_source_page};
40 </script>
38 41 % if c.file.is_dir():
39 42 <div id="commit-stats" class="sidebar-right">
40 43 <%include file='file_tree_author_box.html'/>
41 44 </div>
42 45
43 46 <%include file='files_browser.html'/>
44 47 % else:
45 48 <div id="file_authors" class="sidebar-right">
46 49 <%include file='file_authors_box.html'/>
47 50 </div>
48 51
49 52 <%include file='files_source.html'/>
50 53 % endif
51 54
52 55 </div> No newline at end of file
@@ -1,82 +1,78 b''
1 1 <%namespace name="sourceblock" file="/codeblocks/source.html"/>
2 2
3 3 <div id="codeblock" class="codeblock">
4 4 <div class="codeblock-header">
5 5 <div class="stats">
6 6 <span> <strong>${c.file}</strong></span>
7 7 <span> | ${c.file.lines()[0]} ${ungettext('line', 'lines', c.file.lines()[0])}</span>
8 8 <span> | ${h.format_byte_size_binary(c.file.size)}</span>
9 9 <span> | ${c.file.mimetype} </span>
10 10 <span class="item last"> | ${h.get_lexer_for_filenode(c.file).__class__.__name__}</span>
11 11 </div>
12 12 <div class="buttons">
13 13 <a id="file_history_overview" href="#">
14 14 ${_('History')}
15 15 </a>
16 16 <a id="file_history_overview_full" style="display: none" href="${h.url('changelog_file_home',repo_name=c.repo_name, revision=c.commit.raw_id, f_path=c.f_path)}">
17 17 ${_('Show Full History')}
18 18 </a> |
19 19 %if c.annotate:
20 20 ${h.link_to(_('Source'), h.url('files_home', repo_name=c.repo_name,revision=c.commit.raw_id,f_path=c.f_path))}
21 21 %else:
22 22 ${h.link_to(_('Annotation'), h.url('files_annotate_home',repo_name=c.repo_name,revision=c.commit.raw_id,f_path=c.f_path))}
23 23 %endif
24 24 | ${h.link_to(_('Raw'), h.url('files_raw_home',repo_name=c.repo_name,revision=c.commit.raw_id,f_path=c.f_path))}
25 25 | <a href="${h.url('files_rawfile_home',repo_name=c.repo_name,revision=c.commit.raw_id,f_path=c.f_path)}">
26 26 ${_('Download')}
27 27 </a>
28 28
29 29 %if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name):
30 30 |
31 31 %if c.on_branch_head and c.branch_or_raw_id and not c.file.is_binary:
32 32 <a href="${h.url('files_edit_home',repo_name=c.repo_name,revision=c.branch_or_raw_id,f_path=c.f_path, anchor='edit')}">
33 33 ${_('Edit on Branch:%s') % c.branch_or_raw_id}
34 34 </a>
35 35 | <a class="btn-danger btn-link" href="${h.url('files_delete_home',repo_name=c.repo_name,revision=c.branch_or_raw_id,f_path=c.f_path, anchor='edit')}">${_('Delete')}
36 36 </a>
37 37 %elif c.on_branch_head and c.branch_or_raw_id and c.file.is_binary:
38 38 ${h.link_to(_('Edit'), '#', class_="btn btn-link disabled tooltip", title=_('Editing binary files not allowed'))}
39 39 | ${h.link_to(_('Delete'), h.url('files_delete_home',repo_name=c.repo_name,revision=c.branch_or_raw_id,f_path=c.f_path, anchor='edit'),class_="btn-danger btn-link")}
40 40 %else:
41 41 ${h.link_to(_('Edit'), '#', class_="btn btn-link disabled tooltip", title=_('Editing files allowed only when on branch head commit'))}
42 42 | ${h.link_to(_('Delete'), '#', class_="btn btn-danger btn-link disabled tooltip", title=_('Deleting files allowed only when on branch head commit'))}
43 43 %endif
44 44 %endif
45 45 </div>
46 46 </div>
47 47 <div id="file_history_container"></div>
48 48 <div class="code-body">
49 49 %if c.file.is_binary:
50 50 <div>
51 51 ${_('Binary file (%s)') % c.file.mimetype}
52 52 </div>
53 53 %else:
54 54 % if c.file.size < c.cut_off_limit:
55 55 %if c.renderer and not c.annotate:
56 56 ${h.render(c.file.content, renderer=c.renderer)}
57 57 %else:
58 58 <table class="cb codehilite">
59 59 %if c.annotate:
60 60 <% color_hasher = h.color_hasher() %>
61 61 %for annotation, lines in c.annotated_lines:
62 62 ${sourceblock.render_annotation_lines(annotation, lines, color_hasher)}
63 63 %endfor
64 64 %else:
65 65 %for line_num, tokens in enumerate(c.lines, 1):
66 66 ${sourceblock.render_line(line_num, tokens)}
67 67 %endfor
68 68 %endif
69 69 </table>
70 70 </div>
71 71 %endif
72 72 %else:
73 73 ${_('File is too big to display')} ${h.link_to(_('Show as raw'),
74 74 h.url('files_raw_home',repo_name=c.repo_name,revision=c.commit.raw_id,f_path=c.f_path))}
75 75 %endif
76 76 %endif
77 77 </div>
78 </div>
79
80 <script>
81 var source_page = true;
82 </script>
78 </div> No newline at end of file
General Comments 0
You need to be logged in to leave comments. Login now