##// END OF EJS Templates
Added more tests for web based file edit and add
marcink -
r3839:9dec8704 beta
parent child Browse files
Show More
@@ -1,650 +1,650 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.controllers.files
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 Files controller for RhodeCode
7 7
8 8 :created_on: Apr 21, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
14 14 # it under the terms of the GNU General Public License as published by
15 15 # the Free Software Foundation, either version 3 of the License, or
16 16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25 from __future__ import with_statement
26 26 import os
27 27 import logging
28 28 import traceback
29 29 import tempfile
30 30 import shutil
31 31
32 32 from pylons import request, response, tmpl_context as c, url
33 33 from pylons.i18n.translation import _
34 34 from pylons.controllers.util import redirect
35 35 from rhodecode.lib.utils import jsonify
36 36
37 37 from rhodecode.lib import diffs
38 38 from rhodecode.lib import helpers as h
39 39
40 40 from rhodecode.lib.compat import OrderedDict
41 41 from rhodecode.lib.utils2 import convert_line_endings, detect_mode, safe_str,\
42 42 str2bool
43 43 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
44 44 from rhodecode.lib.base import BaseRepoController, render
45 45 from rhodecode.lib.vcs.backends.base import EmptyChangeset
46 46 from rhodecode.lib.vcs.conf import settings
47 47 from rhodecode.lib.vcs.exceptions import RepositoryError, \
48 48 ChangesetDoesNotExistError, EmptyRepositoryError, \
49 49 ImproperArchiveTypeError, VCSError, NodeAlreadyExistsError,\
50 50 NodeDoesNotExistError, ChangesetError, NodeError
51 51 from rhodecode.lib.vcs.nodes import FileNode
52 52
53 53 from rhodecode.model.repo import RepoModel
54 54 from rhodecode.model.scm import ScmModel
55 55 from rhodecode.model.db import Repository
56 56
57 57 from rhodecode.controllers.changeset import anchor_url, _ignorews_url,\
58 58 _context_url, get_line_ctx, get_ignore_ws
59 59 from webob.exc import HTTPNotFound
60 60
61 61
62 62 log = logging.getLogger(__name__)
63 63
64 64
65 65 class FilesController(BaseRepoController):
66 66
67 67 def __before__(self):
68 68 super(FilesController, self).__before__()
69 69 c.cut_off_limit = self.cut_off_limit
70 70
71 71 def __get_cs_or_redirect(self, rev, repo_name, redirect_after=True):
72 72 """
73 73 Safe way to get changeset if error occur it redirects to tip with
74 74 proper message
75 75
76 76 :param rev: revision to fetch
77 77 :param repo_name: repo name to redirect after
78 78 """
79 79
80 80 try:
81 81 return c.rhodecode_repo.get_changeset(rev)
82 82 except EmptyRepositoryError, e:
83 83 if not redirect_after:
84 84 return None
85 85 url_ = url('files_add_home',
86 86 repo_name=c.repo_name,
87 87 revision=0, f_path='')
88 88 add_new = h.link_to(_('Click here to add new file'), url_)
89 89 h.flash(h.literal(_('There are no files yet %s') % add_new),
90 90 category='warning')
91 91 redirect(h.url('summary_home', repo_name=repo_name))
92 92
93 93 except RepositoryError, e: # including ChangesetDoesNotExistError
94 94 h.flash(str(e), category='error')
95 95 raise HTTPNotFound()
96 96
97 97 def __get_filenode_or_redirect(self, repo_name, cs, path):
98 98 """
99 99 Returns file_node, if error occurs or given path is directory,
100 100 it'll redirect to top level path
101 101
102 102 :param repo_name: repo_name
103 103 :param cs: given changeset
104 104 :param path: path to lookup
105 105 """
106 106
107 107 try:
108 108 file_node = cs.get_node(path)
109 109 if file_node.is_dir():
110 110 raise RepositoryError('given path is a directory')
111 111 except RepositoryError, e:
112 112 h.flash(str(e), category='error')
113 113 raise HTTPNotFound()
114 114
115 115 return file_node
116 116
117 117 @LoginRequired()
118 118 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
119 119 'repository.admin')
120 120 def index(self, repo_name, revision, f_path, annotate=False):
121 121 # redirect to given revision from form if given
122 122 post_revision = request.POST.get('at_rev', None)
123 123 if post_revision:
124 124 cs = self.__get_cs_or_redirect(post_revision, repo_name)
125 125
126 126 c.changeset = self.__get_cs_or_redirect(revision, repo_name)
127 127 c.branch = request.GET.get('branch', None)
128 128 c.f_path = f_path
129 129 c.annotate = annotate
130 130 c.changeset = self.__get_cs_or_redirect(revision, repo_name)
131 131 cur_rev = c.changeset.revision
132 132
133 133 # prev link
134 134 try:
135 135 prev_rev = c.rhodecode_repo.get_changeset(cur_rev).prev(c.branch)
136 136 c.url_prev = url('files_home', repo_name=c.repo_name,
137 137 revision=prev_rev.raw_id, f_path=f_path)
138 138 if c.branch:
139 139 c.url_prev += '?branch=%s' % c.branch
140 140 except (ChangesetDoesNotExistError, VCSError):
141 141 c.url_prev = '#'
142 142
143 143 # next link
144 144 try:
145 145 next_rev = c.rhodecode_repo.get_changeset(cur_rev).next(c.branch)
146 146 c.url_next = url('files_home', repo_name=c.repo_name,
147 147 revision=next_rev.raw_id, f_path=f_path)
148 148 if c.branch:
149 149 c.url_next += '?branch=%s' % c.branch
150 150 except (ChangesetDoesNotExistError, VCSError):
151 151 c.url_next = '#'
152 152
153 153 # files or dirs
154 154 try:
155 155 c.file = c.changeset.get_node(f_path)
156 156
157 157 if c.file.is_file():
158 158 c.load_full_history = False
159 159 file_last_cs = c.file.last_changeset
160 160 c.file_changeset = (c.changeset
161 161 if c.changeset.revision < file_last_cs.revision
162 162 else file_last_cs)
163 163 #determine if we're on branch head
164 164 _branches = c.rhodecode_repo.branches
165 165 c.on_branch_head = revision in _branches.keys() + _branches.values()
166 166 _hist = []
167 167 c.file_history = []
168 168 if c.load_full_history:
169 169 c.file_history, _hist = self._get_node_history(c.changeset, f_path)
170 170
171 171 c.authors = []
172 172 for a in set([x.author for x in _hist]):
173 173 c.authors.append((h.email(a), h.person(a)))
174 174 else:
175 175 c.authors = c.file_history = []
176 176 except RepositoryError, e:
177 177 h.flash(str(e), category='error')
178 178 raise HTTPNotFound()
179 179
180 180 if request.environ.get('HTTP_X_PARTIAL_XHR'):
181 181 return render('files/files_ypjax.html')
182 182
183 183 return render('files/files.html')
184 184
185 185 @LoginRequired()
186 186 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
187 187 'repository.admin')
188 188 def history(self, repo_name, revision, f_path, annotate=False):
189 189 if request.environ.get('HTTP_X_PARTIAL_XHR'):
190 190 c.changeset = self.__get_cs_or_redirect(revision, repo_name)
191 191 c.f_path = f_path
192 192 c.annotate = annotate
193 193 c.file = c.changeset.get_node(f_path)
194 194 if c.file.is_file():
195 195 file_last_cs = c.file.last_changeset
196 196 c.file_changeset = (c.changeset
197 197 if c.changeset.revision < file_last_cs.revision
198 198 else file_last_cs)
199 199 c.file_history, _hist = self._get_node_history(c.changeset, f_path)
200 200 c.authors = []
201 201 for a in set([x.author for x in _hist]):
202 202 c.authors.append((h.email(a), h.person(a)))
203 203 return render('files/files_history_box.html')
204 204
205 205 @LoginRequired()
206 206 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
207 207 'repository.admin')
208 208 def rawfile(self, repo_name, revision, f_path):
209 209 cs = self.__get_cs_or_redirect(revision, repo_name)
210 210 file_node = self.__get_filenode_or_redirect(repo_name, cs, f_path)
211 211
212 212 response.content_disposition = 'attachment; filename=%s' % \
213 213 safe_str(f_path.split(Repository.url_sep())[-1])
214 214
215 215 response.content_type = file_node.mimetype
216 216 return file_node.content
217 217
218 218 @LoginRequired()
219 219 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
220 220 'repository.admin')
221 221 def raw(self, repo_name, revision, f_path):
222 222 cs = self.__get_cs_or_redirect(revision, repo_name)
223 223 file_node = self.__get_filenode_or_redirect(repo_name, cs, f_path)
224 224
225 225 raw_mimetype_mapping = {
226 226 # map original mimetype to a mimetype used for "show as raw"
227 227 # you can also provide a content-disposition to override the
228 228 # default "attachment" disposition.
229 229 # orig_type: (new_type, new_dispo)
230 230
231 231 # show images inline:
232 232 'image/x-icon': ('image/x-icon', 'inline'),
233 233 'image/png': ('image/png', 'inline'),
234 234 'image/gif': ('image/gif', 'inline'),
235 235 'image/jpeg': ('image/jpeg', 'inline'),
236 236 'image/svg+xml': ('image/svg+xml', 'inline'),
237 237 }
238 238
239 239 mimetype = file_node.mimetype
240 240 try:
241 241 mimetype, dispo = raw_mimetype_mapping[mimetype]
242 242 except KeyError:
243 243 # we don't know anything special about this, handle it safely
244 244 if file_node.is_binary:
245 245 # do same as download raw for binary files
246 246 mimetype, dispo = 'application/octet-stream', 'attachment'
247 247 else:
248 248 # do not just use the original mimetype, but force text/plain,
249 249 # otherwise it would serve text/html and that might be unsafe.
250 250 # Note: underlying vcs library fakes text/plain mimetype if the
251 251 # mimetype can not be determined and it thinks it is not
252 252 # binary.This might lead to erroneous text display in some
253 253 # cases, but helps in other cases, like with text files
254 254 # without extension.
255 255 mimetype, dispo = 'text/plain', 'inline'
256 256
257 257 if dispo == 'attachment':
258 258 dispo = 'attachment; filename=%s' % \
259 259 safe_str(f_path.split(os.sep)[-1])
260 260
261 261 response.content_disposition = dispo
262 262 response.content_type = mimetype
263 263 return file_node.content
264 264
265 265 @LoginRequired()
266 266 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
267 267 def edit(self, repo_name, revision, f_path):
268 268 repo = c.rhodecode_db_repo
269 269 if repo.enable_locking and repo.locked[0]:
270 270 h.flash(_('This repository is has been locked by %s on %s')
271 271 % (h.person_by_id(repo.locked[0]),
272 272 h.fmt_date(h.time_to_datetime(repo.locked[1]))),
273 273 'warning')
274 274 return redirect(h.url('files_home',
275 275 repo_name=repo_name, revision='tip'))
276 276
277 277 # check if revision is a branch identifier- basically we cannot
278 278 # create multiple heads via file editing
279 279 _branches = repo.scm_instance.branches
280 280 # check if revision is a branch name or branch hash
281 281 if revision not in _branches.keys() + _branches.values():
282 282 h.flash(_('You can only edit files with revision '
283 283 'being a valid branch '), category='warning')
284 284 return redirect(h.url('files_home',
285 285 repo_name=repo_name, revision='tip',
286 286 f_path=f_path))
287 287
288 288 r_post = request.POST
289 289
290 290 c.cs = self.__get_cs_or_redirect(revision, repo_name)
291 291 c.file = self.__get_filenode_or_redirect(repo_name, c.cs, f_path)
292 292
293 293 if c.file.is_binary:
294 294 return redirect(url('files_home', repo_name=c.repo_name,
295 295 revision=c.cs.raw_id, f_path=f_path))
296 296 c.default_message = _('Edited file %s via RhodeCode') % (f_path)
297 297 c.f_path = f_path
298 298
299 299 if r_post:
300 300
301 301 old_content = c.file.content
302 302 sl = old_content.splitlines(1)
303 303 first_line = sl[0] if sl else ''
304 304 # modes: 0 - Unix, 1 - Mac, 2 - DOS
305 305 mode = detect_mode(first_line, 0)
306 content = convert_line_endings(r_post.get('content'), mode)
306 content = convert_line_endings(r_post.get('content', ''), mode)
307 307
308 308 message = r_post.get('message') or c.default_message
309 309 author = self.rhodecode_user.full_contact
310 310
311 311 if content == old_content:
312 312 h.flash(_('No changes'), category='warning')
313 313 return redirect(url('changeset_home', repo_name=c.repo_name,
314 314 revision='tip'))
315 315 try:
316 316 self.scm_model.commit_change(repo=c.rhodecode_repo,
317 317 repo_name=repo_name, cs=c.cs,
318 318 user=self.rhodecode_user.user_id,
319 319 author=author, message=message,
320 320 content=content, f_path=f_path)
321 321 h.flash(_('Successfully committed to %s') % f_path,
322 322 category='success')
323 323
324 324 except Exception:
325 325 log.error(traceback.format_exc())
326 326 h.flash(_('Error occurred during commit'), category='error')
327 327 return redirect(url('changeset_home',
328 328 repo_name=c.repo_name, revision='tip'))
329 329
330 330 return render('files/files_edit.html')
331 331
332 332 @LoginRequired()
333 333 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
334 334 def add(self, repo_name, revision, f_path):
335 335
336 336 repo = Repository.get_by_repo_name(repo_name)
337 337 if repo.enable_locking and repo.locked[0]:
338 338 h.flash(_('This repository is has been locked by %s on %s')
339 339 % (h.person_by_id(repo.locked[0]),
340 340 h.fmt_date(h.time_to_datetime(repo.locked[1]))),
341 341 'warning')
342 342 return redirect(h.url('files_home',
343 343 repo_name=repo_name, revision='tip'))
344 344
345 345 r_post = request.POST
346 346 c.cs = self.__get_cs_or_redirect(revision, repo_name,
347 347 redirect_after=False)
348 348 if c.cs is None:
349 349 c.cs = EmptyChangeset(alias=c.rhodecode_repo.alias)
350 350 c.default_message = (_('Added file via RhodeCode'))
351 351 c.f_path = f_path
352 352
353 353 if r_post:
354 354 unix_mode = 0
355 content = convert_line_endings(r_post.get('content'), unix_mode)
355 content = convert_line_endings(r_post.get('content', ''), unix_mode)
356 356
357 357 message = r_post.get('message') or c.default_message
358 358 filename = r_post.get('filename')
359 location = r_post.get('location')
359 location = r_post.get('location', '')
360 360 file_obj = r_post.get('upload_file', None)
361 361
362 362 if file_obj is not None and hasattr(file_obj, 'filename'):
363 363 filename = file_obj.filename
364 364 content = file_obj.file
365 365
366 366 if not content:
367 367 h.flash(_('No content'), category='warning')
368 368 return redirect(url('changeset_home', repo_name=c.repo_name,
369 369 revision='tip'))
370 370 if not filename:
371 371 h.flash(_('No filename'), category='warning')
372 372 return redirect(url('changeset_home', repo_name=c.repo_name,
373 373 revision='tip'))
374 374 if location.startswith('/') or location.startswith('.') or '../' in location:
375 375 h.flash(_('Location must be relative path and must not '
376 376 'contain .. in path'), category='warning')
377 377 return redirect(url('changeset_home', repo_name=c.repo_name,
378 378 revision='tip'))
379 379 if location:
380 380 location = os.path.normpath(location)
381 381 filename = os.path.basename(filename)
382 382 node_path = os.path.join(location, filename)
383 383 author = self.rhodecode_user.full_contact
384 384
385 385 try:
386 386 self.scm_model.create_node(repo=c.rhodecode_repo,
387 387 repo_name=repo_name, cs=c.cs,
388 388 user=self.rhodecode_user.user_id,
389 389 author=author, message=message,
390 390 content=content, f_path=node_path)
391 391 h.flash(_('Successfully committed to %s') % node_path,
392 392 category='success')
393 393 except (NodeError, NodeAlreadyExistsError), e:
394 394 h.flash(_(e), category='error')
395 395 except Exception:
396 396 log.error(traceback.format_exc())
397 397 h.flash(_('Error occurred during commit'), category='error')
398 398 return redirect(url('changeset_home',
399 399 repo_name=c.repo_name, revision='tip'))
400 400
401 401 return render('files/files_add.html')
402 402
403 403 @LoginRequired()
404 404 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
405 405 'repository.admin')
406 406 def archivefile(self, repo_name, fname):
407 407
408 408 fileformat = None
409 409 revision = None
410 410 ext = None
411 411 subrepos = request.GET.get('subrepos') == 'true'
412 412
413 413 for a_type, ext_data in settings.ARCHIVE_SPECS.items():
414 414 archive_spec = fname.split(ext_data[1])
415 415 if len(archive_spec) == 2 and archive_spec[1] == '':
416 416 fileformat = a_type or ext_data[1]
417 417 revision = archive_spec[0]
418 418 ext = ext_data[1]
419 419
420 420 try:
421 421 dbrepo = RepoModel().get_by_repo_name(repo_name)
422 422 if not dbrepo.enable_downloads:
423 423 return _('Downloads disabled')
424 424
425 425 if c.rhodecode_repo.alias == 'hg':
426 426 # patch and reset hooks section of UI config to not run any
427 427 # hooks on fetching archives with subrepos
428 428 for k, v in c.rhodecode_repo._repo.ui.configitems('hooks'):
429 429 c.rhodecode_repo._repo.ui.setconfig('hooks', k, None)
430 430
431 431 cs = c.rhodecode_repo.get_changeset(revision)
432 432 content_type = settings.ARCHIVE_SPECS[fileformat][0]
433 433 except ChangesetDoesNotExistError:
434 434 return _('Unknown revision %s') % revision
435 435 except EmptyRepositoryError:
436 436 return _('Empty repository')
437 437 except (ImproperArchiveTypeError, KeyError):
438 438 return _('Unknown archive type')
439 439 # archive cache
440 440 from rhodecode import CONFIG
441 441 rev_name = cs.raw_id[:12]
442 442 archive_name = '%s-%s%s' % (safe_str(repo_name.replace('/', '_')),
443 443 safe_str(rev_name), ext)
444 444
445 445 use_cached_archive = False # defines if we use cached version of archive
446 446 archive_cache_enabled = CONFIG.get('archive_cache_dir')
447 447 if not subrepos and archive_cache_enabled:
448 448 #check if we it's ok to write
449 449 if not os.path.isdir(CONFIG['archive_cache_dir']):
450 450 os.makedirs(CONFIG['archive_cache_dir'])
451 451 cached_archive_path = os.path.join(CONFIG['archive_cache_dir'], archive_name)
452 452 if os.path.isfile(cached_archive_path):
453 453 log.debug('Found cached archive in %s' % cached_archive_path)
454 454 fd, archive = None, cached_archive_path
455 455 use_cached_archive = True
456 456 else:
457 457 log.debug('Archive %s is not yet cached' % (archive_name))
458 458
459 459 if not use_cached_archive:
460 460 #generate new archive
461 461 try:
462 462 fd, archive = tempfile.mkstemp()
463 463 t = open(archive, 'wb')
464 464 log.debug('Creating new temp archive in %s' % archive)
465 465 cs.fill_archive(stream=t, kind=fileformat, subrepos=subrepos)
466 466 if archive_cache_enabled:
467 467 #if we generated the archive and use cache rename that
468 468 log.debug('Storing new archive in %s' % cached_archive_path)
469 469 shutil.move(archive, cached_archive_path)
470 470 archive = cached_archive_path
471 471 finally:
472 472 t.close()
473 473
474 474 def get_chunked_archive(archive):
475 475 stream = open(archive, 'rb')
476 476 while True:
477 477 data = stream.read(16 * 1024)
478 478 if not data:
479 479 stream.close()
480 480 if fd: # fd means we used temporary file
481 481 os.close(fd)
482 482 if not archive_cache_enabled:
483 483 log.debug('Destroing temp archive %s' % archive)
484 484 os.remove(archive)
485 485 break
486 486 yield data
487 487
488 488 response.content_disposition = str('attachment; filename=%s' % (archive_name))
489 489 response.content_type = str(content_type)
490 490 return get_chunked_archive(archive)
491 491
492 492 @LoginRequired()
493 493 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
494 494 'repository.admin')
495 495 def diff(self, repo_name, f_path):
496 496 ignore_whitespace = request.GET.get('ignorews') == '1'
497 497 line_context = request.GET.get('context', 3)
498 498 diff1 = request.GET.get('diff1', '')
499 499 diff2 = request.GET.get('diff2', '')
500 500 c.action = request.GET.get('diff')
501 501 c.no_changes = diff1 == diff2
502 502 c.f_path = f_path
503 503 c.big_diff = False
504 504 c.anchor_url = anchor_url
505 505 c.ignorews_url = _ignorews_url
506 506 c.context_url = _context_url
507 507 c.changes = OrderedDict()
508 508 c.changes[diff2] = []
509 509
510 510 #special case if we want a show rev only, it's impl here
511 511 #to reduce JS and callbacks
512 512
513 513 if request.GET.get('show_rev'):
514 514 if str2bool(request.GET.get('annotate', 'False')):
515 515 _url = url('files_annotate_home', repo_name=c.repo_name,
516 516 revision=diff1, f_path=c.f_path)
517 517 else:
518 518 _url = url('files_home', repo_name=c.repo_name,
519 519 revision=diff1, f_path=c.f_path)
520 520
521 521 return redirect(_url)
522 522 try:
523 523 if diff1 not in ['', None, 'None', '0' * 12, '0' * 40]:
524 524 c.changeset_1 = c.rhodecode_repo.get_changeset(diff1)
525 525 try:
526 526 node1 = c.changeset_1.get_node(f_path)
527 527 if node1.is_dir():
528 528 raise NodeError('%s path is a %s not a file'
529 529 % (node1, type(node1)))
530 530 except NodeDoesNotExistError:
531 531 c.changeset_1 = EmptyChangeset(cs=diff1,
532 532 revision=c.changeset_1.revision,
533 533 repo=c.rhodecode_repo)
534 534 node1 = FileNode(f_path, '', changeset=c.changeset_1)
535 535 else:
536 536 c.changeset_1 = EmptyChangeset(repo=c.rhodecode_repo)
537 537 node1 = FileNode(f_path, '', changeset=c.changeset_1)
538 538
539 539 if diff2 not in ['', None, 'None', '0' * 12, '0' * 40]:
540 540 c.changeset_2 = c.rhodecode_repo.get_changeset(diff2)
541 541 try:
542 542 node2 = c.changeset_2.get_node(f_path)
543 543 if node2.is_dir():
544 544 raise NodeError('%s path is a %s not a file'
545 545 % (node2, type(node2)))
546 546 except NodeDoesNotExistError:
547 547 c.changeset_2 = EmptyChangeset(cs=diff2,
548 548 revision=c.changeset_2.revision,
549 549 repo=c.rhodecode_repo)
550 550 node2 = FileNode(f_path, '', changeset=c.changeset_2)
551 551 else:
552 552 c.changeset_2 = EmptyChangeset(repo=c.rhodecode_repo)
553 553 node2 = FileNode(f_path, '', changeset=c.changeset_2)
554 554 except (RepositoryError, NodeError):
555 555 log.error(traceback.format_exc())
556 556 return redirect(url('files_home', repo_name=c.repo_name,
557 557 f_path=f_path))
558 558
559 559 if c.action == 'download':
560 560 _diff = diffs.get_gitdiff(node1, node2,
561 561 ignore_whitespace=ignore_whitespace,
562 562 context=line_context)
563 563 diff = diffs.DiffProcessor(_diff, format='gitdiff')
564 564
565 565 diff_name = '%s_vs_%s.diff' % (diff1, diff2)
566 566 response.content_type = 'text/plain'
567 567 response.content_disposition = (
568 568 'attachment; filename=%s' % diff_name
569 569 )
570 570 return diff.as_raw()
571 571
572 572 elif c.action == 'raw':
573 573 _diff = diffs.get_gitdiff(node1, node2,
574 574 ignore_whitespace=ignore_whitespace,
575 575 context=line_context)
576 576 diff = diffs.DiffProcessor(_diff, format='gitdiff')
577 577 response.content_type = 'text/plain'
578 578 return diff.as_raw()
579 579
580 580 else:
581 581 fid = h.FID(diff2, node2.path)
582 582 line_context_lcl = get_line_ctx(fid, request.GET)
583 583 ign_whitespace_lcl = get_ignore_ws(fid, request.GET)
584 584
585 585 lim = request.GET.get('fulldiff') or self.cut_off_limit
586 586 _, cs1, cs2, diff, st = diffs.wrapped_diff(filenode_old=node1,
587 587 filenode_new=node2,
588 588 cut_off_limit=lim,
589 589 ignore_whitespace=ign_whitespace_lcl,
590 590 line_context=line_context_lcl,
591 591 enable_comments=False)
592 592 op = ''
593 593 filename = node1.path
594 594 cs_changes = {
595 595 'fid': [cs1, cs2, op, filename, diff, st]
596 596 }
597 597 c.changes = cs_changes
598 598
599 599 return render('files/file_diff.html')
600 600
601 601 def _get_node_history(self, cs, f_path, changesets=None):
602 602 """
603 603 get changesets history for given node
604 604
605 605 :param cs: changeset to calculate history
606 606 :param f_path: path for node to calculate history for
607 607 :param changesets: if passed don't calculate history and take
608 608 changesets defined in this list
609 609 """
610 610 # calculate history based on tip
611 611 tip_cs = c.rhodecode_repo.get_changeset()
612 612 if changesets is None:
613 613 try:
614 614 changesets = tip_cs.get_file_history(f_path)
615 615 except (NodeDoesNotExistError, ChangesetError):
616 616 #this node is not present at tip !
617 617 changesets = cs.get_file_history(f_path)
618 618 hist_l = []
619 619
620 620 changesets_group = ([], _("Changesets"))
621 621 branches_group = ([], _("Branches"))
622 622 tags_group = ([], _("Tags"))
623 623 _hg = cs.repository.alias == 'hg'
624 624 for chs in changesets:
625 625 #_branch = '(%s)' % chs.branch if _hg else ''
626 626 _branch = chs.branch
627 627 n_desc = 'r%s:%s (%s)' % (chs.revision, chs.short_id, _branch)
628 628 changesets_group[0].append((chs.raw_id, n_desc,))
629 629 hist_l.append(changesets_group)
630 630
631 631 for name, chs in c.rhodecode_repo.branches.items():
632 632 branches_group[0].append((chs, name),)
633 633 hist_l.append(branches_group)
634 634
635 635 for name, chs in c.rhodecode_repo.tags.items():
636 636 tags_group[0].append((chs, name),)
637 637 hist_l.append(tags_group)
638 638
639 639 return hist_l, changesets
640 640
641 641 @LoginRequired()
642 642 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
643 643 'repository.admin')
644 644 @jsonify
645 645 def nodelist(self, repo_name, revision, f_path):
646 646 if request.environ.get('HTTP_X_PARTIAL_XHR'):
647 647 cs = self.__get_cs_or_redirect(revision, repo_name)
648 648 _d, _f = ScmModel().get_nodes(repo_name, cs.raw_id, f_path,
649 649 flat=False)
650 650 return {'nodes': _d + _f}
@@ -1,467 +1,735 b''
1 import os
1 2 from rhodecode.tests import *
2 3 from rhodecode.model.db import Repository
3 4 from rhodecode.model.meta import Session
5 from rhodecode.tests.fixture import Fixture
6
7 fixture = Fixture()
4 8
5 9 ARCHIVE_SPECS = {
6 10 '.tar.bz2': ('application/x-bzip2', 'tbz2', ''),
7 11 '.tar.gz': ('application/x-gzip', 'tgz', ''),
8 12 '.zip': ('application/zip', 'zip', ''),
9 13 }
10 14
11 15
12 16 def _set_downloads(repo_name, set_to):
13 17 repo = Repository.get_by_repo_name(repo_name)
14 18 repo.enable_downloads = set_to
15 19 Session().add(repo)
16 20 Session().commit()
17 21
18 22
19 23 class TestFilesController(TestController):
20 24
21 25 def test_index(self):
22 26 self.log_user()
23 27 response = self.app.get(url(controller='files', action='index',
24 28 repo_name=HG_REPO,
25 29 revision='tip',
26 30 f_path='/'))
27 31 # Test response...
28 32 response.mustcontain('<a class="browser-dir ypjax-link" href="/vcs_test_hg/files/96507bd11ecc815ebc6270fdf6db110928c09c1e/docs">docs</a>')
29 33 response.mustcontain('<a class="browser-dir ypjax-link" href="/vcs_test_hg/files/96507bd11ecc815ebc6270fdf6db110928c09c1e/vcs">vcs</a>')
30 34 response.mustcontain('<a class="browser-file ypjax-link" href="/vcs_test_hg/files/96507bd11ecc815ebc6270fdf6db110928c09c1e/.gitignore">.gitignore</a>')
31 35 response.mustcontain('<a class="browser-file ypjax-link" href="/vcs_test_hg/files/96507bd11ecc815ebc6270fdf6db110928c09c1e/.hgignore">.hgignore</a>')
32 36 response.mustcontain('<a class="browser-file ypjax-link" href="/vcs_test_hg/files/96507bd11ecc815ebc6270fdf6db110928c09c1e/.hgtags">.hgtags</a>')
33 37 response.mustcontain('<a class="browser-file ypjax-link" href="/vcs_test_hg/files/96507bd11ecc815ebc6270fdf6db110928c09c1e/.travis.yml">.travis.yml</a>')
34 38 response.mustcontain('<a class="browser-file ypjax-link" href="/vcs_test_hg/files/96507bd11ecc815ebc6270fdf6db110928c09c1e/MANIFEST.in">MANIFEST.in</a>')
35 39 response.mustcontain('<a class="browser-file ypjax-link" href="/vcs_test_hg/files/96507bd11ecc815ebc6270fdf6db110928c09c1e/README.rst">README.rst</a>')
36 40 response.mustcontain('<a class="browser-file ypjax-link" href="/vcs_test_hg/files/96507bd11ecc815ebc6270fdf6db110928c09c1e/run_test_and_report.sh">run_test_and_report.sh</a>')
37 41 response.mustcontain('<a class="browser-file ypjax-link" href="/vcs_test_hg/files/96507bd11ecc815ebc6270fdf6db110928c09c1e/setup.cfg">setup.cfg</a>')
38 42 response.mustcontain('<a class="browser-file ypjax-link" href="/vcs_test_hg/files/96507bd11ecc815ebc6270fdf6db110928c09c1e/setup.py">setup.py</a>')
39 43 response.mustcontain('<a class="browser-file ypjax-link" href="/vcs_test_hg/files/96507bd11ecc815ebc6270fdf6db110928c09c1e/test_and_report.sh">test_and_report.sh</a>')
40 44 response.mustcontain('<a class="browser-file ypjax-link" href="/vcs_test_hg/files/96507bd11ecc815ebc6270fdf6db110928c09c1e/tox.ini">tox.ini</a>')
41 45
42 46 def test_index_revision(self):
43 47 self.log_user()
44 48
45 49 response = self.app.get(
46 50 url(controller='files', action='index',
47 51 repo_name=HG_REPO,
48 52 revision='7ba66bec8d6dbba14a2155be32408c435c5f4492',
49 53 f_path='/')
50 54 )
51 55
52 56 #Test response...
53 57
54 58 response.mustcontain('<a class="browser-dir ypjax-link" href="/vcs_test_hg/files/7ba66bec8d6dbba14a2155be32408c435c5f4492/docs">docs</a>')
55 59 response.mustcontain('<a class="browser-dir ypjax-link" href="/vcs_test_hg/files/7ba66bec8d6dbba14a2155be32408c435c5f4492/tests">tests</a>')
56 60 response.mustcontain('<a class="browser-file ypjax-link" href="/vcs_test_hg/files/7ba66bec8d6dbba14a2155be32408c435c5f4492/README.rst">README.rst</a>')
57 61 response.mustcontain('1.1 KiB')
58 62 response.mustcontain('text/x-python')
59 63
60 64 def test_index_different_branch(self):
61 65 self.log_user()
62 66
63 67 response = self.app.get(url(controller='files', action='index',
64 68 repo_name=HG_REPO,
65 69 revision='97e8b885c04894463c51898e14387d80c30ed1ee',
66 70 f_path='/'))
67 71
68 72 response.mustcontain("""<span style="text-transform: uppercase;"><a href="#">Branch: git</a></span>""")
69 73
70 74 def test_index_paging(self):
71 75 self.log_user()
72 76
73 77 for r in [(73, 'a066b25d5df7016b45a41b7e2a78c33b57adc235'),
74 78 (92, 'cc66b61b8455b264a7a8a2d8ddc80fcfc58c221e'),
75 79 (109, '75feb4c33e81186c87eac740cee2447330288412'),
76 80 (1, '3d8f361e72ab303da48d799ff1ac40d5ac37c67e'),
77 81 (0, 'b986218ba1c9b0d6a259fac9b050b1724ed8e545')]:
78 82
79 83 response = self.app.get(url(controller='files', action='index',
80 84 repo_name=HG_REPO,
81 85 revision=r[1],
82 86 f_path='/'))
83 87
84 88 response.mustcontain("""@ r%s:%s""" % (r[0], r[1][:12]))
85 89
86 90 def test_file_source(self):
87 91 self.log_user()
88 92 response = self.app.get(url(controller='files', action='index',
89 93 repo_name=HG_REPO,
90 94 revision='8911406ad776fdd3d0b9932a2e89677e57405a48',
91 95 f_path='vcs/nodes.py'))
92 96
93 97 response.mustcontain("""<div class="commit">Partially implemented <a class="issue-tracker-link" href="https://myissueserver.com/vcs_test_hg/issue/16">#16</a>. filecontent/commit message/author/node name are safe_unicode now.
94 98 In addition some other __str__ are unicode as well
95 99 Added test for unicode
96 100 Improved test to clone into uniq repository.
97 101 removed extra unicode conversion in diff.</div>
98 102 """)
99 103
100 104 response.mustcontain("""<span style="text-transform: uppercase;"><a href="#">Branch: default</a></span>""")
101 105
102 106 def test_file_source_history(self):
103 107 self.log_user()
104 108 response = self.app.get(url(controller='files', action='history',
105 109 repo_name=HG_REPO,
106 110 revision='27cd5cce30c96924232dffcd24178a07ffeb5dfc',
107 111 f_path='vcs/nodes.py'),
108 112 extra_environ={'HTTP_X_PARTIAL_XHR': '1'},)
109 113 #test or history
110 114 response.mustcontain("""<optgroup label="Changesets">
111 115 <option value="dbec37a0d5cab8ff39af4cfc4a4cd3996e4acfc6">r648:dbec37a0d5ca (default)</option>
112 116 <option value="1d20ed9eda9482d46ff0a6af5812550218b3ff15">r639:1d20ed9eda94 (default)</option>
113 117 <option value="0173395e822797f098799ed95c1a81b6a547a9ad">r547:0173395e8227 (default)</option>
114 118 <option value="afbb45ade933a8182f1d8ec5d4d1bb2de2572043">r546:afbb45ade933 (default)</option>
115 119 <option value="6f093e30cac34e6b4b11275a9f22f80c5d7ad1f7">r502:6f093e30cac3 (default)</option>
116 120 <option value="c7e2212dd2ae975d1d06534a3d7e317165c06960">r476:c7e2212dd2ae (default)</option>
117 121 <option value="45477506df79f701bf69419aac3e1f0fed3c5bcf">r472:45477506df79 (default)</option>
118 122 <option value="5fc76cb25d11e07c60de040f78b8cd265ff10d53">r469:5fc76cb25d11 (default)</option>
119 123 <option value="b073433cf8994969ee5cd7cce84cbe587bb880b2">r468:b073433cf899 (default)</option>
120 124 <option value="7a74dbfcacd1dbcb58bb9c860b2f29fbb22c4c96">r467:7a74dbfcacd1 (default)</option>
121 125 <option value="71ee52cc4d629096bdbee036325975dac2af4501">r465:71ee52cc4d62 (default)</option>
122 126 <option value="a5b217d26c5f111e72bae4de672b084ee0fbf75c">r452:a5b217d26c5f (default)</option>
123 127 <option value="47aedd538bf616eedcb0e7d630ea476df0e159c7">r450:47aedd538bf6 (default)</option>
124 128 <option value="8e4915fa32d727dcbf09746f637a5f82e539511e">r432:8e4915fa32d7 (default)</option>
125 129 <option value="25213a5fbb048dff8ba65d21e466a835536e5b70">r356:25213a5fbb04 (default)</option>
126 130 <option value="23debcedddc1c23c14be33e713e7786d4a9de471">r351:23debcedddc1 (default)</option>
127 131 <option value="61e25b2a90a19e7fffd75dea1e4c7e20df526bbe">r342:61e25b2a90a1 (default)</option>
128 132 <option value="fb95b340e0d03fa51f33c56c991c08077c99303e">r318:fb95b340e0d0 (webvcs)</option>
129 133 <option value="bda35e0e564fbbc5cd26fe0a37fb647a254c99fe">r303:bda35e0e564f (default)</option>
130 134 <option value="97ff74896d7dbf3115a337a421d44b55154acc89">r302:97ff74896d7d (default)</option>
131 135 <option value="cec3473c3fdb9599c98067182a075b49bde570f9">r293:cec3473c3fdb (default)</option>
132 136 <option value="0e86c43eef866a013a587666a877c879899599bb">r289:0e86c43eef86 (default)</option>
133 137 <option value="91a27c312808100cf20a602f78befbbff9d89bfd">r288:91a27c312808 (default)</option>
134 138 <option value="400e36a1670a57d11e3edcb5b07bf82c30006d0b">r287:400e36a1670a (default)</option>
135 139 <option value="014fb17dfc95b0995e838c565376bf9a993e230a">r261:014fb17dfc95 (default)</option>
136 140 <option value="cca7aebbc4d6125798446b11e69dc8847834a982">r260:cca7aebbc4d6 (default)</option>
137 141 <option value="14cdb2957c011a5feba36f50d960d9832ba0f0c1">r258:14cdb2957c01 (workdir)</option>
138 142 <option value="34df20118ed74b5987d22a579e8a60e903da5bf8">r245:34df20118ed7 (default)</option>
139 143 <option value="0375d9042a64a1ac1641528f0f0668f9a339e86d">r233:0375d9042a64 (workdir)</option>
140 144 <option value="94aa45fc1806c04d4ba640933edf682c22478453">r222:94aa45fc1806 (workdir)</option>
141 145 <option value="7ed99bc738818879941e3ce20243f8856a7cfc84">r188:7ed99bc73881 (default)</option>
142 146 <option value="1e85975528bcebe853732a9e5fb8dbf4461f6bb2">r184:1e85975528bc (default)</option>
143 147 <option value="ed30beddde7bbddb26042625be19bcd11576c1dd">r183:ed30beddde7b (default)</option>
144 148 <option value="a6664e18181c6fc81b751a8d01474e7e1a3fe7fc">r177:a6664e18181c (default)</option>
145 149 <option value="8911406ad776fdd3d0b9932a2e89677e57405a48">r167:8911406ad776 (default)</option>
146 150 <option value="aa957ed78c35a1541f508d2ec90e501b0a9e3167">r165:aa957ed78c35 (default)</option>
147 151 <option value="48e11b73e94c0db33e736eaeea692f990cb0b5f1">r140:48e11b73e94c (default)</option>
148 152 <option value="adf3cbf483298563b968a6c673cd5bde5f7d5eea">r126:adf3cbf48329 (default)</option>
149 153 <option value="6249fd0fb2cfb1411e764129f598e2cf0de79a6f">r113:6249fd0fb2cf (git)</option>
150 154 <option value="75feb4c33e81186c87eac740cee2447330288412">r109:75feb4c33e81 (default)</option>
151 155 <option value="9a4dc232ecdc763ef2e98ae2238cfcbba4f6ad8d">r108:9a4dc232ecdc (default)</option>
152 156 <option value="595cce4efa21fda2f2e4eeb4fe5f2a6befe6fa2d">r107:595cce4efa21 (default)</option>
153 157 <option value="4a8bd421fbc2dfbfb70d85a3fe064075ab2c49da">r104:4a8bd421fbc2 (default)</option>
154 158 <option value="57be63fc8f85e65a0106a53187f7316f8c487ffa">r102:57be63fc8f85 (default)</option>
155 159 <option value="5530bd87f7e2e124a64d07cb2654c997682128be">r101:5530bd87f7e2 (git)</option>
156 160 <option value="e516008b1c93f142263dc4b7961787cbad654ce1">r99:e516008b1c93 (default)</option>
157 161 <option value="41f43fc74b8b285984554532eb105ac3be5c434f">r93:41f43fc74b8b (default)</option>
158 162 <option value="cc66b61b8455b264a7a8a2d8ddc80fcfc58c221e">r92:cc66b61b8455 (default)</option>
159 163 <option value="73ab5b616b3271b0518682fb4988ce421de8099f">r91:73ab5b616b32 (default)</option>
160 164 <option value="e0da75f308c0f18f98e9ce6257626009fdda2b39">r82:e0da75f308c0 (default)</option>
161 165 <option value="fb2e41e0f0810be4d7103bc2a4c7be16ee3ec611">r81:fb2e41e0f081 (default)</option>
162 166 <option value="602ae2f5e7ade70b3b66a58cdd9e3e613dc8a028">r76:602ae2f5e7ad (default)</option>
163 167 <option value="a066b25d5df7016b45a41b7e2a78c33b57adc235">r73:a066b25d5df7 (default)</option>
164 168 <option value="637a933c905958ce5151f154147c25c1c7b68832">r61:637a933c9059 (web)</option>
165 169 <option value="0c21004effeb8ce2d2d5b4a8baf6afa8394b6fbc">r60:0c21004effeb (web)</option>
166 170 <option value="a1f39c56d3f1d52d5fb5920370a2a2716cd9a444">r59:a1f39c56d3f1 (web)</option>
167 171 <option value="97d32df05c715a3bbf936bf3cc4e32fb77fe1a7f">r58:97d32df05c71 (web)</option>
168 172 <option value="08eaf14517718dccea4b67755a93368341aca919">r57:08eaf1451771 (web)</option>
169 173 <option value="22f71ad265265a53238359c883aa976e725aa07d">r56:22f71ad26526 (web)</option>
170 174 <option value="97501f02b7b4330924b647755663a2d90a5e638d">r49:97501f02b7b4 (web)</option>
171 175 <option value="86ede6754f2b27309452bb11f997386ae01d0e5a">r47:86ede6754f2b (web)</option>
172 176 <option value="014c40c0203c423dc19ecf94644f7cac9d4cdce0">r45:014c40c0203c (web)</option>
173 177 <option value="ee87846a61c12153b51543bf860e1026c6d3dcba">r30:ee87846a61c1 (default)</option>
174 178 <option value="9bb326a04ae5d98d437dece54be04f830cf1edd9">r26:9bb326a04ae5 (default)</option>
175 179 <option value="536c1a19428381cfea92ac44985304f6a8049569">r24:536c1a194283 (default)</option>
176 180 <option value="dc5d2c0661b61928834a785d3e64a3f80d3aad9c">r8:dc5d2c0661b6 (default)</option>
177 181 <option value="3803844fdbd3b711175fc3da9bdacfcd6d29a6fb">r7:3803844fdbd3 (default)</option>
178 182 </optgroup>
179 183 <optgroup label="Branches">
180 184 <option value="96507bd11ecc815ebc6270fdf6db110928c09c1e">default</option>
181 185 <option value="4f7e2131323e0749a740c0a56ab68ae9269c562a">stable</option>
182 186 </optgroup>
183 187 <optgroup label="Tags">
184 188 <option value="2c96c02def9a7c997f33047761a53943e6254396">v0.2.0</option>
185 189 <option value="8680b1d1cee3aa3c1ab3734b76ee164bbedbc5c9">v0.1.9</option>
186 190 <option value="ecb25ba9c96faf1e65a0bc3fd914918420a2f116">v0.1.8</option>
187 191 <option value="f67633a2894edaf28513706d558205fa93df9209">v0.1.7</option>
188 192 <option value="02b38c0eb6f982174750c0e309ff9faddc0c7e12">v0.1.6</option>
189 193 <option value="a6664e18181c6fc81b751a8d01474e7e1a3fe7fc">v0.1.5</option>
190 194 <option value="fd4bdb5e9b2a29b4393a4ac6caef48c17ee1a200">v0.1.4</option>
191 195 <option value="17544fbfcd33ffb439e2b728b5d526b1ef30bfcf">v0.1.3</option>
192 196 <option value="a7e60bff65d57ac3a1a1ce3b12a70f8a9e8a7720">v0.1.2</option>
193 197 <option value="fef5bfe1dc17611d5fb59a7f6f95c55c3606f933">v0.1.11</option>
194 198 <option value="92831aebf2f8dd4879e897024b89d09af214df1c">v0.1.10</option>
195 199 <option value="eb3a60fc964309c1a318b8dfe26aa2d1586c85ae">v0.1.1</option>
196 200 <option value="96507bd11ecc815ebc6270fdf6db110928c09c1e">tip</option>
197 201 </optgroup>
198 202 """)
199 203
200 204 def test_file_annotation(self):
201 205 self.log_user()
202 206 response = self.app.get(url(controller='files', action='index',
203 207 repo_name=HG_REPO,
204 208 revision='27cd5cce30c96924232dffcd24178a07ffeb5dfc',
205 209 f_path='vcs/nodes.py',
206 210 annotate=True))
207 211
208 212 response.mustcontain("""<span style="text-transform: uppercase;"><a href="#">Branch: default</a></span>""")
209 213
210 214 def test_file_annotation_history(self):
211 215 self.log_user()
212 216 response = self.app.get(url(controller='files', action='history',
213 217 repo_name=HG_REPO,
214 218 revision='27cd5cce30c96924232dffcd24178a07ffeb5dfc',
215 219 f_path='vcs/nodes.py',
216 220 annotate=True),
217 221 extra_environ={'HTTP_X_PARTIAL_XHR': '1'})
218 222
219 223 response.mustcontain("""
220 224 <option value="dbec37a0d5cab8ff39af4cfc4a4cd3996e4acfc6">r648:dbec37a0d5ca (default)</option>
221 225 <option value="1d20ed9eda9482d46ff0a6af5812550218b3ff15">r639:1d20ed9eda94 (default)</option>
222 226 <option value="0173395e822797f098799ed95c1a81b6a547a9ad">r547:0173395e8227 (default)</option>
223 227 <option value="afbb45ade933a8182f1d8ec5d4d1bb2de2572043">r546:afbb45ade933 (default)</option>
224 228 <option value="6f093e30cac34e6b4b11275a9f22f80c5d7ad1f7">r502:6f093e30cac3 (default)</option>
225 229 <option value="c7e2212dd2ae975d1d06534a3d7e317165c06960">r476:c7e2212dd2ae (default)</option>
226 230 <option value="45477506df79f701bf69419aac3e1f0fed3c5bcf">r472:45477506df79 (default)</option>
227 231 <option value="5fc76cb25d11e07c60de040f78b8cd265ff10d53">r469:5fc76cb25d11 (default)</option>
228 232 <option value="b073433cf8994969ee5cd7cce84cbe587bb880b2">r468:b073433cf899 (default)</option>
229 233 <option value="7a74dbfcacd1dbcb58bb9c860b2f29fbb22c4c96">r467:7a74dbfcacd1 (default)</option>
230 234 <option value="71ee52cc4d629096bdbee036325975dac2af4501">r465:71ee52cc4d62 (default)</option>
231 235 <option value="a5b217d26c5f111e72bae4de672b084ee0fbf75c">r452:a5b217d26c5f (default)</option>
232 236 <option value="47aedd538bf616eedcb0e7d630ea476df0e159c7">r450:47aedd538bf6 (default)</option>
233 237 <option value="8e4915fa32d727dcbf09746f637a5f82e539511e">r432:8e4915fa32d7 (default)</option>
234 238 <option value="25213a5fbb048dff8ba65d21e466a835536e5b70">r356:25213a5fbb04 (default)</option>
235 239 <option value="23debcedddc1c23c14be33e713e7786d4a9de471">r351:23debcedddc1 (default)</option>
236 240 <option value="61e25b2a90a19e7fffd75dea1e4c7e20df526bbe">r342:61e25b2a90a1 (default)</option>
237 241 <option value="fb95b340e0d03fa51f33c56c991c08077c99303e">r318:fb95b340e0d0 (webvcs)</option>
238 242 <option value="bda35e0e564fbbc5cd26fe0a37fb647a254c99fe">r303:bda35e0e564f (default)</option>
239 243 <option value="97ff74896d7dbf3115a337a421d44b55154acc89">r302:97ff74896d7d (default)</option>
240 244 <option value="cec3473c3fdb9599c98067182a075b49bde570f9">r293:cec3473c3fdb (default)</option>
241 245 <option value="0e86c43eef866a013a587666a877c879899599bb">r289:0e86c43eef86 (default)</option>
242 246 <option value="91a27c312808100cf20a602f78befbbff9d89bfd">r288:91a27c312808 (default)</option>
243 247 <option value="400e36a1670a57d11e3edcb5b07bf82c30006d0b">r287:400e36a1670a (default)</option>
244 248 <option value="014fb17dfc95b0995e838c565376bf9a993e230a">r261:014fb17dfc95 (default)</option>
245 249 <option value="cca7aebbc4d6125798446b11e69dc8847834a982">r260:cca7aebbc4d6 (default)</option>
246 250 <option value="14cdb2957c011a5feba36f50d960d9832ba0f0c1">r258:14cdb2957c01 (workdir)</option>
247 251 <option value="34df20118ed74b5987d22a579e8a60e903da5bf8">r245:34df20118ed7 (default)</option>
248 252 <option value="0375d9042a64a1ac1641528f0f0668f9a339e86d">r233:0375d9042a64 (workdir)</option>
249 253 <option value="94aa45fc1806c04d4ba640933edf682c22478453">r222:94aa45fc1806 (workdir)</option>
250 254 <option value="7ed99bc738818879941e3ce20243f8856a7cfc84">r188:7ed99bc73881 (default)</option>
251 255 <option value="1e85975528bcebe853732a9e5fb8dbf4461f6bb2">r184:1e85975528bc (default)</option>
252 256 <option value="ed30beddde7bbddb26042625be19bcd11576c1dd">r183:ed30beddde7b (default)</option>
253 257 <option value="a6664e18181c6fc81b751a8d01474e7e1a3fe7fc">r177:a6664e18181c (default)</option>
254 258 <option value="8911406ad776fdd3d0b9932a2e89677e57405a48">r167:8911406ad776 (default)</option>
255 259 <option value="aa957ed78c35a1541f508d2ec90e501b0a9e3167">r165:aa957ed78c35 (default)</option>
256 260 <option value="48e11b73e94c0db33e736eaeea692f990cb0b5f1">r140:48e11b73e94c (default)</option>
257 261 <option value="adf3cbf483298563b968a6c673cd5bde5f7d5eea">r126:adf3cbf48329 (default)</option>
258 262 <option value="6249fd0fb2cfb1411e764129f598e2cf0de79a6f">r113:6249fd0fb2cf (git)</option>
259 263 <option value="75feb4c33e81186c87eac740cee2447330288412">r109:75feb4c33e81 (default)</option>
260 264 <option value="9a4dc232ecdc763ef2e98ae2238cfcbba4f6ad8d">r108:9a4dc232ecdc (default)</option>
261 265 <option value="595cce4efa21fda2f2e4eeb4fe5f2a6befe6fa2d">r107:595cce4efa21 (default)</option>
262 266 <option value="4a8bd421fbc2dfbfb70d85a3fe064075ab2c49da">r104:4a8bd421fbc2 (default)</option>
263 267 <option value="57be63fc8f85e65a0106a53187f7316f8c487ffa">r102:57be63fc8f85 (default)</option>
264 268 <option value="5530bd87f7e2e124a64d07cb2654c997682128be">r101:5530bd87f7e2 (git)</option>
265 269 <option value="e516008b1c93f142263dc4b7961787cbad654ce1">r99:e516008b1c93 (default)</option>
266 270 <option value="41f43fc74b8b285984554532eb105ac3be5c434f">r93:41f43fc74b8b (default)</option>
267 271 <option value="cc66b61b8455b264a7a8a2d8ddc80fcfc58c221e">r92:cc66b61b8455 (default)</option>
268 272 <option value="73ab5b616b3271b0518682fb4988ce421de8099f">r91:73ab5b616b32 (default)</option>
269 273 <option value="e0da75f308c0f18f98e9ce6257626009fdda2b39">r82:e0da75f308c0 (default)</option>
270 274 <option value="fb2e41e0f0810be4d7103bc2a4c7be16ee3ec611">r81:fb2e41e0f081 (default)</option>
271 275 <option value="602ae2f5e7ade70b3b66a58cdd9e3e613dc8a028">r76:602ae2f5e7ad (default)</option>
272 276 <option value="a066b25d5df7016b45a41b7e2a78c33b57adc235">r73:a066b25d5df7 (default)</option>
273 277 <option value="637a933c905958ce5151f154147c25c1c7b68832">r61:637a933c9059 (web)</option>
274 278 <option value="0c21004effeb8ce2d2d5b4a8baf6afa8394b6fbc">r60:0c21004effeb (web)</option>
275 279 <option value="a1f39c56d3f1d52d5fb5920370a2a2716cd9a444">r59:a1f39c56d3f1 (web)</option>
276 280 <option value="97d32df05c715a3bbf936bf3cc4e32fb77fe1a7f">r58:97d32df05c71 (web)</option>
277 281 <option value="08eaf14517718dccea4b67755a93368341aca919">r57:08eaf1451771 (web)</option>
278 282 <option value="22f71ad265265a53238359c883aa976e725aa07d">r56:22f71ad26526 (web)</option>
279 283 <option value="97501f02b7b4330924b647755663a2d90a5e638d">r49:97501f02b7b4 (web)</option>
280 284 <option value="86ede6754f2b27309452bb11f997386ae01d0e5a">r47:86ede6754f2b (web)</option>
281 285 <option value="014c40c0203c423dc19ecf94644f7cac9d4cdce0">r45:014c40c0203c (web)</option>
282 286 <option value="ee87846a61c12153b51543bf860e1026c6d3dcba">r30:ee87846a61c1 (default)</option>
283 287 <option value="9bb326a04ae5d98d437dece54be04f830cf1edd9">r26:9bb326a04ae5 (default)</option>
284 288 <option value="536c1a19428381cfea92ac44985304f6a8049569">r24:536c1a194283 (default)</option>
285 289 <option value="dc5d2c0661b61928834a785d3e64a3f80d3aad9c">r8:dc5d2c0661b6 (default)</option>
286 290 <option value="3803844fdbd3b711175fc3da9bdacfcd6d29a6fb">r7:3803844fdbd3 (default)</option>
287 291 </optgroup>
288 292 <optgroup label="Branches">
289 293 <option value="96507bd11ecc815ebc6270fdf6db110928c09c1e">default</option>
290 294 <option value="4f7e2131323e0749a740c0a56ab68ae9269c562a">stable</option>
291 295 </optgroup>
292 296 <optgroup label="Tags">
293 297 <option value="2c96c02def9a7c997f33047761a53943e6254396">v0.2.0</option>
294 298 <option value="8680b1d1cee3aa3c1ab3734b76ee164bbedbc5c9">v0.1.9</option>
295 299 <option value="ecb25ba9c96faf1e65a0bc3fd914918420a2f116">v0.1.8</option>
296 300 <option value="f67633a2894edaf28513706d558205fa93df9209">v0.1.7</option>
297 301 <option value="02b38c0eb6f982174750c0e309ff9faddc0c7e12">v0.1.6</option>
298 302 <option value="a6664e18181c6fc81b751a8d01474e7e1a3fe7fc">v0.1.5</option>
299 303 <option value="fd4bdb5e9b2a29b4393a4ac6caef48c17ee1a200">v0.1.4</option>
300 304 <option value="17544fbfcd33ffb439e2b728b5d526b1ef30bfcf">v0.1.3</option>
301 305 <option value="a7e60bff65d57ac3a1a1ce3b12a70f8a9e8a7720">v0.1.2</option>
302 306 <option value="fef5bfe1dc17611d5fb59a7f6f95c55c3606f933">v0.1.11</option>
303 307 <option value="92831aebf2f8dd4879e897024b89d09af214df1c">v0.1.10</option>
304 308 <option value="eb3a60fc964309c1a318b8dfe26aa2d1586c85ae">v0.1.1</option>
305 309 <option value="96507bd11ecc815ebc6270fdf6db110928c09c1e">tip</option>
306 310 </optgroup>""")
307 311
308 312 def test_file_annotation_git(self):
309 313 self.log_user()
310 314 response = self.app.get(url(controller='files', action='index',
311 315 repo_name=GIT_REPO,
312 316 revision='master',
313 317 f_path='vcs/nodes.py',
314 318 annotate=True))
315 319
316 320 def test_archival(self):
317 321 self.log_user()
318 322 _set_downloads(HG_REPO, set_to=True)
319 323 for arch_ext, info in ARCHIVE_SPECS.items():
320 324 short = '27cd5cce30c9%s' % arch_ext
321 325 fname = '27cd5cce30c96924232dffcd24178a07ffeb5dfc%s' % arch_ext
322 326 filename = '%s-%s' % (HG_REPO, short)
323 327 response = self.app.get(url(controller='files',
324 328 action='archivefile',
325 329 repo_name=HG_REPO,
326 330 fname=fname))
327 331
328 332 self.assertEqual(response.status, '200 OK')
329 333 heads = [
330 334 ('Pragma', 'no-cache'),
331 335 ('Cache-Control', 'no-cache'),
332 336 ('Content-Disposition', 'attachment; filename=%s' % filename),
333 337 ('Content-Type', '%s; charset=utf-8' % info[0]),
334 338 ]
335 339 self.assertEqual(response.response._headers.items(), heads)
336 340
337 341 def test_archival_wrong_ext(self):
338 342 self.log_user()
339 343 _set_downloads(HG_REPO, set_to=True)
340 344 for arch_ext in ['tar', 'rar', 'x', '..ax', '.zipz']:
341 345 fname = '27cd5cce30c96924232dffcd24178a07ffeb5dfc%s' % arch_ext
342 346
343 347 response = self.app.get(url(controller='files',
344 348 action='archivefile',
345 349 repo_name=HG_REPO,
346 350 fname=fname))
347 351 response.mustcontain('Unknown archive type')
348 352
349 353 def test_archival_wrong_revision(self):
350 354 self.log_user()
351 355 _set_downloads(HG_REPO, set_to=True)
352 356 for rev in ['00x000000', 'tar', 'wrong', '@##$@$42413232', '232dffcd']:
353 357 fname = '%s.zip' % rev
354 358
355 359 response = self.app.get(url(controller='files',
356 360 action='archivefile',
357 361 repo_name=HG_REPO,
358 362 fname=fname))
359 363 response.mustcontain('Unknown revision')
360 364
361 365 #==========================================================================
362 366 # RAW FILE
363 367 #==========================================================================
364 368 def test_raw_file_ok(self):
365 369 self.log_user()
366 370 response = self.app.get(url(controller='files', action='rawfile',
367 371 repo_name=HG_REPO,
368 372 revision='27cd5cce30c96924232dffcd24178a07ffeb5dfc',
369 373 f_path='vcs/nodes.py'))
370 374
371 375 self.assertEqual(response.content_disposition, "attachment; filename=nodes.py")
372 376 self.assertEqual(response.content_type, "text/x-python")
373 377
374 378 def test_raw_file_wrong_cs(self):
375 379 self.log_user()
376 380 rev = u'ERRORce30c96924232dffcd24178a07ffeb5dfc'
377 381 f_path = 'vcs/nodes.py'
378 382
379 383 response = self.app.get(url(controller='files', action='rawfile',
380 384 repo_name=HG_REPO,
381 385 revision=rev,
382 386 f_path=f_path), status=404)
383 387
384 388 msg = """Revision %s does not exist for this repository""" % (rev)
385 389 response.mustcontain(msg)
386 390
387 391 def test_raw_file_wrong_f_path(self):
388 392 self.log_user()
389 393 rev = '27cd5cce30c96924232dffcd24178a07ffeb5dfc'
390 394 f_path = 'vcs/ERRORnodes.py'
391 395 response = self.app.get(url(controller='files', action='rawfile',
392 396 repo_name=HG_REPO,
393 397 revision=rev,
394 398 f_path=f_path), status=404)
395 399
396 400 msg = "There is no file nor directory at the given path: &#39;%s&#39; at revision %s" % (f_path, rev[:12])
397 401 response.mustcontain(msg)
398 402
399 403 #==========================================================================
400 404 # RAW RESPONSE - PLAIN
401 405 #==========================================================================
402 406 def test_raw_ok(self):
403 407 self.log_user()
404 408 response = self.app.get(url(controller='files', action='raw',
405 409 repo_name=HG_REPO,
406 410 revision='27cd5cce30c96924232dffcd24178a07ffeb5dfc',
407 411 f_path='vcs/nodes.py'))
408 412
409 413 self.assertEqual(response.content_type, "text/plain")
410 414
411 415 def test_raw_wrong_cs(self):
412 416 self.log_user()
413 417 rev = u'ERRORcce30c96924232dffcd24178a07ffeb5dfc'
414 418 f_path = 'vcs/nodes.py'
415 419
416 420 response = self.app.get(url(controller='files', action='raw',
417 421 repo_name=HG_REPO,
418 422 revision=rev,
419 423 f_path=f_path), status=404)
420 424
421 425 msg = """Revision %s does not exist for this repository""" % (rev)
422 426 response.mustcontain(msg)
423 427
424 428 def test_raw_wrong_f_path(self):
425 429 self.log_user()
426 430 rev = '27cd5cce30c96924232dffcd24178a07ffeb5dfc'
427 431 f_path = 'vcs/ERRORnodes.py'
428 432 response = self.app.get(url(controller='files', action='raw',
429 433 repo_name=HG_REPO,
430 434 revision=rev,
431 435 f_path=f_path), status=404)
432 436 msg = "There is no file nor directory at the given path: &#39;%s&#39; at revision %s" % (f_path, rev[:12])
433 437 response.mustcontain(msg)
434 438
435 439 def test_ajaxed_files_list(self):
436 440 self.log_user()
437 441 rev = '27cd5cce30c96924232dffcd24178a07ffeb5dfc'
438 442 response = self.app.get(
439 443 url('files_nodelist_home', repo_name=HG_REPO, f_path='/',
440 444 revision=rev),
441 445 extra_environ={'HTTP_X_PARTIAL_XHR': '1'},
442 446 )
443 447 response.mustcontain("vcs/web/simplevcs/views/repository.py")
444 448
449 #HG - ADD FILE
445 450 def test_add_file_view_hg(self):
446 451 self.log_user()
447 452 response = self.app.get(url('files_add_home',
448 453 repo_name=HG_REPO,
449 454 revision='tip', f_path='/'))
450 455
456 def test_add_file_into_hg_missing_content(self):
457 self.log_user()
458 response = self.app.post(url('files_add_home',
459 repo_name=HG_REPO,
460 revision='tip', f_path='/'),
461 params={
462 ''
463 },
464 status=302)
465
466 self.checkSessionFlash(response, 'No content')
467
468 def test_add_file_into_hg_missing_filename(self):
469 self.log_user()
470 response = self.app.post(url('files_add_home',
471 repo_name=HG_REPO,
472 revision='tip', f_path='/'),
473 params={
474 'content': "foo"
475 },
476 status=302)
477
478 self.checkSessionFlash(response, 'No filename')
479
480 @parameterized.expand([
481 ('/abs', 'foo'),
482 ('../rel', 'foo'),
483 ('file/../foo', 'foo'),
484 ])
485 def test_add_file_into_hg_bad_filenames(self, location, filename):
486 self.log_user()
487 response = self.app.post(url('files_add_home',
488 repo_name=HG_REPO,
489 revision='tip', f_path='/'),
490 params={
491 'content': "foo",
492 'filename': filename,
493 'location': location
494 },
495 status=302)
496
497 self.checkSessionFlash(response, 'Location must be relative path and must not contain .. in path')
498
499 @parameterized.expand([
500 (1, '', 'foo.txt'),
501 (2, 'dir', 'foo.rst'),
502 (3, 'rel/dir', 'foo.bar'),
503 ])
504 def test_add_file_into_hg(self, cnt, location, filename):
505 self.log_user()
506 repo = fixture.create_repo('commit-test-%s' % cnt, repo_type='hg')
507 response = self.app.post(url('files_add_home',
508 repo_name=repo.repo_name,
509 revision='tip', f_path='/'),
510 params={
511 'content': "foo",
512 'filename': filename,
513 'location': location
514 },
515 status=302)
516 try:
517 self.checkSessionFlash(response, 'Successfully committed to %s'
518 % os.path.join(location, filename))
519 finally:
520 fixture.destroy_repo(repo.repo_name)
521
522 ##GIT - ADD FILE
451 523 def test_add_file_view_git(self):
452 524 self.log_user()
453 525 response = self.app.get(url('files_add_home',
454 526 repo_name=GIT_REPO,
455 527 revision='tip', f_path='/'))
456 528
529 def test_add_file_into_git_missing_content(self):
530 self.log_user()
531 response = self.app.post(url('files_add_home',
532 repo_name=GIT_REPO,
533 revision='tip', f_path='/'),
534 params={
535 ''
536 },
537 status=302)
538
539 self.checkSessionFlash(response, 'No content')
540
541 def test_add_file_into_git_missing_filename(self):
542 self.log_user()
543 response = self.app.post(url('files_add_home',
544 repo_name=GIT_REPO,
545 revision='tip', f_path='/'),
546 params={
547 'content': "foo"
548 },
549 status=302)
550
551 self.checkSessionFlash(response, 'No filename')
552
553 @parameterized.expand([
554 ('/abs', 'foo'),
555 ('../rel', 'foo'),
556 ('file/../foo', 'foo'),
557 ])
558 def test_add_file_into_git_bad_filenames(self, location, filename):
559 self.log_user()
560 response = self.app.post(url('files_add_home',
561 repo_name=GIT_REPO,
562 revision='tip', f_path='/'),
563 params={
564 'content': "foo",
565 'filename': filename,
566 'location': location
567 },
568 status=302)
569
570 self.checkSessionFlash(response, 'Location must be relative path and must not contain .. in path')
571
572 @parameterized.expand([
573 (1, '', 'foo.txt'),
574 (2, 'dir', 'foo.rst'),
575 (3, 'rel/dir', 'foo.bar'),
576 ])
577 def test_add_file_into_git(self, cnt, location, filename):
578 self.log_user()
579 repo = fixture.create_repo('commit-test-%s' % cnt, repo_type='git')
580 response = self.app.post(url('files_add_home',
581 repo_name=repo.repo_name,
582 revision='tip', f_path='/'),
583 params={
584 'content': "foo",
585 'filename': filename,
586 'location': location
587 },
588 status=302)
589 try:
590 self.checkSessionFlash(response, 'Successfully committed to %s'
591 % os.path.join(location, filename))
592 finally:
593 fixture.destroy_repo(repo.repo_name)
594
595 #HG - EDIT
457 596 def test_edit_file_view_hg(self):
458 597 self.log_user()
459 598 response = self.app.get(url('files_edit_home',
460 599 repo_name=HG_REPO,
461 600 revision='tip', f_path='vcs/nodes.py'))
462 601
602 def test_edit_file_view_not_on_branch_hg(self):
603 self.log_user()
604 repo = fixture.create_repo('test-edit-repo', repo_type='hg')
605
606 ## add file
607 location = 'vcs'
608 filename = 'nodes.py'
609 response = self.app.post(url('files_add_home',
610 repo_name=repo.repo_name,
611 revision='tip', f_path='/'),
612 params={
613 'content': "def py():\n print 'hello'\n",
614 'filename': filename,
615 'location': location
616 },
617 status=302)
618 response.follow()
619 try:
620 self.checkSessionFlash(response, 'Successfully committed to %s'
621 % os.path.join(location, filename))
622 response = self.app.get(url('files_edit_home',
623 repo_name=repo.repo_name,
624 revision='tip', f_path='vcs/nodes.py'),
625 status=302)
626 self.checkSessionFlash(response,
627 'You can only edit files with revision being a valid branch')
628 finally:
629 fixture.destroy_repo(repo.repo_name)
630
631 def test_edit_file_view_commit_changes_hg(self):
632 self.log_user()
633 repo = fixture.create_repo('test-edit-repo', repo_type='hg')
634
635 ## add file
636 location = 'vcs'
637 filename = 'nodes.py'
638 response = self.app.post(url('files_add_home',
639 repo_name=repo.repo_name,
640 revision='tip',
641 f_path='/'),
642 params={
643 'content': "def py():\n print 'hello'\n",
644 'filename': filename,
645 'location': location
646 },
647 status=302)
648 response.follow()
649 try:
650 self.checkSessionFlash(response, 'Successfully committed to %s'
651 % os.path.join(location, filename))
652 response = self.app.post(url('files_edit_home',
653 repo_name=repo.repo_name,
654 revision=repo.scm_instance.DEFAULT_BRANCH_NAME,
655 f_path='vcs/nodes.py'),
656 params={
657 'content': "def py():\n print 'hello world'\n",
658 'message': 'i commited',
659 },
660 status=302)
661 self.checkSessionFlash(response,
662 'Successfully committed to vcs/nodes.py')
663 finally:
664 fixture.destroy_repo(repo.repo_name)
665
666 #GIT - EDIT
463 667 def test_edit_file_view_git(self):
464 668 self.log_user()
465 669 response = self.app.get(url('files_edit_home',
466 670 repo_name=GIT_REPO,
467 671 revision='tip', f_path='vcs/nodes.py'))
672
673 def test_edit_file_view_not_on_branch_git(self):
674 self.log_user()
675 repo = fixture.create_repo('test-edit-repo', repo_type='git')
676
677 ## add file
678 location = 'vcs'
679 filename = 'nodes.py'
680 response = self.app.post(url('files_add_home',
681 repo_name=repo.repo_name,
682 revision='tip', f_path='/'),
683 params={
684 'content': "def py():\n print 'hello'\n",
685 'filename': filename,
686 'location': location
687 },
688 status=302)
689 response.follow()
690 try:
691 self.checkSessionFlash(response, 'Successfully committed to %s'
692 % os.path.join(location, filename))
693 response = self.app.get(url('files_edit_home',
694 repo_name=repo.repo_name,
695 revision='tip', f_path='vcs/nodes.py'),
696 status=302)
697 self.checkSessionFlash(response,
698 'You can only edit files with revision being a valid branch')
699 finally:
700 fixture.destroy_repo(repo.repo_name)
701
702 def test_edit_file_view_commit_changes_git(self):
703 self.log_user()
704 repo = fixture.create_repo('test-edit-repo', repo_type='git')
705
706 ## add file
707 location = 'vcs'
708 filename = 'nodes.py'
709 response = self.app.post(url('files_add_home',
710 repo_name=repo.repo_name,
711 revision='tip',
712 f_path='/'),
713 params={
714 'content': "def py():\n print 'hello'\n",
715 'filename': filename,
716 'location': location
717 },
718 status=302)
719 response.follow()
720 try:
721 self.checkSessionFlash(response, 'Successfully committed to %s'
722 % os.path.join(location, filename))
723 response = self.app.post(url('files_edit_home',
724 repo_name=repo.repo_name,
725 revision=repo.scm_instance.DEFAULT_BRANCH_NAME,
726 f_path='vcs/nodes.py'),
727 params={
728 'content': "def py():\n print 'hello world'\n",
729 'message': 'i commited',
730 },
731 status=302)
732 self.checkSessionFlash(response,
733 'Successfully committed to vcs/nodes.py')
734 finally:
735 fixture.destroy_repo(repo.repo_name)
General Comments 0
You need to be logged in to leave comments. Login now