##// END OF EJS Templates
Use paste fileapp to properly send the archive size
marcink -
r2294:3c1d9917 beta
parent child Browse files
Show More
@@ -1,487 +1,490 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
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 pylons.decorators import jsonify
35 from paste.fileapp import FileApp, _FileIter
35 36
36 37 from rhodecode.lib import diffs
37 38 from rhodecode.lib import helpers as h
38 39
39 40 from rhodecode.lib.compat import OrderedDict
40 41 from rhodecode.lib.utils2 import convert_line_endings, detect_mode, safe_str
41 42 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
42 43 from rhodecode.lib.base import BaseRepoController, render
43 44 from rhodecode.lib.utils import EmptyChangeset
44 45 from rhodecode.lib.vcs.conf import settings
45 46 from rhodecode.lib.vcs.exceptions import RepositoryError, \
46 47 ChangesetDoesNotExistError, EmptyRepositoryError, \
47 48 ImproperArchiveTypeError, VCSError, NodeAlreadyExistsError
48 49 from rhodecode.lib.vcs.nodes import FileNode
49 50
50 51 from rhodecode.model.repo import RepoModel
51 52 from rhodecode.model.scm import ScmModel
52 53 from rhodecode.model.db import Repository
53 54
54 55 from rhodecode.controllers.changeset import anchor_url, _ignorews_url,\
55 56 _context_url, get_line_ctx, get_ignore_ws
56 57
57 58
58 59 log = logging.getLogger(__name__)
59 60
60 61
61 62 class FilesController(BaseRepoController):
62 63
63 64 @LoginRequired()
64 65 def __before__(self):
65 66 super(FilesController, self).__before__()
66 67 c.cut_off_limit = self.cut_off_limit
67 68
68 69 def __get_cs_or_redirect(self, rev, repo_name, redirect_after=True):
69 70 """
70 71 Safe way to get changeset if error occur it redirects to tip with
71 72 proper message
72 73
73 74 :param rev: revision to fetch
74 75 :param repo_name: repo name to redirect after
75 76 """
76 77
77 78 try:
78 79 return c.rhodecode_repo.get_changeset(rev)
79 80 except EmptyRepositoryError, e:
80 81 if not redirect_after:
81 82 return None
82 83 url_ = url('files_add_home',
83 84 repo_name=c.repo_name,
84 85 revision=0, f_path='')
85 86 add_new = '<a href="%s">[%s]</a>' % (url_, _('add new'))
86 87 h.flash(h.literal(_('There are no files yet %s' % add_new)),
87 88 category='warning')
88 89 redirect(h.url('summary_home', repo_name=repo_name))
89 90
90 91 except RepositoryError, e:
91 92 h.flash(str(e), category='warning')
92 93 redirect(h.url('files_home', repo_name=repo_name, revision='tip'))
93 94
94 95 def __get_filenode_or_redirect(self, repo_name, cs, path):
95 96 """
96 97 Returns file_node, if error occurs or given path is directory,
97 98 it'll redirect to top level path
98 99
99 100 :param repo_name: repo_name
100 101 :param cs: given changeset
101 102 :param path: path to lookup
102 103 """
103 104
104 105 try:
105 106 file_node = cs.get_node(path)
106 107 if file_node.is_dir():
107 108 raise RepositoryError('given path is a directory')
108 109 except RepositoryError, e:
109 110 h.flash(str(e), category='warning')
110 111 redirect(h.url('files_home', repo_name=repo_name,
111 112 revision=cs.raw_id))
112 113
113 114 return file_node
114 115
115 116 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
116 117 'repository.admin')
117 118 def index(self, repo_name, revision, f_path, annotate=False):
118 119 # redirect to given revision from form if given
119 120 post_revision = request.POST.get('at_rev', None)
120 121 if post_revision:
121 122 cs = self.__get_cs_or_redirect(post_revision, repo_name)
122 123 redirect(url('files_home', repo_name=c.repo_name,
123 124 revision=cs.raw_id, f_path=f_path))
124 125
125 126 c.changeset = self.__get_cs_or_redirect(revision, repo_name)
126 127 c.branch = request.GET.get('branch', None)
127 128 c.f_path = f_path
128 129 c.annotate = annotate
129 130 cur_rev = c.changeset.revision
130 131
131 132 # prev link
132 133 try:
133 134 prev_rev = c.rhodecode_repo.get_changeset(cur_rev).prev(c.branch)
134 135 c.url_prev = url('files_home', repo_name=c.repo_name,
135 136 revision=prev_rev.raw_id, f_path=f_path)
136 137 if c.branch:
137 138 c.url_prev += '?branch=%s' % c.branch
138 139 except (ChangesetDoesNotExistError, VCSError):
139 140 c.url_prev = '#'
140 141
141 142 # next link
142 143 try:
143 144 next_rev = c.rhodecode_repo.get_changeset(cur_rev).next(c.branch)
144 145 c.url_next = url('files_home', repo_name=c.repo_name,
145 146 revision=next_rev.raw_id, f_path=f_path)
146 147 if c.branch:
147 148 c.url_next += '?branch=%s' % c.branch
148 149 except (ChangesetDoesNotExistError, VCSError):
149 150 c.url_next = '#'
150 151
151 152 # files or dirs
152 153 try:
153 154 c.file = c.changeset.get_node(f_path)
154 155
155 156 if c.file.is_file():
156 157 c.file_history = self._get_node_history(c.changeset, f_path)
157 158 else:
158 159 c.file_history = []
159 160 except RepositoryError, e:
160 161 h.flash(str(e), category='warning')
161 162 redirect(h.url('files_home', repo_name=repo_name,
162 163 revision=revision))
163 164
164 165 return render('files/files.html')
165 166
166 167 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
167 168 'repository.admin')
168 169 def rawfile(self, repo_name, revision, f_path):
169 170 cs = self.__get_cs_or_redirect(revision, repo_name)
170 171 file_node = self.__get_filenode_or_redirect(repo_name, cs, f_path)
171 172
172 173 response.content_disposition = 'attachment; filename=%s' % \
173 174 safe_str(f_path.split(Repository.url_sep())[-1])
174 175
175 176 response.content_type = file_node.mimetype
176 177 return file_node.content
177 178
178 179 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
179 180 'repository.admin')
180 181 def raw(self, repo_name, revision, f_path):
181 182 cs = self.__get_cs_or_redirect(revision, repo_name)
182 183 file_node = self.__get_filenode_or_redirect(repo_name, cs, f_path)
183 184
184 185 raw_mimetype_mapping = {
185 186 # map original mimetype to a mimetype used for "show as raw"
186 187 # you can also provide a content-disposition to override the
187 188 # default "attachment" disposition.
188 189 # orig_type: (new_type, new_dispo)
189 190
190 191 # show images inline:
191 192 'image/x-icon': ('image/x-icon', 'inline'),
192 193 'image/png': ('image/png', 'inline'),
193 194 'image/gif': ('image/gif', 'inline'),
194 195 'image/jpeg': ('image/jpeg', 'inline'),
195 196 'image/svg+xml': ('image/svg+xml', 'inline'),
196 197 }
197 198
198 199 mimetype = file_node.mimetype
199 200 try:
200 201 mimetype, dispo = raw_mimetype_mapping[mimetype]
201 202 except KeyError:
202 203 # we don't know anything special about this, handle it safely
203 204 if file_node.is_binary:
204 205 # do same as download raw for binary files
205 206 mimetype, dispo = 'application/octet-stream', 'attachment'
206 207 else:
207 208 # do not just use the original mimetype, but force text/plain,
208 209 # otherwise it would serve text/html and that might be unsafe.
209 210 # Note: underlying vcs library fakes text/plain mimetype if the
210 211 # mimetype can not be determined and it thinks it is not
211 212 # binary.This might lead to erroneous text display in some
212 213 # cases, but helps in other cases, like with text files
213 214 # without extension.
214 215 mimetype, dispo = 'text/plain', 'inline'
215 216
216 217 if dispo == 'attachment':
217 218 dispo = 'attachment; filename=%s' % \
218 219 safe_str(f_path.split(os.sep)[-1])
219 220
220 221 response.content_disposition = dispo
221 222 response.content_type = mimetype
222 223 return file_node.content
223 224
224 225 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
225 226 def edit(self, repo_name, revision, f_path):
226 227 r_post = request.POST
227 228
228 229 c.cs = self.__get_cs_or_redirect(revision, repo_name)
229 230 c.file = self.__get_filenode_or_redirect(repo_name, c.cs, f_path)
230 231
231 232 if c.file.is_binary:
232 233 return redirect(url('files_home', repo_name=c.repo_name,
233 234 revision=c.cs.raw_id, f_path=f_path))
234 235
235 236 c.f_path = f_path
236 237
237 238 if r_post:
238 239
239 240 old_content = c.file.content
240 241 sl = old_content.splitlines(1)
241 242 first_line = sl[0] if sl else ''
242 243 # modes: 0 - Unix, 1 - Mac, 2 - DOS
243 244 mode = detect_mode(first_line, 0)
244 245 content = convert_line_endings(r_post.get('content'), mode)
245 246
246 247 message = r_post.get('message') or (_('Edited %s via RhodeCode')
247 248 % (f_path))
248 249 author = self.rhodecode_user.full_contact
249 250
250 251 if content == old_content:
251 252 h.flash(_('No changes'),
252 253 category='warning')
253 254 return redirect(url('changeset_home', repo_name=c.repo_name,
254 255 revision='tip'))
255 256
256 257 try:
257 258 self.scm_model.commit_change(repo=c.rhodecode_repo,
258 259 repo_name=repo_name, cs=c.cs,
259 260 user=self.rhodecode_user,
260 261 author=author, message=message,
261 262 content=content, f_path=f_path)
262 263 h.flash(_('Successfully committed to %s' % f_path),
263 264 category='success')
264 265
265 266 except Exception:
266 267 log.error(traceback.format_exc())
267 268 h.flash(_('Error occurred during commit'), category='error')
268 269 return redirect(url('changeset_home',
269 270 repo_name=c.repo_name, revision='tip'))
270 271
271 272 return render('files/files_edit.html')
272 273
273 274 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
274 275 def add(self, repo_name, revision, f_path):
275 276 r_post = request.POST
276 277 c.cs = self.__get_cs_or_redirect(revision, repo_name,
277 278 redirect_after=False)
278 279 if c.cs is None:
279 280 c.cs = EmptyChangeset(alias=c.rhodecode_repo.alias)
280 281
281 282 c.f_path = f_path
282 283
283 284 if r_post:
284 285 unix_mode = 0
285 286 content = convert_line_endings(r_post.get('content'), unix_mode)
286 287
287 288 message = r_post.get('message') or (_('Added %s via RhodeCode')
288 289 % (f_path))
289 290 location = r_post.get('location')
290 291 filename = r_post.get('filename')
291 292 file_obj = r_post.get('upload_file', None)
292 293
293 294 if file_obj is not None and hasattr(file_obj, 'filename'):
294 295 filename = file_obj.filename
295 296 content = file_obj.file
296 297
297 298 node_path = os.path.join(location, filename)
298 299 author = self.rhodecode_user.full_contact
299 300
300 301 if not content:
301 302 h.flash(_('No content'), category='warning')
302 303 return redirect(url('changeset_home', repo_name=c.repo_name,
303 304 revision='tip'))
304 305 if not filename:
305 306 h.flash(_('No filename'), category='warning')
306 307 return redirect(url('changeset_home', repo_name=c.repo_name,
307 308 revision='tip'))
308 309
309 310 try:
310 311 self.scm_model.create_node(repo=c.rhodecode_repo,
311 312 repo_name=repo_name, cs=c.cs,
312 313 user=self.rhodecode_user,
313 314 author=author, message=message,
314 315 content=content, f_path=node_path)
315 316 h.flash(_('Successfully committed to %s' % node_path),
316 317 category='success')
317 318 except NodeAlreadyExistsError, e:
318 319 h.flash(_(e), category='error')
319 320 except Exception:
320 321 log.error(traceback.format_exc())
321 322 h.flash(_('Error occurred during commit'), category='error')
322 323 return redirect(url('changeset_home',
323 324 repo_name=c.repo_name, revision='tip'))
324 325
325 326 return render('files/files_add.html')
326 327
327 328 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
328 329 'repository.admin')
329 330 def archivefile(self, repo_name, fname):
330 331
331 332 fileformat = None
332 333 revision = None
333 334 ext = None
334 335 subrepos = request.GET.get('subrepos') == 'true'
335 336
336 337 for a_type, ext_data in settings.ARCHIVE_SPECS.items():
337 338 archive_spec = fname.split(ext_data[1])
338 339 if len(archive_spec) == 2 and archive_spec[1] == '':
339 340 fileformat = a_type or ext_data[1]
340 341 revision = archive_spec[0]
341 342 ext = ext_data[1]
342 343
343 344 try:
344 345 dbrepo = RepoModel().get_by_repo_name(repo_name)
345 346 if dbrepo.enable_downloads is False:
346 347 return _('downloads disabled')
347 348
348 349 if c.rhodecode_repo.alias == 'hg':
349 350 # patch and reset hooks section of UI config to not run any
350 351 # hooks on fetching archives with subrepos
351 352 for k, v in c.rhodecode_repo._repo.ui.configitems('hooks'):
352 353 c.rhodecode_repo._repo.ui.setconfig('hooks', k, None)
353 354
354 355 cs = c.rhodecode_repo.get_changeset(revision)
355 356 content_type = settings.ARCHIVE_SPECS[fileformat][0]
356 357 except ChangesetDoesNotExistError:
357 358 return _('Unknown revision %s') % revision
358 359 except EmptyRepositoryError:
359 360 return _('Empty repository')
360 361 except (ImproperArchiveTypeError, KeyError):
361 362 return _('Unknown archive type')
362 363
363 364 fd, _archive_name = tempfile.mkstemp(suffix='rcarchive')
364 365 with open(_archive_name, 'wb') as f:
365 366 cs.fill_archive(stream=f, kind=fileformat, subrepos=subrepos)
366 367
367 response.content_type = content_type
368 response.content_disposition = 'attachment; filename=%s-%s%s' \
368 content_disposition = 'attachment; filename=%s-%s%s' \
369 369 % (repo_name, revision[:12], ext)
370 response.content_length = str(os.path.getsize(_archive_name))
370 content_length = os.path.getsize(_archive_name)
371
372 headers = [('Content-Disposition', str(content_disposition)),
373 ('Content-Type', str(content_type)),
374 ('Content-Length', str(content_length))]
371 375
372 def get_chunked_archive(tmpfile):
373 while True:
374 data = tmpfile.read(16 * 1024)
375 if not data:
376 tmpfile.close()
377 os.unlink(tmpfile.name)
378 break
379 yield data
380 return get_chunked_archive(tmpfile=open(_archive_name, 'rb'))
376 class _DestroyingFileWrapper(_FileIter):
377 def close(self):
378 self.file.close
379 os.remove(self.file.name)
380
381 request.environ['wsgi.file_wrapper'] = _DestroyingFileWrapper
382 fapp = FileApp(_archive_name, headers=headers)
383 return fapp(request.environ, self.start_response)
381 384
382 385 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
383 386 'repository.admin')
384 387 def diff(self, repo_name, f_path):
385 388 ignore_whitespace = request.GET.get('ignorews') == '1'
386 389 line_context = request.GET.get('context', 3)
387 390 diff1 = request.GET.get('diff1', '')
388 391 diff2 = request.GET.get('diff2', '')
389 392 c.action = request.GET.get('diff')
390 393 c.no_changes = diff1 == diff2
391 394 c.f_path = f_path
392 395 c.big_diff = False
393 396 c.anchor_url = anchor_url
394 397 c.ignorews_url = _ignorews_url
395 398 c.context_url = _context_url
396 399 c.changes = OrderedDict()
397 400 c.changes[diff2] = []
398 401 try:
399 402 if diff1 not in ['', None, 'None', '0' * 12, '0' * 40]:
400 403 c.changeset_1 = c.rhodecode_repo.get_changeset(diff1)
401 404 node1 = c.changeset_1.get_node(f_path)
402 405 else:
403 406 c.changeset_1 = EmptyChangeset(repo=c.rhodecode_repo)
404 407 node1 = FileNode('.', '', changeset=c.changeset_1)
405 408
406 409 if diff2 not in ['', None, 'None', '0' * 12, '0' * 40]:
407 410 c.changeset_2 = c.rhodecode_repo.get_changeset(diff2)
408 411 node2 = c.changeset_2.get_node(f_path)
409 412 else:
410 413 c.changeset_2 = EmptyChangeset(repo=c.rhodecode_repo)
411 414 node2 = FileNode('.', '', changeset=c.changeset_2)
412 415 except RepositoryError:
413 416 return redirect(url('files_home', repo_name=c.repo_name,
414 417 f_path=f_path))
415 418
416 419 if c.action == 'download':
417 420 _diff = diffs.get_gitdiff(node1, node2,
418 421 ignore_whitespace=ignore_whitespace,
419 422 context=line_context)
420 423 diff = diffs.DiffProcessor(_diff, format='gitdiff')
421 424
422 425 diff_name = '%s_vs_%s.diff' % (diff1, diff2)
423 426 response.content_type = 'text/plain'
424 427 response.content_disposition = (
425 428 'attachment; filename=%s' % diff_name
426 429 )
427 430 return diff.raw_diff()
428 431
429 432 elif c.action == 'raw':
430 433 _diff = diffs.get_gitdiff(node1, node2,
431 434 ignore_whitespace=ignore_whitespace,
432 435 context=line_context)
433 436 diff = diffs.DiffProcessor(_diff, format='gitdiff')
434 437 response.content_type = 'text/plain'
435 438 return diff.raw_diff()
436 439
437 440 else:
438 441 fid = h.FID(diff2, node2.path)
439 442 line_context_lcl = get_line_ctx(fid, request.GET)
440 443 ign_whitespace_lcl = get_ignore_ws(fid, request.GET)
441 444
442 445 lim = request.GET.get('fulldiff') or self.cut_off_limit
443 446 _, cs1, cs2, diff, st = diffs.wrapped_diff(filenode_old=node1,
444 447 filenode_new=node2,
445 448 cut_off_limit=lim,
446 449 ignore_whitespace=ign_whitespace_lcl,
447 450 line_context=line_context_lcl,
448 451 enable_comments=False)
449 452
450 453 c.changes = [('', node2, diff, cs1, cs2, st,)]
451 454
452 455 return render('files/file_diff.html')
453 456
454 457 def _get_node_history(self, cs, f_path):
455 458 changesets = cs.get_file_history(f_path)
456 459 hist_l = []
457 460
458 461 changesets_group = ([], _("Changesets"))
459 462 branches_group = ([], _("Branches"))
460 463 tags_group = ([], _("Tags"))
461 464 _hg = cs.repository.alias == 'hg'
462 465 for chs in changesets:
463 466 _branch = '(%s)' % chs.branch if _hg else ''
464 467 n_desc = 'r%s:%s %s' % (chs.revision, chs.short_id, _branch)
465 468 changesets_group[0].append((chs.raw_id, n_desc,))
466 469
467 470 hist_l.append(changesets_group)
468 471
469 472 for name, chs in c.rhodecode_repo.branches.items():
470 473 branches_group[0].append((chs, name),)
471 474 hist_l.append(branches_group)
472 475
473 476 for name, chs in c.rhodecode_repo.tags.items():
474 477 tags_group[0].append((chs, name),)
475 478 hist_l.append(tags_group)
476 479
477 480 return hist_l
478 481
479 482 @jsonify
480 483 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
481 484 'repository.admin')
482 485 def nodelist(self, repo_name, revision, f_path):
483 486 if request.environ.get('HTTP_X_PARTIAL_XHR'):
484 487 cs = self.__get_cs_or_redirect(revision, repo_name)
485 488 _d, _f = ScmModel().get_nodes(repo_name, cs.raw_id, f_path,
486 489 flat=False)
487 490 return _d + _f
@@ -1,318 +1,317 b''
1 1 from rhodecode.tests import *
2 2
3 3 ARCHIVE_SPECS = {
4 4 '.tar.bz2': ('application/x-bzip2', 'tbz2', ''),
5 5 '.tar.gz': ('application/x-gzip', 'tgz', ''),
6 6 '.zip': ('application/zip', 'zip', ''),
7 7 }
8 8
9 9
10 10 class TestFilesController(TestController):
11 11
12 12 def test_index(self):
13 13 self.log_user()
14 14 response = self.app.get(url(controller='files', action='index',
15 15 repo_name=HG_REPO,
16 16 revision='tip',
17 17 f_path='/'))
18 18 # Test response...
19 19 response.mustcontain('<a class="browser-dir ypjax-link" href="/vcs_test_hg/files/27cd5cce30c96924232dffcd24178a07ffeb5dfc/docs">docs</a>')
20 20 response.mustcontain('<a class="browser-dir ypjax-link" href="/vcs_test_hg/files/27cd5cce30c96924232dffcd24178a07ffeb5dfc/tests">tests</a>')
21 21 response.mustcontain('<a class="browser-dir ypjax-link" href="/vcs_test_hg/files/27cd5cce30c96924232dffcd24178a07ffeb5dfc/vcs">vcs</a>')
22 22 response.mustcontain('<a class="browser-file ypjax-link" href="/vcs_test_hg/files/27cd5cce30c96924232dffcd24178a07ffeb5dfc/.hgignore">.hgignore</a>')
23 23 response.mustcontain('<a class="browser-file ypjax-link" href="/vcs_test_hg/files/27cd5cce30c96924232dffcd24178a07ffeb5dfc/MANIFEST.in">MANIFEST.in</a>')
24 24
25 25 def test_index_revision(self):
26 26 self.log_user()
27 27
28 28 response = self.app.get(
29 29 url(controller='files', action='index',
30 30 repo_name=HG_REPO,
31 31 revision='7ba66bec8d6dbba14a2155be32408c435c5f4492',
32 32 f_path='/')
33 33 )
34 34
35 35 #Test response...
36 36
37 37 response.mustcontain('<a class="browser-dir ypjax-link" href="/vcs_test_hg/files/7ba66bec8d6dbba14a2155be32408c435c5f4492/docs">docs</a>')
38 38 response.mustcontain('<a class="browser-dir ypjax-link" href="/vcs_test_hg/files/7ba66bec8d6dbba14a2155be32408c435c5f4492/tests">tests</a>')
39 39 response.mustcontain('<a class="browser-file ypjax-link" href="/vcs_test_hg/files/7ba66bec8d6dbba14a2155be32408c435c5f4492/README.rst">README.rst</a>')
40 40 response.mustcontain('1.1 KiB')
41 41 response.mustcontain('text/x-python')
42 42
43 43 def test_index_different_branch(self):
44 44 self.log_user()
45 45
46 46 response = self.app.get(url(controller='files', action='index',
47 47 repo_name=HG_REPO,
48 48 revision='97e8b885c04894463c51898e14387d80c30ed1ee',
49 49 f_path='/'))
50 50
51 51 response.mustcontain("""<span style="text-transform: uppercase;"><a href="#">branch: git</a></span>""")
52 52
53 53 def test_index_paging(self):
54 54 self.log_user()
55 55
56 56 for r in [(73, 'a066b25d5df7016b45a41b7e2a78c33b57adc235'),
57 57 (92, 'cc66b61b8455b264a7a8a2d8ddc80fcfc58c221e'),
58 58 (109, '75feb4c33e81186c87eac740cee2447330288412'),
59 59 (1, '3d8f361e72ab303da48d799ff1ac40d5ac37c67e'),
60 60 (0, 'b986218ba1c9b0d6a259fac9b050b1724ed8e545')]:
61 61
62 62 response = self.app.get(url(controller='files', action='index',
63 63 repo_name=HG_REPO,
64 64 revision=r[1],
65 65 f_path='/'))
66 66
67 67 response.mustcontain("""@ r%s:%s""" % (r[0], r[1][:12]))
68 68
69 69 def test_file_source(self):
70 70 self.log_user()
71 71 response = self.app.get(url(controller='files', action='index',
72 72 repo_name=HG_REPO,
73 73 revision='27cd5cce30c96924232dffcd24178a07ffeb5dfc',
74 74 f_path='vcs/nodes.py'))
75 75
76 76 #test or history
77 77 response.mustcontain("""<optgroup label="Changesets">
78 78 <option value="8911406ad776fdd3d0b9932a2e89677e57405a48">r167:8911406ad776 (default)</option>
79 79 <option value="aa957ed78c35a1541f508d2ec90e501b0a9e3167">r165:aa957ed78c35 (default)</option>
80 80 <option value="48e11b73e94c0db33e736eaeea692f990cb0b5f1">r140:48e11b73e94c (default)</option>
81 81 <option value="adf3cbf483298563b968a6c673cd5bde5f7d5eea">r126:adf3cbf48329 (default)</option>
82 82 <option value="6249fd0fb2cfb1411e764129f598e2cf0de79a6f">r113:6249fd0fb2cf (git)</option>
83 83 <option value="75feb4c33e81186c87eac740cee2447330288412">r109:75feb4c33e81 (default)</option>
84 84 <option value="9a4dc232ecdc763ef2e98ae2238cfcbba4f6ad8d">r108:9a4dc232ecdc (default)</option>
85 85 <option value="595cce4efa21fda2f2e4eeb4fe5f2a6befe6fa2d">r107:595cce4efa21 (default)</option>
86 86 <option value="4a8bd421fbc2dfbfb70d85a3fe064075ab2c49da">r104:4a8bd421fbc2 (default)</option>
87 87 <option value="57be63fc8f85e65a0106a53187f7316f8c487ffa">r102:57be63fc8f85 (default)</option>
88 88 <option value="5530bd87f7e2e124a64d07cb2654c997682128be">r101:5530bd87f7e2 (git)</option>
89 89 <option value="e516008b1c93f142263dc4b7961787cbad654ce1">r99:e516008b1c93 (default)</option>
90 90 <option value="41f43fc74b8b285984554532eb105ac3be5c434f">r93:41f43fc74b8b (default)</option>
91 91 <option value="cc66b61b8455b264a7a8a2d8ddc80fcfc58c221e">r92:cc66b61b8455 (default)</option>
92 92 <option value="73ab5b616b3271b0518682fb4988ce421de8099f">r91:73ab5b616b32 (default)</option>
93 93 <option value="e0da75f308c0f18f98e9ce6257626009fdda2b39">r82:e0da75f308c0 (default)</option>
94 94 <option value="fb2e41e0f0810be4d7103bc2a4c7be16ee3ec611">r81:fb2e41e0f081 (default)</option>
95 95 <option value="602ae2f5e7ade70b3b66a58cdd9e3e613dc8a028">r76:602ae2f5e7ad (default)</option>
96 96 <option value="a066b25d5df7016b45a41b7e2a78c33b57adc235">r73:a066b25d5df7 (default)</option>
97 97 <option value="637a933c905958ce5151f154147c25c1c7b68832">r61:637a933c9059 (web)</option>
98 98 <option value="0c21004effeb8ce2d2d5b4a8baf6afa8394b6fbc">r60:0c21004effeb (web)</option>
99 99 <option value="a1f39c56d3f1d52d5fb5920370a2a2716cd9a444">r59:a1f39c56d3f1 (web)</option>
100 100 <option value="97d32df05c715a3bbf936bf3cc4e32fb77fe1a7f">r58:97d32df05c71 (web)</option>
101 101 <option value="08eaf14517718dccea4b67755a93368341aca919">r57:08eaf1451771 (web)</option>
102 102 <option value="22f71ad265265a53238359c883aa976e725aa07d">r56:22f71ad26526 (web)</option>
103 103 <option value="97501f02b7b4330924b647755663a2d90a5e638d">r49:97501f02b7b4 (web)</option>
104 104 <option value="86ede6754f2b27309452bb11f997386ae01d0e5a">r47:86ede6754f2b (web)</option>
105 105 <option value="014c40c0203c423dc19ecf94644f7cac9d4cdce0">r45:014c40c0203c (web)</option>
106 106 <option value="ee87846a61c12153b51543bf860e1026c6d3dcba">r30:ee87846a61c1 (default)</option>
107 107 <option value="9bb326a04ae5d98d437dece54be04f830cf1edd9">r26:9bb326a04ae5 (default)</option>
108 108 <option value="536c1a19428381cfea92ac44985304f6a8049569">r24:536c1a194283 (default)</option>
109 109 <option value="dc5d2c0661b61928834a785d3e64a3f80d3aad9c">r8:dc5d2c0661b6 (default)</option>
110 110 <option value="3803844fdbd3b711175fc3da9bdacfcd6d29a6fb">r7:3803844fdbd3 (default)</option>
111 111 </optgroup>
112 112 <optgroup label="Branches">
113 113 <option selected="selected" value="27cd5cce30c96924232dffcd24178a07ffeb5dfc">default</option>
114 114 <option value="97e8b885c04894463c51898e14387d80c30ed1ee">git</option>
115 115 <option value="2e6a2bf9356ca56df08807f4ad86d480da72a8f4">web</option>
116 116 </optgroup>
117 117 <optgroup label="Tags">
118 118 <option selected="selected" value="27cd5cce30c96924232dffcd24178a07ffeb5dfc">tip</option>
119 119 <option value="fd4bdb5e9b2a29b4393a4ac6caef48c17ee1a200">0.1.4</option>
120 120 <option value="17544fbfcd33ffb439e2b728b5d526b1ef30bfcf">0.1.3</option>
121 121 <option value="a7e60bff65d57ac3a1a1ce3b12a70f8a9e8a7720">0.1.2</option>
122 122 <option value="eb3a60fc964309c1a318b8dfe26aa2d1586c85ae">0.1.1</option>
123 123 </optgroup>
124 124 """)
125 125
126 126 response.mustcontain("""<div class="commit">merge</div>""")
127 127
128 128 response.mustcontain("""<span style="text-transform: uppercase;"><a href="#">branch: default</a></span>""")
129 129
130 130 def test_file_annotation(self):
131 131 self.log_user()
132 132 response = self.app.get(url(controller='files', action='index',
133 133 repo_name=HG_REPO,
134 134 revision='27cd5cce30c96924232dffcd24178a07ffeb5dfc',
135 135 f_path='vcs/nodes.py',
136 136 annotate=True))
137 137
138 138
139 139 response.mustcontain("""<optgroup label="Changesets">
140 140 <option value="8911406ad776fdd3d0b9932a2e89677e57405a48">r167:8911406ad776 (default)</option>
141 141 <option value="aa957ed78c35a1541f508d2ec90e501b0a9e3167">r165:aa957ed78c35 (default)</option>
142 142 <option value="48e11b73e94c0db33e736eaeea692f990cb0b5f1">r140:48e11b73e94c (default)</option>
143 143 <option value="adf3cbf483298563b968a6c673cd5bde5f7d5eea">r126:adf3cbf48329 (default)</option>
144 144 <option value="6249fd0fb2cfb1411e764129f598e2cf0de79a6f">r113:6249fd0fb2cf (git)</option>
145 145 <option value="75feb4c33e81186c87eac740cee2447330288412">r109:75feb4c33e81 (default)</option>
146 146 <option value="9a4dc232ecdc763ef2e98ae2238cfcbba4f6ad8d">r108:9a4dc232ecdc (default)</option>
147 147 <option value="595cce4efa21fda2f2e4eeb4fe5f2a6befe6fa2d">r107:595cce4efa21 (default)</option>
148 148 <option value="4a8bd421fbc2dfbfb70d85a3fe064075ab2c49da">r104:4a8bd421fbc2 (default)</option>
149 149 <option value="57be63fc8f85e65a0106a53187f7316f8c487ffa">r102:57be63fc8f85 (default)</option>
150 150 <option value="5530bd87f7e2e124a64d07cb2654c997682128be">r101:5530bd87f7e2 (git)</option>
151 151 <option value="e516008b1c93f142263dc4b7961787cbad654ce1">r99:e516008b1c93 (default)</option>
152 152 <option value="41f43fc74b8b285984554532eb105ac3be5c434f">r93:41f43fc74b8b (default)</option>
153 153 <option value="cc66b61b8455b264a7a8a2d8ddc80fcfc58c221e">r92:cc66b61b8455 (default)</option>
154 154 <option value="73ab5b616b3271b0518682fb4988ce421de8099f">r91:73ab5b616b32 (default)</option>
155 155 <option value="e0da75f308c0f18f98e9ce6257626009fdda2b39">r82:e0da75f308c0 (default)</option>
156 156 <option value="fb2e41e0f0810be4d7103bc2a4c7be16ee3ec611">r81:fb2e41e0f081 (default)</option>
157 157 <option value="602ae2f5e7ade70b3b66a58cdd9e3e613dc8a028">r76:602ae2f5e7ad (default)</option>
158 158 <option value="a066b25d5df7016b45a41b7e2a78c33b57adc235">r73:a066b25d5df7 (default)</option>
159 159 <option value="637a933c905958ce5151f154147c25c1c7b68832">r61:637a933c9059 (web)</option>
160 160 <option value="0c21004effeb8ce2d2d5b4a8baf6afa8394b6fbc">r60:0c21004effeb (web)</option>
161 161 <option value="a1f39c56d3f1d52d5fb5920370a2a2716cd9a444">r59:a1f39c56d3f1 (web)</option>
162 162 <option value="97d32df05c715a3bbf936bf3cc4e32fb77fe1a7f">r58:97d32df05c71 (web)</option>
163 163 <option value="08eaf14517718dccea4b67755a93368341aca919">r57:08eaf1451771 (web)</option>
164 164 <option value="22f71ad265265a53238359c883aa976e725aa07d">r56:22f71ad26526 (web)</option>
165 165 <option value="97501f02b7b4330924b647755663a2d90a5e638d">r49:97501f02b7b4 (web)</option>
166 166 <option value="86ede6754f2b27309452bb11f997386ae01d0e5a">r47:86ede6754f2b (web)</option>
167 167 <option value="014c40c0203c423dc19ecf94644f7cac9d4cdce0">r45:014c40c0203c (web)</option>
168 168 <option value="ee87846a61c12153b51543bf860e1026c6d3dcba">r30:ee87846a61c1 (default)</option>
169 169 <option value="9bb326a04ae5d98d437dece54be04f830cf1edd9">r26:9bb326a04ae5 (default)</option>
170 170 <option value="536c1a19428381cfea92ac44985304f6a8049569">r24:536c1a194283 (default)</option>
171 171 <option value="dc5d2c0661b61928834a785d3e64a3f80d3aad9c">r8:dc5d2c0661b6 (default)</option>
172 172 <option value="3803844fdbd3b711175fc3da9bdacfcd6d29a6fb">r7:3803844fdbd3 (default)</option>
173 173 </optgroup>
174 174 <optgroup label="Branches">
175 175 <option selected="selected" value="27cd5cce30c96924232dffcd24178a07ffeb5dfc">default</option>
176 176 <option value="97e8b885c04894463c51898e14387d80c30ed1ee">git</option>
177 177 <option value="2e6a2bf9356ca56df08807f4ad86d480da72a8f4">web</option>
178 178 </optgroup>
179 179 <optgroup label="Tags">
180 180 <option selected="selected" value="27cd5cce30c96924232dffcd24178a07ffeb5dfc">tip</option>
181 181 <option value="fd4bdb5e9b2a29b4393a4ac6caef48c17ee1a200">0.1.4</option>
182 182 <option value="17544fbfcd33ffb439e2b728b5d526b1ef30bfcf">0.1.3</option>
183 183 <option value="a7e60bff65d57ac3a1a1ce3b12a70f8a9e8a7720">0.1.2</option>
184 184 <option value="eb3a60fc964309c1a318b8dfe26aa2d1586c85ae">0.1.1</option>
185 185 </optgroup>""")
186 186
187 187 response.mustcontain("""<span style="text-transform: uppercase;"><a href="#">branch: default</a></span>""")
188 188
189 189 def test_archival(self):
190 190 self.log_user()
191 191
192 192 for arch_ext, info in ARCHIVE_SPECS.items():
193 193 short = '27cd5cce30c9%s' % arch_ext
194 194 fname = '27cd5cce30c96924232dffcd24178a07ffeb5dfc%s' % arch_ext
195 195 filename = '%s-%s' % (HG_REPO, short)
196 196 response = self.app.get(url(controller='files',
197 197 action='archivefile',
198 198 repo_name=HG_REPO,
199 199 fname=fname))
200 200
201 201 self.assertEqual(response.status, '200 OK')
202 self.assertEqual(response.response._headers.items(),
203 [('Pragma', 'no-cache'),
204 ('Cache-Control', 'no-cache'),
205 ('Content-Type', '%s; charset=utf-8' % info[0]),
206 ('Content-Disposition', 'attachment; filename=%s' % filename),
207 ]
208 )
202 heads = [
203 ('Content-Type', 'text/html; charset=utf-8'),
204 ('Content-Length', '0'), ('Pragma', 'no-cache'),
205 ('Cache-Control', 'no-cache')
206 ]
207 self.assertEqual(response.response._headers.items(), heads)
209 208
210 209 def test_archival_wrong_ext(self):
211 210 self.log_user()
212 211
213 212 for arch_ext in ['tar', 'rar', 'x', '..ax', '.zipz']:
214 213 fname = '27cd5cce30c96924232dffcd24178a07ffeb5dfc%s' % arch_ext
215 214
216 215 response = self.app.get(url(controller='files', action='archivefile',
217 216 repo_name=HG_REPO,
218 217 fname=fname))
219 218 response.mustcontain('Unknown archive type')
220 219
221 220 def test_archival_wrong_revision(self):
222 221 self.log_user()
223 222
224 223 for rev in ['00x000000', 'tar', 'wrong', '@##$@$424213232', '232dffcd']:
225 224 fname = '%s.zip' % rev
226 225
227 226 response = self.app.get(url(controller='files', action='archivefile',
228 227 repo_name=HG_REPO,
229 228 fname=fname))
230 229 response.mustcontain('Unknown revision')
231 230
232 231 #==========================================================================
233 232 # RAW FILE
234 233 #==========================================================================
235 234 def test_raw_file_ok(self):
236 235 self.log_user()
237 236 response = self.app.get(url(controller='files', action='rawfile',
238 237 repo_name=HG_REPO,
239 238 revision='27cd5cce30c96924232dffcd24178a07ffeb5dfc',
240 239 f_path='vcs/nodes.py'))
241 240
242 241 self.assertEqual(response.content_disposition, "attachment; filename=nodes.py")
243 242 self.assertEqual(response.content_type, "text/x-python")
244 243
245 244 def test_raw_file_wrong_cs(self):
246 245 self.log_user()
247 246 rev = u'ERRORce30c96924232dffcd24178a07ffeb5dfc'
248 247 f_path = 'vcs/nodes.py'
249 248
250 249 response = self.app.get(url(controller='files', action='rawfile',
251 250 repo_name=HG_REPO,
252 251 revision=rev,
253 252 f_path=f_path))
254 253
255 254 msg = """Revision %r does not exist for this repository""" % (rev)
256 255 self.checkSessionFlash(response, msg)
257 256
258 257 msg = """%s""" % (HG_REPO)
259 258 self.checkSessionFlash(response, msg)
260 259
261 260 def test_raw_file_wrong_f_path(self):
262 261 self.log_user()
263 262 rev = '27cd5cce30c96924232dffcd24178a07ffeb5dfc'
264 263 f_path = 'vcs/ERRORnodes.py'
265 264 response = self.app.get(url(controller='files', action='rawfile',
266 265 repo_name=HG_REPO,
267 266 revision=rev,
268 267 f_path=f_path))
269 268
270 269 msg = "There is no file nor directory at the given path: %r at revision %r" % (f_path, rev[:12])
271 270 self.checkSessionFlash(response, msg)
272 271
273 272 #==========================================================================
274 273 # RAW RESPONSE - PLAIN
275 274 #==========================================================================
276 275 def test_raw_ok(self):
277 276 self.log_user()
278 277 response = self.app.get(url(controller='files', action='raw',
279 278 repo_name=HG_REPO,
280 279 revision='27cd5cce30c96924232dffcd24178a07ffeb5dfc',
281 280 f_path='vcs/nodes.py'))
282 281
283 282 self.assertEqual(response.content_type, "text/plain")
284 283
285 284 def test_raw_wrong_cs(self):
286 285 self.log_user()
287 286 rev = u'ERRORcce30c96924232dffcd24178a07ffeb5dfc'
288 287 f_path = 'vcs/nodes.py'
289 288
290 289 response = self.app.get(url(controller='files', action='raw',
291 290 repo_name=HG_REPO,
292 291 revision=rev,
293 292 f_path=f_path))
294 293 msg = """Revision %r does not exist for this repository""" % (rev)
295 294 self.checkSessionFlash(response, msg)
296 295
297 296 msg = """%s""" % (HG_REPO)
298 297 self.checkSessionFlash(response, msg)
299 298
300 299 def test_raw_wrong_f_path(self):
301 300 self.log_user()
302 301 rev = '27cd5cce30c96924232dffcd24178a07ffeb5dfc'
303 302 f_path = 'vcs/ERRORnodes.py'
304 303 response = self.app.get(url(controller='files', action='raw',
305 304 repo_name=HG_REPO,
306 305 revision=rev,
307 306 f_path=f_path))
308 307 msg = "There is no file nor directory at the given path: %r at revision %r" % (f_path, rev[:12])
309 308 self.checkSessionFlash(response, msg)
310 309
311 310 def test_ajaxed_files_list(self):
312 311 self.log_user()
313 312 rev = '27cd5cce30c96924232dffcd24178a07ffeb5dfc'
314 313 response = self.app.get(
315 314 url('files_nodelist_home', repo_name=HG_REPO,f_path='/',revision=rev),
316 315 extra_environ={'HTTP_X_PARTIAL_XHR': '1'},
317 316 )
318 317 response.mustcontain("vcs/web/simplevcs/views/repository.py")
General Comments 0
You need to be logged in to leave comments. Login now