##// END OF EJS Templates
changes for archivals in rhodecode. Also made it work for git that way
marcink -
r1308:73b2fc32 beta
parent child Browse files
Show More
@@ -1,406 +1,420 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 mimetypes
29 29 import traceback
30 30
31 31 from pylons import request, response, session, tmpl_context as c, url
32 32 from pylons.i18n.translation import _
33 33 from pylons.controllers.util import redirect
34 34
35 35 from vcs.backends import ARCHIVE_SPECS
36 36 from vcs.exceptions import RepositoryError, ChangesetDoesNotExistError, \
37 37 EmptyRepositoryError, ImproperArchiveTypeError, VCSError
38 38 from vcs.nodes import FileNode, NodeKind
39 39 from vcs.utils import diffs as differ
40 40
41 41 from rhodecode.lib import convert_line_endings, detect_mode
42 42 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
43 43 from rhodecode.lib.base import BaseRepoController, render
44 44 from rhodecode.lib.utils import EmptyChangeset
45 45 import rhodecode.lib.helpers as h
46 46 from rhodecode.model.repo import RepoModel
47 47
48 48 log = logging.getLogger(__name__)
49 49
50 50
51 51 class FilesController(BaseRepoController):
52 52
53 53 @LoginRequired()
54 54 def __before__(self):
55 55 super(FilesController, self).__before__()
56 56 c.cut_off_limit = self.cut_off_limit
57 57
58 58 def __get_cs_or_redirect(self, rev, repo_name):
59 59 """
60 60 Safe way to get changeset if error occur it redirects to tip with
61 61 proper message
62 62
63 63 :param rev: revision to fetch
64 64 :param repo_name: repo name to redirect after
65 65 """
66 66
67 67 try:
68 68 return c.rhodecode_repo.get_changeset(rev)
69 69 except EmptyRepositoryError, e:
70 70 h.flash(_('There are no files yet'), category='warning')
71 71 redirect(h.url('summary_home', repo_name=repo_name))
72 72
73 73 except RepositoryError, e:
74 74 h.flash(str(e), category='warning')
75 75 redirect(h.url('files_home', repo_name=repo_name, revision='tip'))
76 76
77 77 def __get_filenode_or_redirect(self, repo_name, cs, path):
78 78 """
79 79 Returns file_node, if error occurs or given path is directory,
80 80 it'll redirect to top level path
81 81
82 82 :param repo_name: repo_name
83 83 :param cs: given changeset
84 84 :param path: path to lookup
85 85 """
86 86
87 87 try:
88 88 file_node = cs.get_node(path)
89 89 if file_node.is_dir():
90 90 raise RepositoryError('given path is a directory')
91 91 except RepositoryError, e:
92 92 h.flash(str(e), category='warning')
93 93 redirect(h.url('files_home', repo_name=repo_name,
94 94 revision=cs.raw_id))
95 95
96 96 return file_node
97 97
98 98 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
99 99 'repository.admin')
100 100 def index(self, repo_name, revision, f_path):
101 101 #reditect to given revision from form if given
102 102 post_revision = request.POST.get('at_rev', None)
103 103 if post_revision:
104 104 cs = self.__get_cs_or_redirect(post_revision, repo_name)
105 105 redirect(url('files_home', repo_name=c.repo_name,
106 106 revision=cs.raw_id, f_path=f_path))
107 107
108 108 c.changeset = self.__get_cs_or_redirect(revision, repo_name)
109 109 c.branch = request.GET.get('branch', None)
110 110 c.f_path = f_path
111 111
112 112 cur_rev = c.changeset.revision
113 113
114 114 #prev link
115 115 try:
116 116 prev_rev = c.rhodecode_repo.get_changeset(cur_rev).prev(c.branch)
117 117 c.url_prev = url('files_home', repo_name=c.repo_name,
118 118 revision=prev_rev.raw_id, f_path=f_path)
119 119 if c.branch:
120 120 c.url_prev += '?branch=%s' % c.branch
121 121 except (ChangesetDoesNotExistError, VCSError):
122 122 c.url_prev = '#'
123 123
124 124 #next link
125 125 try:
126 126 next_rev = c.rhodecode_repo.get_changeset(cur_rev).next(c.branch)
127 127 c.url_next = url('files_home', repo_name=c.repo_name,
128 128 revision=next_rev.raw_id, f_path=f_path)
129 129 if c.branch:
130 130 c.url_next += '?branch=%s' % c.branch
131 131 except (ChangesetDoesNotExistError, VCSError):
132 132 c.url_next = '#'
133 133
134 134 #files or dirs
135 135 try:
136 136 c.files_list = c.changeset.get_node(f_path)
137 137
138 138 if c.files_list.is_file():
139 139 c.file_history = self._get_node_history(c.changeset, f_path)
140 140 else:
141 141 c.file_history = []
142 142 except RepositoryError, e:
143 143 h.flash(str(e), category='warning')
144 144 redirect(h.url('files_home', repo_name=repo_name,
145 145 revision=revision))
146 146
147 147 return render('files/files.html')
148 148
149 149 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
150 150 'repository.admin')
151 151 def rawfile(self, repo_name, revision, f_path):
152 152 cs = self.__get_cs_or_redirect(revision, repo_name)
153 153 file_node = self.__get_filenode_or_redirect(repo_name, cs, f_path)
154 154
155 155 response.content_disposition = 'attachment; filename=%s' % \
156 156 f_path.split(os.sep)[-1].encode('utf8', 'replace')
157 157
158 158 response.content_type = file_node.mimetype
159 159 return file_node.content
160 160
161 161 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
162 162 'repository.admin')
163 163 def raw(self, repo_name, revision, f_path):
164 164 cs = self.__get_cs_or_redirect(revision, repo_name)
165 165 file_node = self.__get_filenode_or_redirect(repo_name, cs, f_path)
166 166
167 167 raw_mimetype_mapping = {
168 168 # map original mimetype to a mimetype used for "show as raw"
169 169 # you can also provide a content-disposition to override the
170 170 # default "attachment" disposition.
171 171 # orig_type: (new_type, new_dispo)
172 172
173 173 # show images inline:
174 174 'image/x-icon': ('image/x-icon', 'inline'),
175 175 'image/png': ('image/png', 'inline'),
176 176 'image/gif': ('image/gif', 'inline'),
177 177 'image/jpeg': ('image/jpeg', 'inline'),
178 178 'image/svg+xml': ('image/svg+xml', 'inline'),
179 179 }
180 180
181 181 mimetype = file_node.mimetype
182 182 try:
183 183 mimetype, dispo = raw_mimetype_mapping[mimetype]
184 184 except KeyError:
185 185 # we don't know anything special about this, handle it safely
186 186 if file_node.is_binary:
187 187 # do same as download raw for binary files
188 188 mimetype, dispo = 'application/octet-stream', 'attachment'
189 189 else:
190 190 # do not just use the original mimetype, but force text/plain,
191 191 # otherwise it would serve text/html and that might be unsafe.
192 192 # Note: underlying vcs library fakes text/plain mimetype if the
193 193 # mimetype can not be determined and it thinks it is not
194 194 # binary.This might lead to erroneous text display in some
195 195 # cases, but helps in other cases, like with text files
196 196 # without extension.
197 197 mimetype, dispo = 'text/plain', 'inline'
198 198
199 199 if dispo == 'attachment':
200 200 dispo = 'attachment; filename=%s' % \
201 201 f_path.split(os.sep)[-1].encode('utf8', 'replace')
202 202
203 203 response.content_disposition = dispo
204 204 response.content_type = mimetype
205 205 return file_node.content
206 206
207 207 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
208 208 'repository.admin')
209 209 def annotate(self, repo_name, revision, f_path):
210 210 c.cs = self.__get_cs_or_redirect(revision, repo_name)
211 211 c.file = self.__get_filenode_or_redirect(repo_name, c.cs, f_path)
212 212
213 213 c.file_history = self._get_node_history(c.cs, f_path)
214 214 c.f_path = f_path
215 215 return render('files/files_annotate.html')
216 216
217 217 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
218 218 def edit(self, repo_name, revision, f_path):
219 219 r_post = request.POST
220 220
221 221 if c.rhodecode_repo.alias == 'hg':
222 222 from vcs.backends.hg import MercurialInMemoryChangeset as IMC
223 223 elif c.rhodecode_repo.alias == 'git':
224 224 from vcs.backends.git import GitInMemoryChangeset as IMC
225 225
226 226 c.cs = self.__get_cs_or_redirect(revision, repo_name)
227 227 c.file = self.__get_filenode_or_redirect(repo_name, c.cs, f_path)
228 228
229 229 c.file_history = self._get_node_history(c.cs, f_path)
230 230 c.f_path = f_path
231 231
232 232 if r_post:
233 233
234 234 old_content = c.file.content
235 235 sl = old_content.splitlines(1)
236 236 first_line = sl[0] if sl else ''
237 237 # modes: 0 - Unix, 1 - Mac, 2 - DOS
238 238 mode = detect_mode(first_line, 0)
239 239 content = convert_line_endings(r_post.get('content'), mode)
240 240
241 241 message = r_post.get('message') or (_('Edited %s via RhodeCode')
242 242 % (f_path))
243 243
244 244 if content == old_content:
245 245 h.flash(_('No changes'),
246 246 category='warning')
247 247 return redirect(url('changeset_home', repo_name=c.repo_name,
248 248 revision='tip'))
249 249
250 250 try:
251 251 # decoding here will force that we have proper encoded values
252 252 # in any other case this will throw exceptions and deny commit
253 253 content = content.encode('utf8')
254 254 message = message.encode('utf8')
255 255 path = f_path.encode('utf8')
256 256 author = self.rhodecode_user.full_contact.encode('utf8')
257 257 m = IMC(c.rhodecode_repo)
258 258 m.change(FileNode(path, content))
259 259 m.commit(message=message,
260 260 author=author,
261 261 parents=[c.cs], branch=c.cs.branch)
262 262 h.flash(_('Successfully committed to %s' % f_path),
263 263 category='success')
264 264
265 265 except Exception, e:
266 266 log.error(traceback.format_exc())
267 267 h.flash(_('Error occurred during commit'), category='error')
268 268 return redirect(url('changeset_home',
269 269 repo_name=c.repo_name, revision='tip'))
270 270
271 271 return render('files/files_edit.html')
272 272
273 273 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
274 274 'repository.admin')
275 275 def archivefile(self, repo_name, fname):
276 276
277 277 fileformat = None
278 278 revision = None
279 279 ext = None
280 280
281 281 for a_type, ext_data in ARCHIVE_SPECS.items():
282 282 archive_spec = fname.split(ext_data[1])
283 283 if len(archive_spec) == 2 and archive_spec[1] == '':
284 284 fileformat = a_type or ext_data[1]
285 285 revision = archive_spec[0]
286 286 ext = ext_data[1]
287 287
288 288 try:
289 289 dbrepo = RepoModel().get_by_repo_name(repo_name)
290 290 if dbrepo.enable_downloads is False:
291 291 return _('downloads disabled')
292 292
293 293 cs = c.rhodecode_repo.get_changeset(revision)
294 294 content_type = ARCHIVE_SPECS[fileformat][0]
295 295 except ChangesetDoesNotExistError:
296 296 return _('Unknown revision %s') % revision
297 297 except EmptyRepositoryError:
298 298 return _('Empty repository')
299 299 except (ImproperArchiveTypeError, KeyError):
300 300 return _('Unknown archive type')
301 301
302 302 response.content_type = content_type
303 303 response.content_disposition = 'attachment; filename=%s-%s%s' \
304 304 % (repo_name, revision, ext)
305 305
306 return cs.get_chunked_archive(stream=None, kind=fileformat)
306 import tempfile
307 archive = tempfile.mkstemp()[1]
308 t = open(archive, 'wb')
309 cs.fill_archive(stream=t, kind=fileformat)
310
311 def get_chunked_archive(archive):
312 stream = open(archive, 'rb')
313 while True:
314 data = stream.read(4096)
315 if not data:
316 os.remove(archive)
317 break
318 yield data
319
320 return get_chunked_archive(archive)
307 321
308 322 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
309 323 'repository.admin')
310 324 def diff(self, repo_name, f_path):
311 325 diff1 = request.GET.get('diff1')
312 326 diff2 = request.GET.get('diff2')
313 327 c.action = request.GET.get('diff')
314 328 c.no_changes = diff1 == diff2
315 329 c.f_path = f_path
316 330 c.big_diff = False
317 331
318 332 try:
319 333 if diff1 not in ['', None, 'None', '0' * 12, '0' * 40]:
320 334 c.changeset_1 = c.rhodecode_repo.get_changeset(diff1)
321 335 node1 = c.changeset_1.get_node(f_path)
322 336 else:
323 337 c.changeset_1 = EmptyChangeset(repo=c.rhodecode_repo)
324 338 node1 = FileNode('.', '', changeset=c.changeset_1)
325 339
326 340 if diff2 not in ['', None, 'None', '0' * 12, '0' * 40]:
327 341 c.changeset_2 = c.rhodecode_repo.get_changeset(diff2)
328 342 node2 = c.changeset_2.get_node(f_path)
329 343 else:
330 344 c.changeset_2 = EmptyChangeset(repo=c.rhodecode_repo)
331 345 node2 = FileNode('.', '', changeset=c.changeset_2)
332 346 except RepositoryError:
333 347 return redirect(url('files_home',
334 348 repo_name=c.repo_name, f_path=f_path))
335 349
336 350 if c.action == 'download':
337 351 diff = differ.DiffProcessor(differ.get_gitdiff(node1, node2),
338 352 format='gitdiff')
339 353
340 354 diff_name = '%s_vs_%s.diff' % (diff1, diff2)
341 355 response.content_type = 'text/plain'
342 356 response.content_disposition = 'attachment; filename=%s' \
343 357 % diff_name
344 358 return diff.raw_diff()
345 359
346 360 elif c.action == 'raw':
347 361 diff = differ.DiffProcessor(differ.get_gitdiff(node1, node2),
348 362 format='gitdiff')
349 363 response.content_type = 'text/plain'
350 364 return diff.raw_diff()
351 365
352 366 elif c.action == 'diff':
353 367 if node1.is_binary or node2.is_binary:
354 368 c.cur_diff = _('Binary file')
355 369 elif node1.size > self.cut_off_limit or \
356 370 node2.size > self.cut_off_limit:
357 371 c.cur_diff = ''
358 372 c.big_diff = True
359 373 else:
360 374 diff = differ.DiffProcessor(differ.get_gitdiff(node1, node2),
361 375 format='gitdiff')
362 376 c.cur_diff = diff.as_html()
363 377 else:
364 378
365 379 #default option
366 380 if node1.is_binary or node2.is_binary:
367 381 c.cur_diff = _('Binary file')
368 382 elif node1.size > self.cut_off_limit or \
369 383 node2.size > self.cut_off_limit:
370 384 c.cur_diff = ''
371 385 c.big_diff = True
372 386
373 387 else:
374 388 diff = differ.DiffProcessor(differ.get_gitdiff(node1, node2),
375 389 format='gitdiff')
376 390 c.cur_diff = diff.as_html()
377 391
378 392 if not c.cur_diff and not c.big_diff:
379 393 c.no_changes = True
380 394 return render('files/file_diff.html')
381 395
382 396 def _get_node_history(self, cs, f_path):
383 397 changesets = cs.get_file_history(f_path)
384 398 hist_l = []
385 399
386 400 changesets_group = ([], _("Changesets"))
387 401 branches_group = ([], _("Branches"))
388 402 tags_group = ([], _("Tags"))
389 403
390 404 for chs in changesets:
391 405 n_desc = 'r%s:%s' % (chs.revision, chs.short_id)
392 406 changesets_group[0].append((chs.raw_id, n_desc,))
393 407
394 408 hist_l.append(changesets_group)
395 409
396 410 for name, chs in c.rhodecode_repo.branches.items():
397 411 #chs = chs.split(':')[-1]
398 412 branches_group[0].append((chs, name),)
399 413 hist_l.append(branches_group)
400 414
401 415 for name, chs in c.rhodecode_repo.tags.items():
402 416 #chs = chs.split(':')[-1]
403 417 tags_group[0].append((chs, name),)
404 418 hist_l.append(tags_group)
405 419
406 420 return hist_l
General Comments 0
You need to be logged in to leave comments. Login now