##// END OF EJS Templates
files: fix problem with specifing custom path for filename....
marcink -
r1688:b6ddc59f default
parent child Browse files
Show More
@@ -1,1101 +1,1102 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2017 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.mako')
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 226 c.lf_node = c.file.get_largefile_node()
227 227
228 228 c.file_source_page = 'true'
229 229 c.file_last_commit = c.file.last_commit
230 230 if c.file.size < self.cut_off_limit_file:
231 231 if c.annotate: # annotation has precedence over renderer
232 232 c.annotated_lines = filenode_as_annotated_lines_tokens(
233 233 c.file
234 234 )
235 235 else:
236 236 c.renderer = (
237 237 c.renderer and h.renderer_from_filename(c.file.path)
238 238 )
239 239 if not c.renderer:
240 240 c.lines = filenode_as_lines_tokens(c.file)
241 241
242 242 c.on_branch_head = self._is_valid_head(
243 243 commit_id, c.rhodecode_repo)
244 244
245 245 branch = c.commit.branch if (
246 246 c.commit.branch and '/' not in c.commit.branch) else None
247 247 c.branch_or_raw_id = branch or c.commit.raw_id
248 248 c.branch_name = c.commit.branch or h.short_id(c.commit.raw_id)
249 249
250 250 author = c.file_last_commit.author
251 251 c.authors = [(h.email(author),
252 252 h.person(author, 'username_or_name_or_email'))]
253 253 else:
254 254 c.file_source_page = 'false'
255 255 c.authors = []
256 256 c.file_tree = self._get_tree_at_commit(
257 257 repo_name, c.commit.raw_id, f_path)
258 258
259 259 except RepositoryError as e:
260 260 h.flash(safe_str(e), category='error')
261 261 raise HTTPNotFound()
262 262
263 263 if request.environ.get('HTTP_X_PJAX'):
264 264 return render('files/files_pjax.mako')
265 265
266 266 return render('files/files.mako')
267 267
268 268 @LoginRequired()
269 269 @HasRepoPermissionAnyDecorator(
270 270 'repository.read', 'repository.write', 'repository.admin')
271 271 def annotate_previous(self, repo_name, revision, f_path):
272 272
273 273 commit_id = revision
274 274 commit = self.__get_commit_or_redirect(commit_id, repo_name)
275 275 prev_commit_id = commit.raw_id
276 276
277 277 f_path = f_path
278 278 is_file = False
279 279 try:
280 280 _file = commit.get_node(f_path)
281 281 is_file = _file.is_file()
282 282 except (NodeDoesNotExistError, CommitDoesNotExistError, VCSError):
283 283 pass
284 284
285 285 if is_file:
286 286 history = commit.get_file_history(f_path)
287 287 prev_commit_id = history[1].raw_id \
288 288 if len(history) > 1 else prev_commit_id
289 289
290 290 return redirect(h.url(
291 291 'files_annotate_home', repo_name=repo_name,
292 292 revision=prev_commit_id, f_path=f_path))
293 293
294 294 @LoginRequired()
295 295 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
296 296 'repository.admin')
297 297 @jsonify
298 298 def history(self, repo_name, revision, f_path):
299 299 commit = self.__get_commit_or_redirect(revision, repo_name)
300 300 f_path = f_path
301 301 _file = commit.get_node(f_path)
302 302 if _file.is_file():
303 303 file_history, _hist = self._get_node_history(commit, f_path)
304 304
305 305 res = []
306 306 for obj in file_history:
307 307 res.append({
308 308 'text': obj[1],
309 309 'children': [{'id': o[0], 'text': o[1]} for o in obj[0]]
310 310 })
311 311
312 312 data = {
313 313 'more': False,
314 314 'results': res
315 315 }
316 316 return data
317 317
318 318 @LoginRequired()
319 319 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
320 320 'repository.admin')
321 321 def authors(self, repo_name, revision, f_path):
322 322 commit = self.__get_commit_or_redirect(revision, repo_name)
323 323 file_node = commit.get_node(f_path)
324 324 if file_node.is_file():
325 325 c.file_last_commit = file_node.last_commit
326 326 if request.GET.get('annotate') == '1':
327 327 # use _hist from annotation if annotation mode is on
328 328 commit_ids = set(x[1] for x in file_node.annotate)
329 329 _hist = (
330 330 c.rhodecode_repo.get_commit(commit_id)
331 331 for commit_id in commit_ids)
332 332 else:
333 333 _f_history, _hist = self._get_node_history(commit, f_path)
334 334 c.file_author = False
335 335 c.authors = []
336 336 for author in set(commit.author for commit in _hist):
337 337 c.authors.append((
338 338 h.email(author),
339 339 h.person(author, 'username_or_name_or_email')))
340 340 return render('files/file_authors_box.mako')
341 341
342 342 @LoginRequired()
343 343 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
344 344 'repository.admin')
345 345 def rawfile(self, repo_name, revision, f_path):
346 346 """
347 347 Action for download as raw
348 348 """
349 349 commit = self.__get_commit_or_redirect(revision, repo_name)
350 350 file_node = self.__get_filenode_or_redirect(repo_name, commit, f_path)
351 351
352 352 if request.GET.get('lf'):
353 353 # only if lf get flag is passed, we download this file
354 354 # as LFS/Largefile
355 355 lf_node = file_node.get_largefile_node()
356 356 if lf_node:
357 357 # overwrite our pointer with the REAL large-file
358 358 file_node = lf_node
359 359
360 360 response.content_disposition = 'attachment; filename=%s' % \
361 361 safe_str(f_path.split(Repository.NAME_SEP)[-1])
362 362
363 363 response.content_type = file_node.mimetype
364 364 charset = self._get_default_encoding()
365 365 if charset:
366 366 response.charset = charset
367 367
368 368 return file_node.content
369 369
370 370 @LoginRequired()
371 371 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
372 372 'repository.admin')
373 373 def raw(self, repo_name, revision, f_path):
374 374 """
375 375 Action for show as raw, some mimetypes are "rendered",
376 376 those include images, icons.
377 377 """
378 378 commit = self.__get_commit_or_redirect(revision, repo_name)
379 379 file_node = self.__get_filenode_or_redirect(repo_name, commit, f_path)
380 380
381 381 raw_mimetype_mapping = {
382 382 # map original mimetype to a mimetype used for "show as raw"
383 383 # you can also provide a content-disposition to override the
384 384 # default "attachment" disposition.
385 385 # orig_type: (new_type, new_dispo)
386 386
387 387 # show images inline:
388 388 # Do not re-add SVG: it is unsafe and permits XSS attacks. One can
389 389 # for example render an SVG with javascript inside or even render
390 390 # HTML.
391 391 'image/x-icon': ('image/x-icon', 'inline'),
392 392 'image/png': ('image/png', 'inline'),
393 393 'image/gif': ('image/gif', 'inline'),
394 394 'image/jpeg': ('image/jpeg', 'inline'),
395 395 'application/pdf': ('application/pdf', 'inline'),
396 396 }
397 397
398 398 mimetype = file_node.mimetype
399 399 try:
400 400 mimetype, dispo = raw_mimetype_mapping[mimetype]
401 401 except KeyError:
402 402 # we don't know anything special about this, handle it safely
403 403 if file_node.is_binary:
404 404 # do same as download raw for binary files
405 405 mimetype, dispo = 'application/octet-stream', 'attachment'
406 406 else:
407 407 # do not just use the original mimetype, but force text/plain,
408 408 # otherwise it would serve text/html and that might be unsafe.
409 409 # Note: underlying vcs library fakes text/plain mimetype if the
410 410 # mimetype can not be determined and it thinks it is not
411 411 # binary.This might lead to erroneous text display in some
412 412 # cases, but helps in other cases, like with text files
413 413 # without extension.
414 414 mimetype, dispo = 'text/plain', 'inline'
415 415
416 416 if dispo == 'attachment':
417 417 dispo = 'attachment; filename=%s' % safe_str(
418 418 f_path.split(os.sep)[-1])
419 419
420 420 response.content_disposition = dispo
421 421 response.content_type = mimetype
422 422 charset = self._get_default_encoding()
423 423 if charset:
424 424 response.charset = charset
425 425 return file_node.content
426 426
427 427 @CSRFRequired()
428 428 @LoginRequired()
429 429 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
430 430 def delete(self, repo_name, revision, f_path):
431 431 commit_id = revision
432 432
433 433 repo = c.rhodecode_db_repo
434 434 if repo.enable_locking and repo.locked[0]:
435 435 h.flash(_('This repository has been locked by %s on %s')
436 436 % (h.person_by_id(repo.locked[0]),
437 437 h.format_date(h.time_to_datetime(repo.locked[1]))),
438 438 'warning')
439 439 return redirect(h.url('files_home',
440 440 repo_name=repo_name, revision='tip'))
441 441
442 442 if not self._is_valid_head(commit_id, repo.scm_instance()):
443 443 h.flash(_('You can only delete files with revision '
444 444 'being a valid branch '), category='warning')
445 445 return redirect(h.url('files_home',
446 446 repo_name=repo_name, revision='tip',
447 447 f_path=f_path))
448 448
449 449 c.commit = self.__get_commit_or_redirect(commit_id, repo_name)
450 450 c.file = self.__get_filenode_or_redirect(repo_name, c.commit, f_path)
451 451
452 452 c.default_message = _(
453 453 'Deleted file %s via RhodeCode Enterprise') % (f_path)
454 454 c.f_path = f_path
455 455 node_path = f_path
456 456 author = c.rhodecode_user.full_contact
457 457 message = request.POST.get('message') or c.default_message
458 458 try:
459 459 nodes = {
460 460 node_path: {
461 461 'content': ''
462 462 }
463 463 }
464 464 self.scm_model.delete_nodes(
465 465 user=c.rhodecode_user.user_id, repo=c.rhodecode_db_repo,
466 466 message=message,
467 467 nodes=nodes,
468 468 parent_commit=c.commit,
469 469 author=author,
470 470 )
471 471
472 472 h.flash(_('Successfully deleted file %s') % f_path,
473 473 category='success')
474 474 except Exception:
475 475 msg = _('Error occurred during commit')
476 476 log.exception(msg)
477 477 h.flash(msg, category='error')
478 478 return redirect(url('changeset_home',
479 479 repo_name=c.repo_name, revision='tip'))
480 480
481 481 @LoginRequired()
482 482 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
483 483 def delete_home(self, repo_name, revision, f_path):
484 484 commit_id = revision
485 485
486 486 repo = c.rhodecode_db_repo
487 487 if repo.enable_locking and repo.locked[0]:
488 488 h.flash(_('This repository has been locked by %s on %s')
489 489 % (h.person_by_id(repo.locked[0]),
490 490 h.format_date(h.time_to_datetime(repo.locked[1]))),
491 491 'warning')
492 492 return redirect(h.url('files_home',
493 493 repo_name=repo_name, revision='tip'))
494 494
495 495 if not self._is_valid_head(commit_id, repo.scm_instance()):
496 496 h.flash(_('You can only delete files with revision '
497 497 'being a valid branch '), category='warning')
498 498 return redirect(h.url('files_home',
499 499 repo_name=repo_name, revision='tip',
500 500 f_path=f_path))
501 501
502 502 c.commit = self.__get_commit_or_redirect(commit_id, repo_name)
503 503 c.file = self.__get_filenode_or_redirect(repo_name, c.commit, f_path)
504 504
505 505 c.default_message = _(
506 506 'Deleted file %s via RhodeCode Enterprise') % (f_path)
507 507 c.f_path = f_path
508 508
509 509 return render('files/files_delete.mako')
510 510
511 511 @CSRFRequired()
512 512 @LoginRequired()
513 513 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
514 514 def edit(self, repo_name, revision, f_path):
515 515 commit_id = revision
516 516
517 517 repo = c.rhodecode_db_repo
518 518 if repo.enable_locking and repo.locked[0]:
519 519 h.flash(_('This repository has been locked by %s on %s')
520 520 % (h.person_by_id(repo.locked[0]),
521 521 h.format_date(h.time_to_datetime(repo.locked[1]))),
522 522 'warning')
523 523 return redirect(h.url('files_home',
524 524 repo_name=repo_name, revision='tip'))
525 525
526 526 if not self._is_valid_head(commit_id, repo.scm_instance()):
527 527 h.flash(_('You can only edit files with revision '
528 528 'being a valid branch '), category='warning')
529 529 return redirect(h.url('files_home',
530 530 repo_name=repo_name, revision='tip',
531 531 f_path=f_path))
532 532
533 533 c.commit = self.__get_commit_or_redirect(commit_id, repo_name)
534 534 c.file = self.__get_filenode_or_redirect(repo_name, c.commit, f_path)
535 535
536 536 if c.file.is_binary:
537 537 return redirect(url('files_home', repo_name=c.repo_name,
538 538 revision=c.commit.raw_id, f_path=f_path))
539 539 c.default_message = _(
540 540 'Edited file %s via RhodeCode Enterprise') % (f_path)
541 541 c.f_path = f_path
542 542 old_content = c.file.content
543 543 sl = old_content.splitlines(1)
544 544 first_line = sl[0] if sl else ''
545 545
546 546 # modes: 0 - Unix, 1 - Mac, 2 - DOS
547 547 mode = detect_mode(first_line, 0)
548 548 content = convert_line_endings(request.POST.get('content', ''), mode)
549 549
550 550 message = request.POST.get('message') or c.default_message
551 551 org_f_path = c.file.unicode_path
552 552 filename = request.POST['filename']
553 553 org_filename = c.file.name
554 554
555 555 if content == old_content and filename == org_filename:
556 556 h.flash(_('No changes'), category='warning')
557 557 return redirect(url('changeset_home', repo_name=c.repo_name,
558 558 revision='tip'))
559 559 try:
560 560 mapping = {
561 561 org_f_path: {
562 562 'org_filename': org_f_path,
563 563 'filename': os.path.join(c.file.dir_path, filename),
564 564 'content': content,
565 565 'lexer': '',
566 566 'op': 'mod',
567 567 }
568 568 }
569 569
570 570 ScmModel().update_nodes(
571 571 user=c.rhodecode_user.user_id,
572 572 repo=c.rhodecode_db_repo,
573 573 message=message,
574 574 nodes=mapping,
575 575 parent_commit=c.commit,
576 576 )
577 577
578 578 h.flash(_('Successfully committed to %s') % f_path,
579 579 category='success')
580 580 except Exception:
581 581 msg = _('Error occurred during commit')
582 582 log.exception(msg)
583 583 h.flash(msg, category='error')
584 584 return redirect(url('changeset_home',
585 585 repo_name=c.repo_name, revision='tip'))
586 586
587 587 @LoginRequired()
588 588 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
589 589 def edit_home(self, repo_name, revision, f_path):
590 590 commit_id = revision
591 591
592 592 repo = c.rhodecode_db_repo
593 593 if repo.enable_locking and repo.locked[0]:
594 594 h.flash(_('This repository has been locked by %s on %s')
595 595 % (h.person_by_id(repo.locked[0]),
596 596 h.format_date(h.time_to_datetime(repo.locked[1]))),
597 597 'warning')
598 598 return redirect(h.url('files_home',
599 599 repo_name=repo_name, revision='tip'))
600 600
601 601 if not self._is_valid_head(commit_id, repo.scm_instance()):
602 602 h.flash(_('You can only edit files with revision '
603 603 'being a valid branch '), category='warning')
604 604 return redirect(h.url('files_home',
605 605 repo_name=repo_name, revision='tip',
606 606 f_path=f_path))
607 607
608 608 c.commit = self.__get_commit_or_redirect(commit_id, repo_name)
609 609 c.file = self.__get_filenode_or_redirect(repo_name, c.commit, f_path)
610 610
611 611 if c.file.is_binary:
612 612 return redirect(url('files_home', repo_name=c.repo_name,
613 613 revision=c.commit.raw_id, f_path=f_path))
614 614 c.default_message = _(
615 615 'Edited file %s via RhodeCode Enterprise') % (f_path)
616 616 c.f_path = f_path
617 617
618 618 return render('files/files_edit.mako')
619 619
620 620 def _is_valid_head(self, commit_id, repo):
621 621 # check if commit is a branch identifier- basically we cannot
622 622 # create multiple heads via file editing
623 623 valid_heads = repo.branches.keys() + repo.branches.values()
624 624
625 625 if h.is_svn(repo) and not repo.is_empty():
626 626 # Note: Subversion only has one head, we add it here in case there
627 627 # is no branch matched.
628 628 valid_heads.append(repo.get_commit(commit_idx=-1).raw_id)
629 629
630 630 # check if commit is a branch name or branch hash
631 631 return commit_id in valid_heads
632 632
633 633 @CSRFRequired()
634 634 @LoginRequired()
635 635 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
636 636 def add(self, repo_name, revision, f_path):
637 637 repo = Repository.get_by_repo_name(repo_name)
638 638 if repo.enable_locking and repo.locked[0]:
639 639 h.flash(_('This repository has been locked by %s on %s')
640 640 % (h.person_by_id(repo.locked[0]),
641 641 h.format_date(h.time_to_datetime(repo.locked[1]))),
642 642 'warning')
643 643 return redirect(h.url('files_home',
644 644 repo_name=repo_name, revision='tip'))
645 645
646 646 r_post = request.POST
647 647
648 648 c.commit = self.__get_commit_or_redirect(
649 649 revision, repo_name, redirect_after=False)
650 650 if c.commit is None:
651 651 c.commit = EmptyCommit(alias=c.rhodecode_repo.alias)
652 652 c.default_message = (_('Added file via RhodeCode Enterprise'))
653 653 c.f_path = f_path
654 654 unix_mode = 0
655 655 content = convert_line_endings(r_post.get('content', ''), unix_mode)
656 656
657 657 message = r_post.get('message') or c.default_message
658 658 filename = r_post.get('filename')
659 659 location = r_post.get('location', '') # dir location
660 660 file_obj = r_post.get('upload_file', None)
661 661
662 662 if file_obj is not None and hasattr(file_obj, 'filename'):
663 filename = r_post.get('filename_upload')
663 664 content = file_obj.file
664 665
665 666 if hasattr(content, 'file'):
666 667 # non posix systems store real file under file attr
667 668 content = content.file
668 669
669 670 # If there's no commit, redirect to repo summary
670 671 if type(c.commit) is EmptyCommit:
671 672 redirect_url = "summary_home"
672 673 else:
673 674 redirect_url = "changeset_home"
674 675
675 676 if not filename:
676 677 h.flash(_('No filename'), category='warning')
677 678 return redirect(url(redirect_url, repo_name=c.repo_name,
678 679 revision='tip'))
679 680
680 681 # extract the location from filename,
681 682 # allows using foo/bar.txt syntax to create subdirectories
682 683 subdir_loc = filename.rsplit('/', 1)
683 684 if len(subdir_loc) == 2:
684 685 location = os.path.join(location, subdir_loc[0])
685 686
686 687 # strip all crap out of file, just leave the basename
687 688 filename = os.path.basename(filename)
688 689 node_path = os.path.join(location, filename)
689 690 author = c.rhodecode_user.full_contact
690 691
691 692 try:
692 693 nodes = {
693 694 node_path: {
694 695 'content': content
695 696 }
696 697 }
697 698 self.scm_model.create_nodes(
698 699 user=c.rhodecode_user.user_id,
699 700 repo=c.rhodecode_db_repo,
700 701 message=message,
701 702 nodes=nodes,
702 703 parent_commit=c.commit,
703 704 author=author,
704 705 )
705 706
706 707 h.flash(_('Successfully committed to %s') % node_path,
707 708 category='success')
708 709 except NonRelativePathError as e:
709 710 h.flash(_(
710 711 'The location specified must be a relative path and must not '
711 712 'contain .. in the path'), category='warning')
712 713 return redirect(url('changeset_home', repo_name=c.repo_name,
713 714 revision='tip'))
714 715 except (NodeError, NodeAlreadyExistsError) as e:
715 716 h.flash(_(e), category='error')
716 717 except Exception:
717 718 msg = _('Error occurred during commit')
718 719 log.exception(msg)
719 720 h.flash(msg, category='error')
720 721 return redirect(url('changeset_home',
721 722 repo_name=c.repo_name, revision='tip'))
722 723
723 724 @LoginRequired()
724 725 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
725 726 def add_home(self, repo_name, revision, f_path):
726 727
727 728 repo = Repository.get_by_repo_name(repo_name)
728 729 if repo.enable_locking and repo.locked[0]:
729 730 h.flash(_('This repository has been locked by %s on %s')
730 731 % (h.person_by_id(repo.locked[0]),
731 732 h.format_date(h.time_to_datetime(repo.locked[1]))),
732 733 'warning')
733 734 return redirect(h.url('files_home',
734 735 repo_name=repo_name, revision='tip'))
735 736
736 737 c.commit = self.__get_commit_or_redirect(
737 738 revision, repo_name, redirect_after=False)
738 739 if c.commit is None:
739 740 c.commit = EmptyCommit(alias=c.rhodecode_repo.alias)
740 741 c.default_message = (_('Added file via RhodeCode Enterprise'))
741 742 c.f_path = f_path
742 743
743 744 return render('files/files_add.mako')
744 745
745 746 @LoginRequired()
746 747 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
747 748 'repository.admin')
748 749 def archivefile(self, repo_name, fname):
749 750 fileformat = None
750 751 commit_id = None
751 752 ext = None
752 753 subrepos = request.GET.get('subrepos') == 'true'
753 754
754 755 for a_type, ext_data in settings.ARCHIVE_SPECS.items():
755 756 archive_spec = fname.split(ext_data[1])
756 757 if len(archive_spec) == 2 and archive_spec[1] == '':
757 758 fileformat = a_type or ext_data[1]
758 759 commit_id = archive_spec[0]
759 760 ext = ext_data[1]
760 761
761 762 dbrepo = RepoModel().get_by_repo_name(repo_name)
762 763 if not dbrepo.enable_downloads:
763 764 return _('Downloads disabled')
764 765
765 766 try:
766 767 commit = c.rhodecode_repo.get_commit(commit_id)
767 768 content_type = settings.ARCHIVE_SPECS[fileformat][0]
768 769 except CommitDoesNotExistError:
769 770 return _('Unknown revision %s') % commit_id
770 771 except EmptyRepositoryError:
771 772 return _('Empty repository')
772 773 except KeyError:
773 774 return _('Unknown archive type')
774 775
775 776 # archive cache
776 777 from rhodecode import CONFIG
777 778
778 779 archive_name = '%s-%s%s%s' % (
779 780 safe_str(repo_name.replace('/', '_')),
780 781 '-sub' if subrepos else '',
781 782 safe_str(commit.short_id), ext)
782 783
783 784 use_cached_archive = False
784 785 archive_cache_enabled = CONFIG.get(
785 786 'archive_cache_dir') and not request.GET.get('no_cache')
786 787
787 788 if archive_cache_enabled:
788 789 # check if we it's ok to write
789 790 if not os.path.isdir(CONFIG['archive_cache_dir']):
790 791 os.makedirs(CONFIG['archive_cache_dir'])
791 792 cached_archive_path = os.path.join(
792 793 CONFIG['archive_cache_dir'], archive_name)
793 794 if os.path.isfile(cached_archive_path):
794 795 log.debug('Found cached archive in %s', cached_archive_path)
795 796 fd, archive = None, cached_archive_path
796 797 use_cached_archive = True
797 798 else:
798 799 log.debug('Archive %s is not yet cached', archive_name)
799 800
800 801 if not use_cached_archive:
801 802 # generate new archive
802 803 fd, archive = tempfile.mkstemp()
803 804 log.debug('Creating new temp archive in %s' % (archive,))
804 805 try:
805 806 commit.archive_repo(archive, kind=fileformat, subrepos=subrepos)
806 807 except ImproperArchiveTypeError:
807 808 return _('Unknown archive type')
808 809 if archive_cache_enabled:
809 810 # if we generated the archive and we have cache enabled
810 811 # let's use this for future
811 812 log.debug('Storing new archive in %s' % (cached_archive_path,))
812 813 shutil.move(archive, cached_archive_path)
813 814 archive = cached_archive_path
814 815
815 816 def get_chunked_archive(archive):
816 817 with open(archive, 'rb') as stream:
817 818 while True:
818 819 data = stream.read(16 * 1024)
819 820 if not data:
820 821 if fd: # fd means we used temporary file
821 822 os.close(fd)
822 823 if not archive_cache_enabled:
823 824 log.debug('Destroying temp archive %s', archive)
824 825 os.remove(archive)
825 826 break
826 827 yield data
827 828
828 829 # store download action
829 830 action_logger(user=c.rhodecode_user,
830 831 action='user_downloaded_archive:%s' % archive_name,
831 832 repo=repo_name, ipaddr=self.ip_addr, commit=True)
832 833 response.content_disposition = str(
833 834 'attachment; filename=%s' % archive_name)
834 835 response.content_type = str(content_type)
835 836
836 837 return get_chunked_archive(archive)
837 838
838 839 @LoginRequired()
839 840 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
840 841 'repository.admin')
841 842 def diff(self, repo_name, f_path):
842 843
843 844 c.action = request.GET.get('diff')
844 845 diff1 = request.GET.get('diff1', '')
845 846 diff2 = request.GET.get('diff2', '')
846 847
847 848 path1, diff1 = parse_path_ref(diff1, default_path=f_path)
848 849
849 850 ignore_whitespace = str2bool(request.GET.get('ignorews'))
850 851 line_context = request.GET.get('context', 3)
851 852
852 853 if not any((diff1, diff2)):
853 854 h.flash(
854 855 'Need query parameter "diff1" or "diff2" to generate a diff.',
855 856 category='error')
856 857 raise HTTPBadRequest()
857 858
858 859 if c.action not in ['download', 'raw']:
859 860 # redirect to new view if we render diff
860 861 return redirect(
861 862 url('compare_url', repo_name=repo_name,
862 863 source_ref_type='rev',
863 864 source_ref=diff1,
864 865 target_repo=c.repo_name,
865 866 target_ref_type='rev',
866 867 target_ref=diff2,
867 868 f_path=f_path))
868 869
869 870 try:
870 871 node1 = self._get_file_node(diff1, path1)
871 872 node2 = self._get_file_node(diff2, f_path)
872 873 except (RepositoryError, NodeError):
873 874 log.exception("Exception while trying to get node from repository")
874 875 return redirect(url(
875 876 'files_home', repo_name=c.repo_name, f_path=f_path))
876 877
877 878 if all(isinstance(node.commit, EmptyCommit)
878 879 for node in (node1, node2)):
879 880 raise HTTPNotFound
880 881
881 882 c.commit_1 = node1.commit
882 883 c.commit_2 = node2.commit
883 884
884 885 if c.action == 'download':
885 886 _diff = diffs.get_gitdiff(node1, node2,
886 887 ignore_whitespace=ignore_whitespace,
887 888 context=line_context)
888 889 diff = diffs.DiffProcessor(_diff, format='gitdiff')
889 890
890 891 diff_name = '%s_vs_%s.diff' % (diff1, diff2)
891 892 response.content_type = 'text/plain'
892 893 response.content_disposition = (
893 894 'attachment; filename=%s' % (diff_name,)
894 895 )
895 896 charset = self._get_default_encoding()
896 897 if charset:
897 898 response.charset = charset
898 899 return diff.as_raw()
899 900
900 901 elif c.action == 'raw':
901 902 _diff = diffs.get_gitdiff(node1, node2,
902 903 ignore_whitespace=ignore_whitespace,
903 904 context=line_context)
904 905 diff = diffs.DiffProcessor(_diff, format='gitdiff')
905 906 response.content_type = 'text/plain'
906 907 charset = self._get_default_encoding()
907 908 if charset:
908 909 response.charset = charset
909 910 return diff.as_raw()
910 911
911 912 else:
912 913 return redirect(
913 914 url('compare_url', repo_name=repo_name,
914 915 source_ref_type='rev',
915 916 source_ref=diff1,
916 917 target_repo=c.repo_name,
917 918 target_ref_type='rev',
918 919 target_ref=diff2,
919 920 f_path=f_path))
920 921
921 922 @LoginRequired()
922 923 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
923 924 'repository.admin')
924 925 def diff_2way(self, repo_name, f_path):
925 926 """
926 927 Kept only to make OLD links work
927 928 """
928 929 diff1 = request.GET.get('diff1', '')
929 930 diff2 = request.GET.get('diff2', '')
930 931
931 932 if not any((diff1, diff2)):
932 933 h.flash(
933 934 'Need query parameter "diff1" or "diff2" to generate a diff.',
934 935 category='error')
935 936 raise HTTPBadRequest()
936 937
937 938 return redirect(
938 939 url('compare_url', repo_name=repo_name,
939 940 source_ref_type='rev',
940 941 source_ref=diff1,
941 942 target_repo=c.repo_name,
942 943 target_ref_type='rev',
943 944 target_ref=diff2,
944 945 f_path=f_path,
945 946 diffmode='sideside'))
946 947
947 948 def _get_file_node(self, commit_id, f_path):
948 949 if commit_id not in ['', None, 'None', '0' * 12, '0' * 40]:
949 950 commit = c.rhodecode_repo.get_commit(commit_id=commit_id)
950 951 try:
951 952 node = commit.get_node(f_path)
952 953 if node.is_dir():
953 954 raise NodeError('%s path is a %s not a file'
954 955 % (node, type(node)))
955 956 except NodeDoesNotExistError:
956 957 commit = EmptyCommit(
957 958 commit_id=commit_id,
958 959 idx=commit.idx,
959 960 repo=commit.repository,
960 961 alias=commit.repository.alias,
961 962 message=commit.message,
962 963 author=commit.author,
963 964 date=commit.date)
964 965 node = FileNode(f_path, '', commit=commit)
965 966 else:
966 967 commit = EmptyCommit(
967 968 repo=c.rhodecode_repo,
968 969 alias=c.rhodecode_repo.alias)
969 970 node = FileNode(f_path, '', commit=commit)
970 971 return node
971 972
972 973 def _get_node_history(self, commit, f_path, commits=None):
973 974 """
974 975 get commit history for given node
975 976
976 977 :param commit: commit to calculate history
977 978 :param f_path: path for node to calculate history for
978 979 :param commits: if passed don't calculate history and take
979 980 commits defined in this list
980 981 """
981 982 # calculate history based on tip
982 983 tip = c.rhodecode_repo.get_commit()
983 984 if commits is None:
984 985 pre_load = ["author", "branch"]
985 986 try:
986 987 commits = tip.get_file_history(f_path, pre_load=pre_load)
987 988 except (NodeDoesNotExistError, CommitError):
988 989 # this node is not present at tip!
989 990 commits = commit.get_file_history(f_path, pre_load=pre_load)
990 991
991 992 history = []
992 993 commits_group = ([], _("Changesets"))
993 994 for commit in commits:
994 995 branch = ' (%s)' % commit.branch if commit.branch else ''
995 996 n_desc = 'r%s:%s%s' % (commit.idx, commit.short_id, branch)
996 997 commits_group[0].append((commit.raw_id, n_desc,))
997 998 history.append(commits_group)
998 999
999 1000 symbolic_reference = self._symbolic_reference
1000 1001
1001 1002 if c.rhodecode_repo.alias == 'svn':
1002 1003 adjusted_f_path = self._adjust_file_path_for_svn(
1003 1004 f_path, c.rhodecode_repo)
1004 1005 if adjusted_f_path != f_path:
1005 1006 log.debug(
1006 1007 'Recognized svn tag or branch in file "%s", using svn '
1007 1008 'specific symbolic references', f_path)
1008 1009 f_path = adjusted_f_path
1009 1010 symbolic_reference = self._symbolic_reference_svn
1010 1011
1011 1012 branches = self._create_references(
1012 1013 c.rhodecode_repo.branches, symbolic_reference, f_path)
1013 1014 branches_group = (branches, _("Branches"))
1014 1015
1015 1016 tags = self._create_references(
1016 1017 c.rhodecode_repo.tags, symbolic_reference, f_path)
1017 1018 tags_group = (tags, _("Tags"))
1018 1019
1019 1020 history.append(branches_group)
1020 1021 history.append(tags_group)
1021 1022
1022 1023 return history, commits
1023 1024
1024 1025 def _adjust_file_path_for_svn(self, f_path, repo):
1025 1026 """
1026 1027 Computes the relative path of `f_path`.
1027 1028
1028 1029 This is mainly based on prefix matching of the recognized tags and
1029 1030 branches in the underlying repository.
1030 1031 """
1031 1032 tags_and_branches = itertools.chain(
1032 1033 repo.branches.iterkeys(),
1033 1034 repo.tags.iterkeys())
1034 1035 tags_and_branches = sorted(tags_and_branches, key=len, reverse=True)
1035 1036
1036 1037 for name in tags_and_branches:
1037 1038 if f_path.startswith(name + '/'):
1038 1039 f_path = vcspath.relpath(f_path, name)
1039 1040 break
1040 1041 return f_path
1041 1042
1042 1043 def _create_references(
1043 1044 self, branches_or_tags, symbolic_reference, f_path):
1044 1045 items = []
1045 1046 for name, commit_id in branches_or_tags.items():
1046 1047 sym_ref = symbolic_reference(commit_id, name, f_path)
1047 1048 items.append((sym_ref, name))
1048 1049 return items
1049 1050
1050 1051 def _symbolic_reference(self, commit_id, name, f_path):
1051 1052 return commit_id
1052 1053
1053 1054 def _symbolic_reference_svn(self, commit_id, name, f_path):
1054 1055 new_f_path = vcspath.join(name, f_path)
1055 1056 return u'%s@%s' % (new_f_path, commit_id)
1056 1057
1057 1058 @LoginRequired()
1058 1059 @XHRRequired()
1059 1060 @HasRepoPermissionAnyDecorator(
1060 1061 'repository.read', 'repository.write', 'repository.admin')
1061 1062 @jsonify
1062 1063 def nodelist(self, repo_name, revision, f_path):
1063 1064 commit = self.__get_commit_or_redirect(revision, repo_name)
1064 1065
1065 1066 metadata = self._get_nodelist_at_commit(
1066 1067 repo_name, commit.raw_id, f_path)
1067 1068 return {'nodes': metadata}
1068 1069
1069 1070 @LoginRequired()
1070 1071 @XHRRequired()
1071 1072 @HasRepoPermissionAnyDecorator(
1072 1073 'repository.read', 'repository.write', 'repository.admin')
1073 1074 def nodetree_full(self, repo_name, commit_id, f_path):
1074 1075 """
1075 1076 Returns rendered html of file tree that contains commit date,
1076 1077 author, revision for the specified combination of
1077 1078 repo, commit_id and file path
1078 1079
1079 1080 :param repo_name: name of the repository
1080 1081 :param commit_id: commit_id of file tree
1081 1082 :param f_path: file path of the requested directory
1082 1083 """
1083 1084
1084 1085 commit = self.__get_commit_or_redirect(commit_id, repo_name)
1085 1086 try:
1086 1087 dir_node = commit.get_node(f_path)
1087 1088 except RepositoryError as e:
1088 1089 return 'error {}'.format(safe_str(e))
1089 1090
1090 1091 if dir_node.is_file():
1091 1092 return ''
1092 1093
1093 1094 c.file = dir_node
1094 1095 c.commit = commit
1095 1096
1096 1097 # using force=True here, make a little trick. We flush the cache and
1097 1098 # compute it using the same key as without full_load, so the fully
1098 1099 # loaded cached tree is now returned instead of partial
1099 1100 return self._get_tree_at_commit(
1100 1101 repo_name, commit.raw_id, dir_node.path, full_load=True,
1101 1102 force=True)
@@ -1,236 +1,236 b''
1 1 <%inherit file="/base/base.mako"/>
2 2
3 3 <%def name="title()">
4 4 ${_('%s Files Add') % c.repo_name}
5 5 %if c.rhodecode_name:
6 6 &middot; ${h.branding(c.rhodecode_name)}
7 7 %endif
8 8 </%def>
9 9
10 10 <%def name="menu_bar_nav()">
11 11 ${self.menu_items(active='repositories')}
12 12 </%def>
13 13
14 14 <%def name="breadcrumbs_links()">
15 15 ${_('Add new file')} @ ${h.show_id(c.commit)}
16 16 </%def>
17 17
18 18 <%def name="menu_bar_subnav()">
19 19 ${self.repo_menu(active='files')}
20 20 </%def>
21 21
22 22 <%def name="main()">
23 23 <div class="box">
24 24 <div class="title">
25 25 ${self.repo_page_title(c.rhodecode_db_repo)}
26 26 </div>
27 27 <div class="edit-file-title">
28 28 ${self.breadcrumbs()}
29 29 </div>
30 30 ${h.secure_form(h.url.current(),method='post',id='eform',enctype="multipart/form-data", class_="form-horizontal")}
31 31 <div class="edit-file-fieldset">
32 32 <div class="fieldset">
33 33 <div id="destination-label" class="left-label">
34 34 ${_('Path')}:
35 35 </div>
36 36 <div class="right-content">
37 37 <div id="specify-custom-path-container">
38 38 <span id="path-breadcrumbs">${h.files_breadcrumbs(c.repo_name,c.commit.raw_id,c.f_path)}</span>
39 39 <a class="custom-path-link" id="specify-custom-path" href="#">${_('Specify Custom Path')}</a>
40 40 </div>
41 41 <div id="remove-custom-path-container" style="display: none;">
42 42 ${c.repo_name}/
43 43 <input type="input-small" value="${c.f_path}" size="46" name="location" id="location">
44 44 <a class="custom-path-link" id="remove-custom-path" href="#">${_('Remove Custom Path')}</a>
45 45 </div>
46 46 </div>
47 47 </div>
48 48 <div id="filename_container" class="fieldset">
49 49 <div class="filename-label left-label">
50 50 ${_('Filename')}:
51 51 </div>
52 52 <div class="right-content">
53 53 <input class="input-small" type="text" value="" size="46" name="filename" id="filename">
54 54 <p>${_('or')} <a id="upload_file_enable" href="#">${_('Upload File')}</a></p>
55 55 </div>
56 56 </div>
57 57 <div id="upload_file_container" class="fieldset" style="display: none;">
58 58 <div class="filename-label left-label">
59 59 ${_('Filename')}:
60 60 </div>
61 61 <div class="right-content">
62 <input class="input-small" type="text" value="" size="46" name="filename" id="filename_upload" placeholder="${_('No file selected')}">
62 <input class="input-small" type="text" value="" size="46" name="filename_upload" id="filename_upload" placeholder="${_('No file selected')}">
63 63 </div>
64 64 <div class="filename-label left-label file-upload-label">
65 65 ${_('Upload file')}:
66 66 </div>
67 67 <div class="right-content file-upload-input">
68 68 <label for="upload_file" class="btn btn-default">Browse</label>
69 69
70 70 <input type="file" name="upload_file" id="upload_file">
71 71 <p>${_('or')} <a id="file_enable" href="#">${_('Create New File')}</a></p>
72 72 </div>
73 73 </div>
74 74 </div>
75 75 <div class="table">
76 76 <div id="files_data">
77 77 <div id="codeblock" class="codeblock">
78 78 <div class="code-header form" id="set_mode_header">
79 79 <div class="fields">
80 80 ${h.dropdownmenu('set_mode','plain',[('plain',_('plain'))],enable_filter=True)}
81 81 <label for="line_wrap">${_('line wraps')}</label>
82 82 ${h.dropdownmenu('line_wrap', 'off', [('on', _('on')), ('off', _('off')),])}
83 83
84 84 <div id="render_preview" class="btn btn-small preview hidden" >${_('Preview')}</div>
85 85 </div>
86 86 </div>
87 87 <div id="editor_container">
88 88 <pre id="editor_pre"></pre>
89 89 <textarea id="editor" name="content" ></textarea>
90 90 <div id="editor_preview"></div>
91 91 </div>
92 92 </div>
93 93 </div>
94 94 </div>
95 95
96 96 <div class="edit-file-fieldset">
97 97 <div class="fieldset">
98 98 <div id="commit-message-label" class="commit-message-label left-label">
99 99 ${_('Commit Message')}:
100 100 </div>
101 101 <div class="right-content">
102 102 <div class="message">
103 103 <textarea id="commit" name="message" placeholder="${c.default_message}"></textarea>
104 104 </div>
105 105 </div>
106 106 </div>
107 107 <div class="pull-right">
108 108 ${h.reset('reset',_('Cancel'),class_="btn btn-small")}
109 109 ${h.submit('commit_btn',_('Commit changes'),class_="btn btn-small btn-success")}
110 110 </div>
111 111 </div>
112 112 ${h.end_form()}
113 113 </div>
114 114 <script type="text/javascript">
115 115
116 116 $('#commit_btn').on('click', function() {
117 117 var button = $(this);
118 118 if (button.hasClass('clicked')) {
119 119 button.attr('disabled', true);
120 120 } else {
121 121 button.addClass('clicked');
122 122 }
123 123 });
124 124
125 125 $('#specify-custom-path').on('click', function(e){
126 126 e.preventDefault();
127 127 $('#specify-custom-path-container').hide();
128 128 $('#remove-custom-path-container').show();
129 129 $('#destination-label').css('margin-top', '13px');
130 130 });
131 131
132 132 $('#remove-custom-path').on('click', function(e){
133 133 e.preventDefault();
134 134 $('#specify-custom-path-container').show();
135 135 $('#remove-custom-path-container').hide();
136 136 $('#location').val('${c.f_path}');
137 137 $('#destination-label').css('margin-top', '0');
138 138 });
139 139
140 140 var hide_upload = function(){
141 141 $('#files_data').show();
142 142 $('#upload_file_container').hide();
143 143 $('#filename_container').show();
144 144 };
145 145
146 146 $('#file_enable').on('click', function(e){
147 147 e.preventDefault();
148 148 hide_upload();
149 149 });
150 150
151 151 $('#upload_file_enable').on('click', function(e){
152 152 e.preventDefault();
153 153 $('#files_data').hide();
154 154 $('#upload_file_container').show();
155 155 $('#filename_container').hide();
156 156 if (detectIE() && detectIE() <= 9) {
157 157 $('#upload_file_container .file-upload-input label').hide();
158 158 $('#upload_file_container .file-upload-input span').hide();
159 159 $('#upload_file_container .file-upload-input input').show();
160 160 }
161 161 });
162 162
163 163 $('#upload_file').on('change', function() {
164 164 if (this.files && this.files[0]) {
165 165 $('#filename_upload').val(this.files[0].name);
166 166 }
167 167 });
168 168
169 169 hide_upload();
170 170
171 171 var renderer = "";
172 172 var reset_url = "${h.url('files_home',repo_name=c.repo_name,revision=c.commit.raw_id,f_path=c.f_path)}";
173 173 var myCodeMirror = initCodeMirror('editor', reset_url, false);
174 174
175 175 var modes_select = $('#set_mode');
176 176 fillCodeMirrorOptions(modes_select);
177 177
178 178 var filename_selector = '#filename';
179 179 var callback = function(filename, mimetype, mode){
180 180 CodeMirrorPreviewEnable(mode);
181 181 };
182 182 // on change of select field set mode
183 183 setCodeMirrorModeFromSelect(
184 184 modes_select, filename_selector, myCodeMirror, callback);
185 185
186 186 // on entering the new filename set mode, from given extension
187 187 setCodeMirrorModeFromInput(
188 188 modes_select, filename_selector, myCodeMirror, callback);
189 189
190 190 // if the file is renderable set line wraps automatically
191 191 if (renderer !== ""){
192 192 var line_wrap = 'on';
193 193 $($('#line_wrap option[value="'+line_wrap+'"]')[0]).attr("selected", "selected");
194 194 setCodeMirrorLineWrap(myCodeMirror, true);
195 195 }
196 196
197 197 // on select line wraps change the editor
198 198 $('#line_wrap').on('change', function(e){
199 199 var selected = e.currentTarget;
200 200 var line_wraps = {'on': true, 'off': false}[selected.value];
201 201 setCodeMirrorLineWrap(myCodeMirror, line_wraps)
202 202 });
203 203
204 204 // render preview/edit button
205 205 $('#render_preview').on('click', function(e){
206 206 if($(this).hasClass('preview')){
207 207 $(this).removeClass('preview');
208 208 $(this).html("${_('Edit')}");
209 209 $('#editor_preview').show();
210 210 $(myCodeMirror.getWrapperElement()).hide();
211 211
212 212 var possible_renderer = {
213 213 'rst':'rst',
214 214 'markdown':'markdown',
215 215 'gfm': 'markdown'}[myCodeMirror.getMode().name];
216 216 var _text = myCodeMirror.getValue();
217 217 var _renderer = possible_renderer || DEFAULT_RENDERER;
218 218 var post_data = {'text': _text, 'renderer': _renderer, 'csrf_token': CSRF_TOKEN};
219 219 $('#editor_preview').html(_gettext('Loading ...'));
220 220 var url = pyroutes.url('changeset_comment_preview', {'repo_name': '${c.repo_name}'});
221 221
222 222 ajaxPOST(url, post_data, function(o){
223 223 $('#editor_preview').html(o);
224 224 })
225 225 }
226 226 else{
227 227 $(this).addClass('preview');
228 228 $(this).html("${_('Preview')}");
229 229 $('#editor_preview').hide();
230 230 $(myCodeMirror.getWrapperElement()).show();
231 231 }
232 232 });
233 233 $('#filename').focus();
234 234
235 235 </script>
236 236 </%def>
General Comments 0
You need to be logged in to leave comments. Login now