##// END OF EJS Templates
fix archive download for git
marcink -
r1809:ef0066e9 beta
parent child Browse files
Show More
@@ -1,509 +1,510 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) 2009-2011 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
26 26 import os
27 27 import logging
28 28 import traceback
29 29
30 30 from pylons import request, response, tmpl_context as c, url
31 31 from pylons.i18n.translation import _
32 32 from pylons.controllers.util import redirect
33 33 from pylons.decorators import jsonify
34 34
35 35 from vcs.conf import settings
36 36 from vcs.exceptions import RepositoryError, ChangesetDoesNotExistError, \
37 37 EmptyRepositoryError, ImproperArchiveTypeError, VCSError, \
38 38 NodeAlreadyExistsError
39 39 from vcs.nodes import FileNode
40 40
41 41 from rhodecode.lib.compat import OrderedDict
42 42 from rhodecode.lib import convert_line_endings, detect_mode, safe_str
43 43 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
44 44 from rhodecode.lib.base import BaseRepoController, render
45 45 from rhodecode.lib.utils import EmptyChangeset
46 46 from rhodecode.lib import diffs
47 47 import rhodecode.lib.helpers as h
48 48 from rhodecode.model.repo import RepoModel
49 49 from rhodecode.controllers.changeset import anchor_url, _ignorews_url,\
50 50 _context_url, get_line_ctx, get_ignore_ws
51 51 from rhodecode.lib.diffs import wrapped_diff
52 52
53 53 log = logging.getLogger(__name__)
54 54
55 55
56 56 class FilesController(BaseRepoController):
57 57
58 58 @LoginRequired()
59 59 def __before__(self):
60 60 super(FilesController, self).__before__()
61 61 c.cut_off_limit = self.cut_off_limit
62 62
63 63 def __get_cs_or_redirect(self, rev, repo_name, redirect_after=True):
64 64 """
65 65 Safe way to get changeset if error occur it redirects to tip with
66 66 proper message
67 67
68 68 :param rev: revision to fetch
69 69 :param repo_name: repo name to redirect after
70 70 """
71 71
72 72 try:
73 73 return c.rhodecode_repo.get_changeset(rev)
74 74 except EmptyRepositoryError, e:
75 75 if not redirect_after:
76 76 return None
77 77 url_ = url('files_add_home',
78 78 repo_name=c.repo_name,
79 79 revision=0, f_path='')
80 80 add_new = '<a href="%s">[%s]</a>' % (url_, _('add new'))
81 81 h.flash(h.literal(_('There are no files yet %s' % add_new)),
82 82 category='warning')
83 83 redirect(h.url('summary_home', repo_name=repo_name))
84 84
85 85 except RepositoryError, e:
86 86 h.flash(str(e), category='warning')
87 87 redirect(h.url('files_home', repo_name=repo_name, revision='tip'))
88 88
89 89 def __get_filenode_or_redirect(self, repo_name, cs, path):
90 90 """
91 91 Returns file_node, if error occurs or given path is directory,
92 92 it'll redirect to top level path
93 93
94 94 :param repo_name: repo_name
95 95 :param cs: given changeset
96 96 :param path: path to lookup
97 97 """
98 98
99 99 try:
100 100 file_node = cs.get_node(path)
101 101 if file_node.is_dir():
102 102 raise RepositoryError('given path is a directory')
103 103 except RepositoryError, e:
104 104 h.flash(str(e), category='warning')
105 105 redirect(h.url('files_home', repo_name=repo_name,
106 106 revision=cs.raw_id))
107 107
108 108 return file_node
109 109
110 110 def __get_paths(self, changeset, starting_path):
111 111 """recursive walk in root dir and return a set of all path in that dir
112 112 based on repository walk function
113 113 """
114 114 _files = list()
115 115 _dirs = list()
116 116
117 117 try:
118 118 tip = changeset
119 119 for topnode, dirs, files in tip.walk(starting_path):
120 120 for f in files:
121 121 _files.append(f.path)
122 122 for d in dirs:
123 123 _dirs.append(d.path)
124 124 except RepositoryError, e:
125 125 log.debug(traceback.format_exc())
126 126 pass
127 127 return _dirs, _files
128 128
129 129 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
130 130 'repository.admin')
131 131 def index(self, repo_name, revision, f_path):
132 132 # redirect to given revision from form if given
133 133 post_revision = request.POST.get('at_rev', None)
134 134 if post_revision:
135 135 cs = self.__get_cs_or_redirect(post_revision, repo_name)
136 136 redirect(url('files_home', repo_name=c.repo_name,
137 137 revision=cs.raw_id, f_path=f_path))
138 138
139 139 c.changeset = self.__get_cs_or_redirect(revision, repo_name)
140 140 c.branch = request.GET.get('branch', None)
141 141 c.f_path = f_path
142 142
143 143 cur_rev = c.changeset.revision
144 144
145 145 # prev link
146 146 try:
147 147 prev_rev = c.rhodecode_repo.get_changeset(cur_rev).prev(c.branch)
148 148 c.url_prev = url('files_home', repo_name=c.repo_name,
149 149 revision=prev_rev.raw_id, f_path=f_path)
150 150 if c.branch:
151 151 c.url_prev += '?branch=%s' % c.branch
152 152 except (ChangesetDoesNotExistError, VCSError):
153 153 c.url_prev = '#'
154 154
155 155 # next link
156 156 try:
157 157 next_rev = c.rhodecode_repo.get_changeset(cur_rev).next(c.branch)
158 158 c.url_next = url('files_home', repo_name=c.repo_name,
159 159 revision=next_rev.raw_id, f_path=f_path)
160 160 if c.branch:
161 161 c.url_next += '?branch=%s' % c.branch
162 162 except (ChangesetDoesNotExistError, VCSError):
163 163 c.url_next = '#'
164 164
165 165 # files or dirs
166 166 try:
167 167 c.file = c.changeset.get_node(f_path)
168 168
169 169 if c.file.is_file():
170 170 c.file_history = self._get_node_history(c.changeset, f_path)
171 171 else:
172 172 c.file_history = []
173 173 except RepositoryError, e:
174 174 h.flash(str(e), category='warning')
175 175 redirect(h.url('files_home', repo_name=repo_name,
176 176 revision=revision))
177 177
178 178 return render('files/files.html')
179 179
180 180 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
181 181 'repository.admin')
182 182 def rawfile(self, repo_name, revision, f_path):
183 183 cs = self.__get_cs_or_redirect(revision, repo_name)
184 184 file_node = self.__get_filenode_or_redirect(repo_name, cs, f_path)
185 185
186 186 response.content_disposition = 'attachment; filename=%s' % \
187 187 safe_str(f_path.split(os.sep)[-1])
188 188
189 189 response.content_type = file_node.mimetype
190 190 return file_node.content
191 191
192 192 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
193 193 'repository.admin')
194 194 def raw(self, repo_name, revision, f_path):
195 195 cs = self.__get_cs_or_redirect(revision, repo_name)
196 196 file_node = self.__get_filenode_or_redirect(repo_name, cs, f_path)
197 197
198 198 raw_mimetype_mapping = {
199 199 # map original mimetype to a mimetype used for "show as raw"
200 200 # you can also provide a content-disposition to override the
201 201 # default "attachment" disposition.
202 202 # orig_type: (new_type, new_dispo)
203 203
204 204 # show images inline:
205 205 'image/x-icon': ('image/x-icon', 'inline'),
206 206 'image/png': ('image/png', 'inline'),
207 207 'image/gif': ('image/gif', 'inline'),
208 208 'image/jpeg': ('image/jpeg', 'inline'),
209 209 'image/svg+xml': ('image/svg+xml', 'inline'),
210 210 }
211 211
212 212 mimetype = file_node.mimetype
213 213 try:
214 214 mimetype, dispo = raw_mimetype_mapping[mimetype]
215 215 except KeyError:
216 216 # we don't know anything special about this, handle it safely
217 217 if file_node.is_binary:
218 218 # do same as download raw for binary files
219 219 mimetype, dispo = 'application/octet-stream', 'attachment'
220 220 else:
221 221 # do not just use the original mimetype, but force text/plain,
222 222 # otherwise it would serve text/html and that might be unsafe.
223 223 # Note: underlying vcs library fakes text/plain mimetype if the
224 224 # mimetype can not be determined and it thinks it is not
225 225 # binary.This might lead to erroneous text display in some
226 226 # cases, but helps in other cases, like with text files
227 227 # without extension.
228 228 mimetype, dispo = 'text/plain', 'inline'
229 229
230 230 if dispo == 'attachment':
231 231 dispo = 'attachment; filename=%s' % \
232 232 safe_str(f_path.split(os.sep)[-1])
233 233
234 234 response.content_disposition = dispo
235 235 response.content_type = mimetype
236 236 return file_node.content
237 237
238 238 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
239 239 'repository.admin')
240 240 def annotate(self, repo_name, revision, f_path):
241 241 c.cs = self.__get_cs_or_redirect(revision, repo_name)
242 242 c.file = self.__get_filenode_or_redirect(repo_name, c.cs, f_path)
243 243
244 244 c.file_history = self._get_node_history(c.cs, f_path)
245 245 c.f_path = f_path
246 246 return render('files/files_annotate.html')
247 247
248 248 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
249 249 def edit(self, repo_name, revision, f_path):
250 250 r_post = request.POST
251 251
252 252 c.cs = self.__get_cs_or_redirect(revision, repo_name)
253 253 c.file = self.__get_filenode_or_redirect(repo_name, c.cs, f_path)
254 254
255 255 if c.file.is_binary:
256 256 return redirect(url('files_home', repo_name=c.repo_name,
257 257 revision=c.cs.raw_id, f_path=f_path))
258 258
259 259 c.f_path = f_path
260 260
261 261 if r_post:
262 262
263 263 old_content = c.file.content
264 264 sl = old_content.splitlines(1)
265 265 first_line = sl[0] if sl else ''
266 266 # modes: 0 - Unix, 1 - Mac, 2 - DOS
267 267 mode = detect_mode(first_line, 0)
268 268 content = convert_line_endings(r_post.get('content'), mode)
269 269
270 270 message = r_post.get('message') or (_('Edited %s via RhodeCode')
271 271 % (f_path))
272 272 author = self.rhodecode_user.full_contact
273 273
274 274 if content == old_content:
275 275 h.flash(_('No changes'),
276 276 category='warning')
277 277 return redirect(url('changeset_home', repo_name=c.repo_name,
278 278 revision='tip'))
279 279
280 280 try:
281 281 self.scm_model.commit_change(repo=c.rhodecode_repo,
282 282 repo_name=repo_name, cs=c.cs,
283 283 user=self.rhodecode_user,
284 284 author=author, message=message,
285 285 content=content, f_path=f_path)
286 286 h.flash(_('Successfully committed to %s' % f_path),
287 287 category='success')
288 288
289 289 except Exception:
290 290 log.error(traceback.format_exc())
291 291 h.flash(_('Error occurred during commit'), category='error')
292 292 return redirect(url('changeset_home',
293 293 repo_name=c.repo_name, revision='tip'))
294 294
295 295 return render('files/files_edit.html')
296 296
297 297 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
298 298 def add(self, repo_name, revision, f_path):
299 299 r_post = request.POST
300 300 c.cs = self.__get_cs_or_redirect(revision, repo_name,
301 301 redirect_after=False)
302 302 if c.cs is None:
303 303 c.cs = EmptyChangeset(alias=c.rhodecode_repo.alias)
304 304
305 305 c.f_path = f_path
306 306
307 307 if r_post:
308 308 unix_mode = 0
309 309 content = convert_line_endings(r_post.get('content'), unix_mode)
310 310
311 311 message = r_post.get('message') or (_('Added %s via RhodeCode')
312 312 % (f_path))
313 313 location = r_post.get('location')
314 314 filename = r_post.get('filename')
315 315 file_obj = r_post.get('upload_file', None)
316 316
317 317 if file_obj is not None and hasattr(file_obj, 'filename'):
318 318 filename = file_obj.filename
319 319 content = file_obj.file
320 320
321 321 node_path = os.path.join(location, filename)
322 322 author = self.rhodecode_user.full_contact
323 323
324 324 if not content:
325 325 h.flash(_('No content'), category='warning')
326 326 return redirect(url('changeset_home', repo_name=c.repo_name,
327 327 revision='tip'))
328 328 if not filename:
329 329 h.flash(_('No filename'), category='warning')
330 330 return redirect(url('changeset_home', repo_name=c.repo_name,
331 331 revision='tip'))
332 332
333 333 try:
334 334 self.scm_model.create_node(repo=c.rhodecode_repo,
335 335 repo_name=repo_name, cs=c.cs,
336 336 user=self.rhodecode_user,
337 337 author=author, message=message,
338 338 content=content, f_path=node_path)
339 339 h.flash(_('Successfully committed to %s' % node_path),
340 340 category='success')
341 341 except NodeAlreadyExistsError, e:
342 342 h.flash(_(e), category='error')
343 343 except Exception:
344 344 log.error(traceback.format_exc())
345 345 h.flash(_('Error occurred during commit'), category='error')
346 346 return redirect(url('changeset_home',
347 347 repo_name=c.repo_name, revision='tip'))
348 348
349 349 return render('files/files_add.html')
350 350
351 351 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
352 352 'repository.admin')
353 353 def archivefile(self, repo_name, fname):
354 354
355 355 fileformat = None
356 356 revision = None
357 357 ext = None
358 358 subrepos = request.GET.get('subrepos') == 'true'
359 359
360 360 for a_type, ext_data in settings.ARCHIVE_SPECS.items():
361 361 archive_spec = fname.split(ext_data[1])
362 362 if len(archive_spec) == 2 and archive_spec[1] == '':
363 363 fileformat = a_type or ext_data[1]
364 364 revision = archive_spec[0]
365 365 ext = ext_data[1]
366 366
367 367 try:
368 368 dbrepo = RepoModel().get_by_repo_name(repo_name)
369 369 if dbrepo.enable_downloads is False:
370 370 return _('downloads disabled')
371 371
372 if c.rhodecode_repo.alias == 'hg':
372 373 # patch and reset hooks section of UI config to not run any
373 374 # hooks on fetching archives with subrepos
374 375 for k, v in c.rhodecode_repo._repo.ui.configitems('hooks'):
375 376 c.rhodecode_repo._repo.ui.setconfig('hooks', k, None)
376 377
377 378 cs = c.rhodecode_repo.get_changeset(revision)
378 379 content_type = settings.ARCHIVE_SPECS[fileformat][0]
379 380 except ChangesetDoesNotExistError:
380 381 return _('Unknown revision %s') % revision
381 382 except EmptyRepositoryError:
382 383 return _('Empty repository')
383 384 except (ImproperArchiveTypeError, KeyError):
384 385 return _('Unknown archive type')
385 386
386 387 response.content_type = content_type
387 388 response.content_disposition = 'attachment; filename=%s-%s%s' \
388 389 % (repo_name, revision, ext)
389 390
390 391 import tempfile
391 392 archive = tempfile.mkstemp()[1]
392 393 t = open(archive, 'wb')
393 394 cs.fill_archive(stream=t, kind=fileformat, subrepos=subrepos)
394 395
395 396 def get_chunked_archive(archive):
396 397 stream = open(archive, 'rb')
397 398 while True:
398 399 data = stream.read(4096)
399 400 if not data:
400 401 os.remove(archive)
401 402 break
402 403 yield data
403 404
404 405 return get_chunked_archive(archive)
405 406
406 407 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
407 408 'repository.admin')
408 409 def diff(self, repo_name, f_path):
409 410 ignore_whitespace = request.GET.get('ignorews') == '1'
410 411 line_context = request.GET.get('context', 3)
411 412 diff1 = request.GET.get('diff1', '')
412 413 diff2 = request.GET.get('diff2', '')
413 414 c.action = request.GET.get('diff')
414 415 c.no_changes = diff1 == diff2
415 416 c.f_path = f_path
416 417 c.big_diff = False
417 418 c.anchor_url = anchor_url
418 419 c.ignorews_url = _ignorews_url
419 420 c.context_url = _context_url
420 421 c.changes = OrderedDict()
421 422 c.changes[diff2] = []
422 423 try:
423 424 if diff1 not in ['', None, 'None', '0' * 12, '0' * 40]:
424 425 c.changeset_1 = c.rhodecode_repo.get_changeset(diff1)
425 426 node1 = c.changeset_1.get_node(f_path)
426 427 else:
427 428 c.changeset_1 = EmptyChangeset(repo=c.rhodecode_repo)
428 429 node1 = FileNode('.', '', changeset=c.changeset_1)
429 430
430 431 if diff2 not in ['', None, 'None', '0' * 12, '0' * 40]:
431 432 c.changeset_2 = c.rhodecode_repo.get_changeset(diff2)
432 433 node2 = c.changeset_2.get_node(f_path)
433 434 else:
434 435 c.changeset_2 = EmptyChangeset(repo=c.rhodecode_repo)
435 436 node2 = FileNode('.', '', changeset=c.changeset_2)
436 437 except RepositoryError:
437 438 return redirect(url('files_home', repo_name=c.repo_name,
438 439 f_path=f_path))
439 440
440 441 if c.action == 'download':
441 442 _diff = diffs.get_gitdiff(node1, node2,
442 443 ignore_whitespace=ignore_whitespace,
443 444 context=line_context)
444 445 diff = diffs.DiffProcessor(_diff, format='gitdiff')
445 446
446 447 diff_name = '%s_vs_%s.diff' % (diff1, diff2)
447 448 response.content_type = 'text/plain'
448 449 response.content_disposition = 'attachment; filename=%s' \
449 450 % diff_name
450 451 return diff.raw_diff()
451 452
452 453 elif c.action == 'raw':
453 454 _diff = diffs.get_gitdiff(node1, node2,
454 455 ignore_whitespace=ignore_whitespace,
455 456 context=line_context)
456 457 diff = diffs.DiffProcessor(_diff, format='gitdiff')
457 458 response.content_type = 'text/plain'
458 459 return diff.raw_diff()
459 460
460 461 else:
461 462 fid = h.FID(diff2, node2.path)
462 463 line_context_lcl = get_line_ctx(fid, request.GET)
463 464 ign_whitespace_lcl = get_ignore_ws(fid, request.GET)
464 465
465 466 lim = request.GET.get('fulldiff') or self.cut_off_limit
466 467 _, cs1, cs2, diff, st = wrapped_diff(filenode_old=node1,
467 468 filenode_new=node2,
468 469 cut_off_limit=lim,
469 470 ignore_whitespace=ign_whitespace_lcl,
470 471 line_context=line_context_lcl,
471 472 enable_comments=False)
472 473
473 474 c.changes = [('', node2, diff, cs1, cs2, st,)]
474 475
475 476 return render('files/file_diff.html')
476 477
477 478 def _get_node_history(self, cs, f_path):
478 479 changesets = cs.get_file_history(f_path)
479 480 hist_l = []
480 481
481 482 changesets_group = ([], _("Changesets"))
482 483 branches_group = ([], _("Branches"))
483 484 tags_group = ([], _("Tags"))
484 485
485 486 for chs in changesets:
486 487 n_desc = 'r%s:%s' % (chs.revision, chs.short_id)
487 488 changesets_group[0].append((chs.raw_id, n_desc,))
488 489
489 490 hist_l.append(changesets_group)
490 491
491 492 for name, chs in c.rhodecode_repo.branches.items():
492 493 branches_group[0].append((chs, name),)
493 494 hist_l.append(branches_group)
494 495
495 496 for name, chs in c.rhodecode_repo.tags.items():
496 497 tags_group[0].append((chs, name),)
497 498 hist_l.append(tags_group)
498 499
499 500 return hist_l
500 501
501 502 @jsonify
502 503 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
503 504 'repository.admin')
504 505 def nodelist(self, repo_name, revision, f_path):
505 506 if request.environ.get('HTTP_X_PARTIAL_XHR'):
506 507 cs = self.__get_cs_or_redirect(revision, repo_name)
507 508 _d, _f = self.__get_paths(cs, f_path)
508 509 return _d + _f
509 510
General Comments 0
You need to be logged in to leave comments. Login now