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