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