##// END OF EJS Templates
cleanup code of get archive for repositories
marcink -
r2267:b872bc10 beta
parent child Browse files
Show More
@@ -1,487 +1,485 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.controllers.files
3 rhodecode.controllers.files
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 Files controller for RhodeCode
6 Files controller for RhodeCode
7
7
8 :created_on: Apr 21, 2010
8 :created_on: Apr 21, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
16 # (at your option) any later version.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25
25
26 import os
26 import os
27 import logging
27 import logging
28 import traceback
28 import traceback
29 import tempfile
29
30
30 from pylons import request, response, tmpl_context as c, url
31 from pylons import request, response, tmpl_context as c, url
31 from pylons.i18n.translation import _
32 from pylons.i18n.translation import _
32 from pylons.controllers.util import redirect
33 from pylons.controllers.util import redirect
33 from pylons.decorators import jsonify
34 from pylons.decorators import jsonify
34
35
35 from rhodecode.lib import diffs
36 from rhodecode.lib import diffs
36 from rhodecode.lib import helpers as h
37 from rhodecode.lib import helpers as h
37
38
38 from rhodecode.lib.compat import OrderedDict
39 from rhodecode.lib.compat import OrderedDict
39 from rhodecode.lib.utils2 import convert_line_endings, detect_mode, safe_str
40 from rhodecode.lib.utils2 import convert_line_endings, detect_mode, safe_str
40 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
41 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
41 from rhodecode.lib.base import BaseRepoController, render
42 from rhodecode.lib.base import BaseRepoController, render
42 from rhodecode.lib.utils import EmptyChangeset
43 from rhodecode.lib.utils import EmptyChangeset
43 from rhodecode.lib.vcs.conf import settings
44 from rhodecode.lib.vcs.conf import settings
44 from rhodecode.lib.vcs.exceptions import RepositoryError, \
45 from rhodecode.lib.vcs.exceptions import RepositoryError, \
45 ChangesetDoesNotExistError, EmptyRepositoryError, \
46 ChangesetDoesNotExistError, EmptyRepositoryError, \
46 ImproperArchiveTypeError, VCSError, NodeAlreadyExistsError
47 ImproperArchiveTypeError, VCSError, NodeAlreadyExistsError
47 from rhodecode.lib.vcs.nodes import FileNode
48 from rhodecode.lib.vcs.nodes import FileNode
48
49
49 from rhodecode.model.repo import RepoModel
50 from rhodecode.model.repo import RepoModel
50 from rhodecode.model.scm import ScmModel
51 from rhodecode.model.scm import ScmModel
51 from rhodecode.model.db import Repository
52 from rhodecode.model.db import Repository
52
53
53 from rhodecode.controllers.changeset import anchor_url, _ignorews_url,\
54 from rhodecode.controllers.changeset import anchor_url, _ignorews_url,\
54 _context_url, get_line_ctx, get_ignore_ws
55 _context_url, get_line_ctx, get_ignore_ws
55
56
56
57
57 log = logging.getLogger(__name__)
58 log = logging.getLogger(__name__)
58
59
59
60
60 class FilesController(BaseRepoController):
61 class FilesController(BaseRepoController):
61
62
62 @LoginRequired()
63 @LoginRequired()
63 def __before__(self):
64 def __before__(self):
64 super(FilesController, self).__before__()
65 super(FilesController, self).__before__()
65 c.cut_off_limit = self.cut_off_limit
66 c.cut_off_limit = self.cut_off_limit
66
67
67 def __get_cs_or_redirect(self, rev, repo_name, redirect_after=True):
68 def __get_cs_or_redirect(self, rev, repo_name, redirect_after=True):
68 """
69 """
69 Safe way to get changeset if error occur it redirects to tip with
70 Safe way to get changeset if error occur it redirects to tip with
70 proper message
71 proper message
71
72
72 :param rev: revision to fetch
73 :param rev: revision to fetch
73 :param repo_name: repo name to redirect after
74 :param repo_name: repo name to redirect after
74 """
75 """
75
76
76 try:
77 try:
77 return c.rhodecode_repo.get_changeset(rev)
78 return c.rhodecode_repo.get_changeset(rev)
78 except EmptyRepositoryError, e:
79 except EmptyRepositoryError, e:
79 if not redirect_after:
80 if not redirect_after:
80 return None
81 return None
81 url_ = url('files_add_home',
82 url_ = url('files_add_home',
82 repo_name=c.repo_name,
83 repo_name=c.repo_name,
83 revision=0, f_path='')
84 revision=0, f_path='')
84 add_new = '<a href="%s">[%s]</a>' % (url_, _('add new'))
85 add_new = '<a href="%s">[%s]</a>' % (url_, _('add new'))
85 h.flash(h.literal(_('There are no files yet %s' % add_new)),
86 h.flash(h.literal(_('There are no files yet %s' % add_new)),
86 category='warning')
87 category='warning')
87 redirect(h.url('summary_home', repo_name=repo_name))
88 redirect(h.url('summary_home', repo_name=repo_name))
88
89
89 except RepositoryError, e:
90 except RepositoryError, e:
90 h.flash(str(e), category='warning')
91 h.flash(str(e), category='warning')
91 redirect(h.url('files_home', repo_name=repo_name, revision='tip'))
92 redirect(h.url('files_home', repo_name=repo_name, revision='tip'))
92
93
93 def __get_filenode_or_redirect(self, repo_name, cs, path):
94 def __get_filenode_or_redirect(self, repo_name, cs, path):
94 """
95 """
95 Returns file_node, if error occurs or given path is directory,
96 Returns file_node, if error occurs or given path is directory,
96 it'll redirect to top level path
97 it'll redirect to top level path
97
98
98 :param repo_name: repo_name
99 :param repo_name: repo_name
99 :param cs: given changeset
100 :param cs: given changeset
100 :param path: path to lookup
101 :param path: path to lookup
101 """
102 """
102
103
103 try:
104 try:
104 file_node = cs.get_node(path)
105 file_node = cs.get_node(path)
105 if file_node.is_dir():
106 if file_node.is_dir():
106 raise RepositoryError('given path is a directory')
107 raise RepositoryError('given path is a directory')
107 except RepositoryError, e:
108 except RepositoryError, e:
108 h.flash(str(e), category='warning')
109 h.flash(str(e), category='warning')
109 redirect(h.url('files_home', repo_name=repo_name,
110 redirect(h.url('files_home', repo_name=repo_name,
110 revision=cs.raw_id))
111 revision=cs.raw_id))
111
112
112 return file_node
113 return file_node
113
114
114 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
115 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
115 'repository.admin')
116 'repository.admin')
116 def index(self, repo_name, revision, f_path, annotate=False):
117 def index(self, repo_name, revision, f_path, annotate=False):
117 # redirect to given revision from form if given
118 # redirect to given revision from form if given
118 post_revision = request.POST.get('at_rev', None)
119 post_revision = request.POST.get('at_rev', None)
119 if post_revision:
120 if post_revision:
120 cs = self.__get_cs_or_redirect(post_revision, repo_name)
121 cs = self.__get_cs_or_redirect(post_revision, repo_name)
121 redirect(url('files_home', repo_name=c.repo_name,
122 redirect(url('files_home', repo_name=c.repo_name,
122 revision=cs.raw_id, f_path=f_path))
123 revision=cs.raw_id, f_path=f_path))
123
124
124 c.changeset = self.__get_cs_or_redirect(revision, repo_name)
125 c.changeset = self.__get_cs_or_redirect(revision, repo_name)
125 c.branch = request.GET.get('branch', None)
126 c.branch = request.GET.get('branch', None)
126 c.f_path = f_path
127 c.f_path = f_path
127 c.annotate = annotate
128 c.annotate = annotate
128 cur_rev = c.changeset.revision
129 cur_rev = c.changeset.revision
129
130
130 # prev link
131 # prev link
131 try:
132 try:
132 prev_rev = c.rhodecode_repo.get_changeset(cur_rev).prev(c.branch)
133 prev_rev = c.rhodecode_repo.get_changeset(cur_rev).prev(c.branch)
133 c.url_prev = url('files_home', repo_name=c.repo_name,
134 c.url_prev = url('files_home', repo_name=c.repo_name,
134 revision=prev_rev.raw_id, f_path=f_path)
135 revision=prev_rev.raw_id, f_path=f_path)
135 if c.branch:
136 if c.branch:
136 c.url_prev += '?branch=%s' % c.branch
137 c.url_prev += '?branch=%s' % c.branch
137 except (ChangesetDoesNotExistError, VCSError):
138 except (ChangesetDoesNotExistError, VCSError):
138 c.url_prev = '#'
139 c.url_prev = '#'
139
140
140 # next link
141 # next link
141 try:
142 try:
142 next_rev = c.rhodecode_repo.get_changeset(cur_rev).next(c.branch)
143 next_rev = c.rhodecode_repo.get_changeset(cur_rev).next(c.branch)
143 c.url_next = url('files_home', repo_name=c.repo_name,
144 c.url_next = url('files_home', repo_name=c.repo_name,
144 revision=next_rev.raw_id, f_path=f_path)
145 revision=next_rev.raw_id, f_path=f_path)
145 if c.branch:
146 if c.branch:
146 c.url_next += '?branch=%s' % c.branch
147 c.url_next += '?branch=%s' % c.branch
147 except (ChangesetDoesNotExistError, VCSError):
148 except (ChangesetDoesNotExistError, VCSError):
148 c.url_next = '#'
149 c.url_next = '#'
149
150
150 # files or dirs
151 # files or dirs
151 try:
152 try:
152 c.file = c.changeset.get_node(f_path)
153 c.file = c.changeset.get_node(f_path)
153
154
154 if c.file.is_file():
155 if c.file.is_file():
155 c.file_history = self._get_node_history(c.changeset, f_path)
156 c.file_history = self._get_node_history(c.changeset, f_path)
156 else:
157 else:
157 c.file_history = []
158 c.file_history = []
158 except RepositoryError, e:
159 except RepositoryError, e:
159 h.flash(str(e), category='warning')
160 h.flash(str(e), category='warning')
160 redirect(h.url('files_home', repo_name=repo_name,
161 redirect(h.url('files_home', repo_name=repo_name,
161 revision=revision))
162 revision=revision))
162
163
163 return render('files/files.html')
164 return render('files/files.html')
164
165
165 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
166 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
166 'repository.admin')
167 'repository.admin')
167 def rawfile(self, repo_name, revision, f_path):
168 def rawfile(self, repo_name, revision, f_path):
168 cs = self.__get_cs_or_redirect(revision, repo_name)
169 cs = self.__get_cs_or_redirect(revision, repo_name)
169 file_node = self.__get_filenode_or_redirect(repo_name, cs, f_path)
170 file_node = self.__get_filenode_or_redirect(repo_name, cs, f_path)
170
171
171 response.content_disposition = 'attachment; filename=%s' % \
172 response.content_disposition = 'attachment; filename=%s' % \
172 safe_str(f_path.split(Repository.url_sep())[-1])
173 safe_str(f_path.split(Repository.url_sep())[-1])
173
174
174 response.content_type = file_node.mimetype
175 response.content_type = file_node.mimetype
175 return file_node.content
176 return file_node.content
176
177
177 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
178 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
178 'repository.admin')
179 'repository.admin')
179 def raw(self, repo_name, revision, f_path):
180 def raw(self, repo_name, revision, f_path):
180 cs = self.__get_cs_or_redirect(revision, repo_name)
181 cs = self.__get_cs_or_redirect(revision, repo_name)
181 file_node = self.__get_filenode_or_redirect(repo_name, cs, f_path)
182 file_node = self.__get_filenode_or_redirect(repo_name, cs, f_path)
182
183
183 raw_mimetype_mapping = {
184 raw_mimetype_mapping = {
184 # map original mimetype to a mimetype used for "show as raw"
185 # map original mimetype to a mimetype used for "show as raw"
185 # you can also provide a content-disposition to override the
186 # you can also provide a content-disposition to override the
186 # default "attachment" disposition.
187 # default "attachment" disposition.
187 # orig_type: (new_type, new_dispo)
188 # orig_type: (new_type, new_dispo)
188
189
189 # show images inline:
190 # show images inline:
190 'image/x-icon': ('image/x-icon', 'inline'),
191 'image/x-icon': ('image/x-icon', 'inline'),
191 'image/png': ('image/png', 'inline'),
192 'image/png': ('image/png', 'inline'),
192 'image/gif': ('image/gif', 'inline'),
193 'image/gif': ('image/gif', 'inline'),
193 'image/jpeg': ('image/jpeg', 'inline'),
194 'image/jpeg': ('image/jpeg', 'inline'),
194 'image/svg+xml': ('image/svg+xml', 'inline'),
195 'image/svg+xml': ('image/svg+xml', 'inline'),
195 }
196 }
196
197
197 mimetype = file_node.mimetype
198 mimetype = file_node.mimetype
198 try:
199 try:
199 mimetype, dispo = raw_mimetype_mapping[mimetype]
200 mimetype, dispo = raw_mimetype_mapping[mimetype]
200 except KeyError:
201 except KeyError:
201 # we don't know anything special about this, handle it safely
202 # we don't know anything special about this, handle it safely
202 if file_node.is_binary:
203 if file_node.is_binary:
203 # do same as download raw for binary files
204 # do same as download raw for binary files
204 mimetype, dispo = 'application/octet-stream', 'attachment'
205 mimetype, dispo = 'application/octet-stream', 'attachment'
205 else:
206 else:
206 # do not just use the original mimetype, but force text/plain,
207 # do not just use the original mimetype, but force text/plain,
207 # otherwise it would serve text/html and that might be unsafe.
208 # otherwise it would serve text/html and that might be unsafe.
208 # Note: underlying vcs library fakes text/plain mimetype if the
209 # Note: underlying vcs library fakes text/plain mimetype if the
209 # mimetype can not be determined and it thinks it is not
210 # mimetype can not be determined and it thinks it is not
210 # binary.This might lead to erroneous text display in some
211 # binary.This might lead to erroneous text display in some
211 # cases, but helps in other cases, like with text files
212 # cases, but helps in other cases, like with text files
212 # without extension.
213 # without extension.
213 mimetype, dispo = 'text/plain', 'inline'
214 mimetype, dispo = 'text/plain', 'inline'
214
215
215 if dispo == 'attachment':
216 if dispo == 'attachment':
216 dispo = 'attachment; filename=%s' % \
217 dispo = 'attachment; filename=%s' % \
217 safe_str(f_path.split(os.sep)[-1])
218 safe_str(f_path.split(os.sep)[-1])
218
219
219 response.content_disposition = dispo
220 response.content_disposition = dispo
220 response.content_type = mimetype
221 response.content_type = mimetype
221 return file_node.content
222 return file_node.content
222
223
223 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
224 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
224 def edit(self, repo_name, revision, f_path):
225 def edit(self, repo_name, revision, f_path):
225 r_post = request.POST
226 r_post = request.POST
226
227
227 c.cs = self.__get_cs_or_redirect(revision, repo_name)
228 c.cs = self.__get_cs_or_redirect(revision, repo_name)
228 c.file = self.__get_filenode_or_redirect(repo_name, c.cs, f_path)
229 c.file = self.__get_filenode_or_redirect(repo_name, c.cs, f_path)
229
230
230 if c.file.is_binary:
231 if c.file.is_binary:
231 return redirect(url('files_home', repo_name=c.repo_name,
232 return redirect(url('files_home', repo_name=c.repo_name,
232 revision=c.cs.raw_id, f_path=f_path))
233 revision=c.cs.raw_id, f_path=f_path))
233
234
234 c.f_path = f_path
235 c.f_path = f_path
235
236
236 if r_post:
237 if r_post:
237
238
238 old_content = c.file.content
239 old_content = c.file.content
239 sl = old_content.splitlines(1)
240 sl = old_content.splitlines(1)
240 first_line = sl[0] if sl else ''
241 first_line = sl[0] if sl else ''
241 # modes: 0 - Unix, 1 - Mac, 2 - DOS
242 # modes: 0 - Unix, 1 - Mac, 2 - DOS
242 mode = detect_mode(first_line, 0)
243 mode = detect_mode(first_line, 0)
243 content = convert_line_endings(r_post.get('content'), mode)
244 content = convert_line_endings(r_post.get('content'), mode)
244
245
245 message = r_post.get('message') or (_('Edited %s via RhodeCode')
246 message = r_post.get('message') or (_('Edited %s via RhodeCode')
246 % (f_path))
247 % (f_path))
247 author = self.rhodecode_user.full_contact
248 author = self.rhodecode_user.full_contact
248
249
249 if content == old_content:
250 if content == old_content:
250 h.flash(_('No changes'),
251 h.flash(_('No changes'),
251 category='warning')
252 category='warning')
252 return redirect(url('changeset_home', repo_name=c.repo_name,
253 return redirect(url('changeset_home', repo_name=c.repo_name,
253 revision='tip'))
254 revision='tip'))
254
255
255 try:
256 try:
256 self.scm_model.commit_change(repo=c.rhodecode_repo,
257 self.scm_model.commit_change(repo=c.rhodecode_repo,
257 repo_name=repo_name, cs=c.cs,
258 repo_name=repo_name, cs=c.cs,
258 user=self.rhodecode_user,
259 user=self.rhodecode_user,
259 author=author, message=message,
260 author=author, message=message,
260 content=content, f_path=f_path)
261 content=content, f_path=f_path)
261 h.flash(_('Successfully committed to %s' % f_path),
262 h.flash(_('Successfully committed to %s' % f_path),
262 category='success')
263 category='success')
263
264
264 except Exception:
265 except Exception:
265 log.error(traceback.format_exc())
266 log.error(traceback.format_exc())
266 h.flash(_('Error occurred during commit'), category='error')
267 h.flash(_('Error occurred during commit'), category='error')
267 return redirect(url('changeset_home',
268 return redirect(url('changeset_home',
268 repo_name=c.repo_name, revision='tip'))
269 repo_name=c.repo_name, revision='tip'))
269
270
270 return render('files/files_edit.html')
271 return render('files/files_edit.html')
271
272
272 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
273 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
273 def add(self, repo_name, revision, f_path):
274 def add(self, repo_name, revision, f_path):
274 r_post = request.POST
275 r_post = request.POST
275 c.cs = self.__get_cs_or_redirect(revision, repo_name,
276 c.cs = self.__get_cs_or_redirect(revision, repo_name,
276 redirect_after=False)
277 redirect_after=False)
277 if c.cs is None:
278 if c.cs is None:
278 c.cs = EmptyChangeset(alias=c.rhodecode_repo.alias)
279 c.cs = EmptyChangeset(alias=c.rhodecode_repo.alias)
279
280
280 c.f_path = f_path
281 c.f_path = f_path
281
282
282 if r_post:
283 if r_post:
283 unix_mode = 0
284 unix_mode = 0
284 content = convert_line_endings(r_post.get('content'), unix_mode)
285 content = convert_line_endings(r_post.get('content'), unix_mode)
285
286
286 message = r_post.get('message') or (_('Added %s via RhodeCode')
287 message = r_post.get('message') or (_('Added %s via RhodeCode')
287 % (f_path))
288 % (f_path))
288 location = r_post.get('location')
289 location = r_post.get('location')
289 filename = r_post.get('filename')
290 filename = r_post.get('filename')
290 file_obj = r_post.get('upload_file', None)
291 file_obj = r_post.get('upload_file', None)
291
292
292 if file_obj is not None and hasattr(file_obj, 'filename'):
293 if file_obj is not None and hasattr(file_obj, 'filename'):
293 filename = file_obj.filename
294 filename = file_obj.filename
294 content = file_obj.file
295 content = file_obj.file
295
296
296 node_path = os.path.join(location, filename)
297 node_path = os.path.join(location, filename)
297 author = self.rhodecode_user.full_contact
298 author = self.rhodecode_user.full_contact
298
299
299 if not content:
300 if not content:
300 h.flash(_('No content'), category='warning')
301 h.flash(_('No content'), category='warning')
301 return redirect(url('changeset_home', repo_name=c.repo_name,
302 return redirect(url('changeset_home', repo_name=c.repo_name,
302 revision='tip'))
303 revision='tip'))
303 if not filename:
304 if not filename:
304 h.flash(_('No filename'), category='warning')
305 h.flash(_('No filename'), category='warning')
305 return redirect(url('changeset_home', repo_name=c.repo_name,
306 return redirect(url('changeset_home', repo_name=c.repo_name,
306 revision='tip'))
307 revision='tip'))
307
308
308 try:
309 try:
309 self.scm_model.create_node(repo=c.rhodecode_repo,
310 self.scm_model.create_node(repo=c.rhodecode_repo,
310 repo_name=repo_name, cs=c.cs,
311 repo_name=repo_name, cs=c.cs,
311 user=self.rhodecode_user,
312 user=self.rhodecode_user,
312 author=author, message=message,
313 author=author, message=message,
313 content=content, f_path=node_path)
314 content=content, f_path=node_path)
314 h.flash(_('Successfully committed to %s' % node_path),
315 h.flash(_('Successfully committed to %s' % node_path),
315 category='success')
316 category='success')
316 except NodeAlreadyExistsError, e:
317 except NodeAlreadyExistsError, e:
317 h.flash(_(e), category='error')
318 h.flash(_(e), category='error')
318 except Exception:
319 except Exception:
319 log.error(traceback.format_exc())
320 log.error(traceback.format_exc())
320 h.flash(_('Error occurred during commit'), category='error')
321 h.flash(_('Error occurred during commit'), category='error')
321 return redirect(url('changeset_home',
322 return redirect(url('changeset_home',
322 repo_name=c.repo_name, revision='tip'))
323 repo_name=c.repo_name, revision='tip'))
323
324
324 return render('files/files_add.html')
325 return render('files/files_add.html')
325
326
326 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
327 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
327 'repository.admin')
328 'repository.admin')
328 def archivefile(self, repo_name, fname):
329 def archivefile(self, repo_name, fname):
329
330
330 fileformat = None
331 fileformat = None
331 revision = None
332 revision = None
332 ext = None
333 ext = None
333 subrepos = request.GET.get('subrepos') == 'true'
334 subrepos = request.GET.get('subrepos') == 'true'
334
335
335 for a_type, ext_data in settings.ARCHIVE_SPECS.items():
336 for a_type, ext_data in settings.ARCHIVE_SPECS.items():
336 archive_spec = fname.split(ext_data[1])
337 archive_spec = fname.split(ext_data[1])
337 if len(archive_spec) == 2 and archive_spec[1] == '':
338 if len(archive_spec) == 2 and archive_spec[1] == '':
338 fileformat = a_type or ext_data[1]
339 fileformat = a_type or ext_data[1]
339 revision = archive_spec[0]
340 revision = archive_spec[0]
340 ext = ext_data[1]
341 ext = ext_data[1]
341
342
342 try:
343 try:
343 dbrepo = RepoModel().get_by_repo_name(repo_name)
344 dbrepo = RepoModel().get_by_repo_name(repo_name)
344 if dbrepo.enable_downloads is False:
345 if dbrepo.enable_downloads is False:
345 return _('downloads disabled')
346 return _('downloads disabled')
346
347
347 if c.rhodecode_repo.alias == 'hg':
348 if c.rhodecode_repo.alias == 'hg':
348 # patch and reset hooks section of UI config to not run any
349 # patch and reset hooks section of UI config to not run any
349 # hooks on fetching archives with subrepos
350 # hooks on fetching archives with subrepos
350 for k, v in c.rhodecode_repo._repo.ui.configitems('hooks'):
351 for k, v in c.rhodecode_repo._repo.ui.configitems('hooks'):
351 c.rhodecode_repo._repo.ui.setconfig('hooks', k, None)
352 c.rhodecode_repo._repo.ui.setconfig('hooks', k, None)
352
353
353 cs = c.rhodecode_repo.get_changeset(revision)
354 cs = c.rhodecode_repo.get_changeset(revision)
354 content_type = settings.ARCHIVE_SPECS[fileformat][0]
355 content_type = settings.ARCHIVE_SPECS[fileformat][0]
355 except ChangesetDoesNotExistError:
356 except ChangesetDoesNotExistError:
356 return _('Unknown revision %s') % revision
357 return _('Unknown revision %s') % revision
357 except EmptyRepositoryError:
358 except EmptyRepositoryError:
358 return _('Empty repository')
359 return _('Empty repository')
359 except (ImproperArchiveTypeError, KeyError):
360 except (ImproperArchiveTypeError, KeyError):
360 return _('Unknown archive type')
361 return _('Unknown archive type')
361
362
363 archive = tempfile.NamedTemporaryFile(mode='w+r+b')
364 cs.fill_archive(stream=archive, kind=fileformat, subrepos=subrepos)
365
362 response.content_type = content_type
366 response.content_type = content_type
363 response.content_disposition = 'attachment; filename=%s-%s%s' \
367 response.content_disposition = 'attachment; filename=%s-%s%s' \
364 % (repo_name, revision, ext)
368 % (repo_name, revision[:12], ext)
365
369 response.content_length = str(os.path.getsize(archive.name))
366 import tempfile
367 archive = tempfile.mkstemp()[1]
368 t = open(archive, 'wb')
369 cs.fill_archive(stream=t, kind=fileformat, subrepos=subrepos)
370
370
371 def get_chunked_archive(archive):
371 def get_chunked_archive(tmpfile):
372 stream = open(archive, 'rb')
373 while True:
372 while True:
374 data = stream.read(4096)
373 data = tmpfile.read(16 * 1024)
375 if not data:
374 if not data:
376 os.remove(archive)
375 tmpfile.close()
377 break
376 break
378 yield data
377 yield data
379
378 return get_chunked_archive(tmpfile=archive)
380 return get_chunked_archive(archive)
381
379
382 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
380 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
383 'repository.admin')
381 'repository.admin')
384 def diff(self, repo_name, f_path):
382 def diff(self, repo_name, f_path):
385 ignore_whitespace = request.GET.get('ignorews') == '1'
383 ignore_whitespace = request.GET.get('ignorews') == '1'
386 line_context = request.GET.get('context', 3)
384 line_context = request.GET.get('context', 3)
387 diff1 = request.GET.get('diff1', '')
385 diff1 = request.GET.get('diff1', '')
388 diff2 = request.GET.get('diff2', '')
386 diff2 = request.GET.get('diff2', '')
389 c.action = request.GET.get('diff')
387 c.action = request.GET.get('diff')
390 c.no_changes = diff1 == diff2
388 c.no_changes = diff1 == diff2
391 c.f_path = f_path
389 c.f_path = f_path
392 c.big_diff = False
390 c.big_diff = False
393 c.anchor_url = anchor_url
391 c.anchor_url = anchor_url
394 c.ignorews_url = _ignorews_url
392 c.ignorews_url = _ignorews_url
395 c.context_url = _context_url
393 c.context_url = _context_url
396 c.changes = OrderedDict()
394 c.changes = OrderedDict()
397 c.changes[diff2] = []
395 c.changes[diff2] = []
398 try:
396 try:
399 if diff1 not in ['', None, 'None', '0' * 12, '0' * 40]:
397 if diff1 not in ['', None, 'None', '0' * 12, '0' * 40]:
400 c.changeset_1 = c.rhodecode_repo.get_changeset(diff1)
398 c.changeset_1 = c.rhodecode_repo.get_changeset(diff1)
401 node1 = c.changeset_1.get_node(f_path)
399 node1 = c.changeset_1.get_node(f_path)
402 else:
400 else:
403 c.changeset_1 = EmptyChangeset(repo=c.rhodecode_repo)
401 c.changeset_1 = EmptyChangeset(repo=c.rhodecode_repo)
404 node1 = FileNode('.', '', changeset=c.changeset_1)
402 node1 = FileNode('.', '', changeset=c.changeset_1)
405
403
406 if diff2 not in ['', None, 'None', '0' * 12, '0' * 40]:
404 if diff2 not in ['', None, 'None', '0' * 12, '0' * 40]:
407 c.changeset_2 = c.rhodecode_repo.get_changeset(diff2)
405 c.changeset_2 = c.rhodecode_repo.get_changeset(diff2)
408 node2 = c.changeset_2.get_node(f_path)
406 node2 = c.changeset_2.get_node(f_path)
409 else:
407 else:
410 c.changeset_2 = EmptyChangeset(repo=c.rhodecode_repo)
408 c.changeset_2 = EmptyChangeset(repo=c.rhodecode_repo)
411 node2 = FileNode('.', '', changeset=c.changeset_2)
409 node2 = FileNode('.', '', changeset=c.changeset_2)
412 except RepositoryError:
410 except RepositoryError:
413 return redirect(url('files_home', repo_name=c.repo_name,
411 return redirect(url('files_home', repo_name=c.repo_name,
414 f_path=f_path))
412 f_path=f_path))
415
413
416 if c.action == 'download':
414 if c.action == 'download':
417 _diff = diffs.get_gitdiff(node1, node2,
415 _diff = diffs.get_gitdiff(node1, node2,
418 ignore_whitespace=ignore_whitespace,
416 ignore_whitespace=ignore_whitespace,
419 context=line_context)
417 context=line_context)
420 diff = diffs.DiffProcessor(_diff, format='gitdiff')
418 diff = diffs.DiffProcessor(_diff, format='gitdiff')
421
419
422 diff_name = '%s_vs_%s.diff' % (diff1, diff2)
420 diff_name = '%s_vs_%s.diff' % (diff1, diff2)
423 response.content_type = 'text/plain'
421 response.content_type = 'text/plain'
424 response.content_disposition = (
422 response.content_disposition = (
425 'attachment; filename=%s' % diff_name
423 'attachment; filename=%s' % diff_name
426 )
424 )
427 return diff.raw_diff()
425 return diff.raw_diff()
428
426
429 elif c.action == 'raw':
427 elif c.action == 'raw':
430 _diff = diffs.get_gitdiff(node1, node2,
428 _diff = diffs.get_gitdiff(node1, node2,
431 ignore_whitespace=ignore_whitespace,
429 ignore_whitespace=ignore_whitespace,
432 context=line_context)
430 context=line_context)
433 diff = diffs.DiffProcessor(_diff, format='gitdiff')
431 diff = diffs.DiffProcessor(_diff, format='gitdiff')
434 response.content_type = 'text/plain'
432 response.content_type = 'text/plain'
435 return diff.raw_diff()
433 return diff.raw_diff()
436
434
437 else:
435 else:
438 fid = h.FID(diff2, node2.path)
436 fid = h.FID(diff2, node2.path)
439 line_context_lcl = get_line_ctx(fid, request.GET)
437 line_context_lcl = get_line_ctx(fid, request.GET)
440 ign_whitespace_lcl = get_ignore_ws(fid, request.GET)
438 ign_whitespace_lcl = get_ignore_ws(fid, request.GET)
441
439
442 lim = request.GET.get('fulldiff') or self.cut_off_limit
440 lim = request.GET.get('fulldiff') or self.cut_off_limit
443 _, cs1, cs2, diff, st = diffs.wrapped_diff(filenode_old=node1,
441 _, cs1, cs2, diff, st = diffs.wrapped_diff(filenode_old=node1,
444 filenode_new=node2,
442 filenode_new=node2,
445 cut_off_limit=lim,
443 cut_off_limit=lim,
446 ignore_whitespace=ign_whitespace_lcl,
444 ignore_whitespace=ign_whitespace_lcl,
447 line_context=line_context_lcl,
445 line_context=line_context_lcl,
448 enable_comments=False)
446 enable_comments=False)
449
447
450 c.changes = [('', node2, diff, cs1, cs2, st,)]
448 c.changes = [('', node2, diff, cs1, cs2, st,)]
451
449
452 return render('files/file_diff.html')
450 return render('files/file_diff.html')
453
451
454 def _get_node_history(self, cs, f_path):
452 def _get_node_history(self, cs, f_path):
455 changesets = cs.get_file_history(f_path)
453 changesets = cs.get_file_history(f_path)
456 hist_l = []
454 hist_l = []
457
455
458 changesets_group = ([], _("Changesets"))
456 changesets_group = ([], _("Changesets"))
459 branches_group = ([], _("Branches"))
457 branches_group = ([], _("Branches"))
460 tags_group = ([], _("Tags"))
458 tags_group = ([], _("Tags"))
461 _hg = cs.repository.alias == 'hg'
459 _hg = cs.repository.alias == 'hg'
462 for chs in changesets:
460 for chs in changesets:
463 _branch = '(%s)' % chs.branch if _hg else ''
461 _branch = '(%s)' % chs.branch if _hg else ''
464 n_desc = 'r%s:%s %s' % (chs.revision, chs.short_id, _branch)
462 n_desc = 'r%s:%s %s' % (chs.revision, chs.short_id, _branch)
465 changesets_group[0].append((chs.raw_id, n_desc,))
463 changesets_group[0].append((chs.raw_id, n_desc,))
466
464
467 hist_l.append(changesets_group)
465 hist_l.append(changesets_group)
468
466
469 for name, chs in c.rhodecode_repo.branches.items():
467 for name, chs in c.rhodecode_repo.branches.items():
470 branches_group[0].append((chs, name),)
468 branches_group[0].append((chs, name),)
471 hist_l.append(branches_group)
469 hist_l.append(branches_group)
472
470
473 for name, chs in c.rhodecode_repo.tags.items():
471 for name, chs in c.rhodecode_repo.tags.items():
474 tags_group[0].append((chs, name),)
472 tags_group[0].append((chs, name),)
475 hist_l.append(tags_group)
473 hist_l.append(tags_group)
476
474
477 return hist_l
475 return hist_l
478
476
479 @jsonify
477 @jsonify
480 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
478 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
481 'repository.admin')
479 'repository.admin')
482 def nodelist(self, repo_name, revision, f_path):
480 def nodelist(self, repo_name, revision, f_path):
483 if request.environ.get('HTTP_X_PARTIAL_XHR'):
481 if request.environ.get('HTTP_X_PARTIAL_XHR'):
484 cs = self.__get_cs_or_redirect(revision, repo_name)
482 cs = self.__get_cs_or_redirect(revision, repo_name)
485 _d, _f = ScmModel().get_nodes(repo_name, cs.raw_id, f_path,
483 _d, _f = ScmModel().get_nodes(repo_name, cs.raw_id, f_path,
486 flat=False)
484 flat=False)
487 return _d + _f
485 return _d + _f
@@ -1,359 +1,357 b''
1 import os
1 import os
2 import posixpath
2 import posixpath
3
3
4 from rhodecode.lib.vcs.backends.base import BaseChangeset
4 from rhodecode.lib.vcs.backends.base import BaseChangeset
5 from rhodecode.lib.vcs.conf import settings
5 from rhodecode.lib.vcs.conf import settings
6 from rhodecode.lib.vcs.exceptions import ChangesetDoesNotExistError, \
6 from rhodecode.lib.vcs.exceptions import ChangesetDoesNotExistError, \
7 ChangesetError, ImproperArchiveTypeError, NodeDoesNotExistError, VCSError
7 ChangesetError, ImproperArchiveTypeError, NodeDoesNotExistError, VCSError
8 from rhodecode.lib.vcs.nodes import AddedFileNodesGenerator, \
8 from rhodecode.lib.vcs.nodes import AddedFileNodesGenerator, \
9 ChangedFileNodesGenerator, DirNode, FileNode, NodeKind, \
9 ChangedFileNodesGenerator, DirNode, FileNode, NodeKind, \
10 RemovedFileNodesGenerator, RootNode, SubModuleNode
10 RemovedFileNodesGenerator, RootNode, SubModuleNode
11
11
12 from rhodecode.lib.vcs.utils import safe_str, safe_unicode, date_fromtimestamp
12 from rhodecode.lib.vcs.utils import safe_str, safe_unicode, date_fromtimestamp
13 from rhodecode.lib.vcs.utils.lazy import LazyProperty
13 from rhodecode.lib.vcs.utils.lazy import LazyProperty
14 from rhodecode.lib.vcs.utils.paths import get_dirs_for_path
14 from rhodecode.lib.vcs.utils.paths import get_dirs_for_path
15
15
16 from ...utils.hgcompat import archival, hex
16 from ...utils.hgcompat import archival, hex
17
17
18
18
19 class MercurialChangeset(BaseChangeset):
19 class MercurialChangeset(BaseChangeset):
20 """
20 """
21 Represents state of the repository at the single revision.
21 Represents state of the repository at the single revision.
22 """
22 """
23
23
24 def __init__(self, repository, revision):
24 def __init__(self, repository, revision):
25 self.repository = repository
25 self.repository = repository
26 self.raw_id = revision
26 self.raw_id = revision
27 self._ctx = repository._repo[revision]
27 self._ctx = repository._repo[revision]
28 self.revision = self._ctx._rev
28 self.revision = self._ctx._rev
29 self.nodes = {}
29 self.nodes = {}
30
30
31 @LazyProperty
31 @LazyProperty
32 def tags(self):
32 def tags(self):
33 return map(safe_unicode, self._ctx.tags())
33 return map(safe_unicode, self._ctx.tags())
34
34
35 @LazyProperty
35 @LazyProperty
36 def branch(self):
36 def branch(self):
37 return safe_unicode(self._ctx.branch())
37 return safe_unicode(self._ctx.branch())
38
38
39 @LazyProperty
39 @LazyProperty
40 def bookmarks(self):
40 def bookmarks(self):
41 return map(safe_unicode, self._ctx.bookmarks())
41 return map(safe_unicode, self._ctx.bookmarks())
42
42
43 @LazyProperty
43 @LazyProperty
44 def message(self):
44 def message(self):
45 return safe_unicode(self._ctx.description())
45 return safe_unicode(self._ctx.description())
46
46
47 @LazyProperty
47 @LazyProperty
48 def author(self):
48 def author(self):
49 return safe_unicode(self._ctx.user())
49 return safe_unicode(self._ctx.user())
50
50
51 @LazyProperty
51 @LazyProperty
52 def date(self):
52 def date(self):
53 return date_fromtimestamp(*self._ctx.date())
53 return date_fromtimestamp(*self._ctx.date())
54
54
55 @LazyProperty
55 @LazyProperty
56 def status(self):
56 def status(self):
57 """
57 """
58 Returns modified, added, removed, deleted files for current changeset
58 Returns modified, added, removed, deleted files for current changeset
59 """
59 """
60 return self.repository._repo.status(self._ctx.p1().node(),
60 return self.repository._repo.status(self._ctx.p1().node(),
61 self._ctx.node())
61 self._ctx.node())
62
62
63 @LazyProperty
63 @LazyProperty
64 def _file_paths(self):
64 def _file_paths(self):
65 return list(self._ctx)
65 return list(self._ctx)
66
66
67 @LazyProperty
67 @LazyProperty
68 def _dir_paths(self):
68 def _dir_paths(self):
69 p = list(set(get_dirs_for_path(*self._file_paths)))
69 p = list(set(get_dirs_for_path(*self._file_paths)))
70 p.insert(0, '')
70 p.insert(0, '')
71 return p
71 return p
72
72
73 @LazyProperty
73 @LazyProperty
74 def _paths(self):
74 def _paths(self):
75 return self._dir_paths + self._file_paths
75 return self._dir_paths + self._file_paths
76
76
77 @LazyProperty
77 @LazyProperty
78 def id(self):
78 def id(self):
79 if self.last:
79 if self.last:
80 return u'tip'
80 return u'tip'
81 return self.short_id
81 return self.short_id
82
82
83 @LazyProperty
83 @LazyProperty
84 def short_id(self):
84 def short_id(self):
85 return self.raw_id[:12]
85 return self.raw_id[:12]
86
86
87 @LazyProperty
87 @LazyProperty
88 def parents(self):
88 def parents(self):
89 """
89 """
90 Returns list of parents changesets.
90 Returns list of parents changesets.
91 """
91 """
92 return [self.repository.get_changeset(parent.rev())
92 return [self.repository.get_changeset(parent.rev())
93 for parent in self._ctx.parents() if parent.rev() >= 0]
93 for parent in self._ctx.parents() if parent.rev() >= 0]
94
94
95 def next(self, branch=None):
95 def next(self, branch=None):
96
96
97 if branch and self.branch != branch:
97 if branch and self.branch != branch:
98 raise VCSError('Branch option used on changeset not belonging '
98 raise VCSError('Branch option used on changeset not belonging '
99 'to that branch')
99 'to that branch')
100
100
101 def _next(changeset, branch):
101 def _next(changeset, branch):
102 try:
102 try:
103 next_ = changeset.revision + 1
103 next_ = changeset.revision + 1
104 next_rev = changeset.repository.revisions[next_]
104 next_rev = changeset.repository.revisions[next_]
105 except IndexError:
105 except IndexError:
106 raise ChangesetDoesNotExistError
106 raise ChangesetDoesNotExistError
107 cs = changeset.repository.get_changeset(next_rev)
107 cs = changeset.repository.get_changeset(next_rev)
108
108
109 if branch and branch != cs.branch:
109 if branch and branch != cs.branch:
110 return _next(cs, branch)
110 return _next(cs, branch)
111
111
112 return cs
112 return cs
113
113
114 return _next(self, branch)
114 return _next(self, branch)
115
115
116 def prev(self, branch=None):
116 def prev(self, branch=None):
117 if branch and self.branch != branch:
117 if branch and self.branch != branch:
118 raise VCSError('Branch option used on changeset not belonging '
118 raise VCSError('Branch option used on changeset not belonging '
119 'to that branch')
119 'to that branch')
120
120
121 def _prev(changeset, branch):
121 def _prev(changeset, branch):
122 try:
122 try:
123 prev_ = changeset.revision - 1
123 prev_ = changeset.revision - 1
124 if prev_ < 0:
124 if prev_ < 0:
125 raise IndexError
125 raise IndexError
126 prev_rev = changeset.repository.revisions[prev_]
126 prev_rev = changeset.repository.revisions[prev_]
127 except IndexError:
127 except IndexError:
128 raise ChangesetDoesNotExistError
128 raise ChangesetDoesNotExistError
129
129
130 cs = changeset.repository.get_changeset(prev_rev)
130 cs = changeset.repository.get_changeset(prev_rev)
131
131
132 if branch and branch != cs.branch:
132 if branch and branch != cs.branch:
133 return _prev(cs, branch)
133 return _prev(cs, branch)
134
134
135 return cs
135 return cs
136
136
137 return _prev(self, branch)
137 return _prev(self, branch)
138
138
139 def _fix_path(self, path):
139 def _fix_path(self, path):
140 """
140 """
141 Paths are stored without trailing slash so we need to get rid off it if
141 Paths are stored without trailing slash so we need to get rid off it if
142 needed. Also mercurial keeps filenodes as str so we need to decode
142 needed. Also mercurial keeps filenodes as str so we need to decode
143 from unicode to str
143 from unicode to str
144 """
144 """
145 if path.endswith('/'):
145 if path.endswith('/'):
146 path = path.rstrip('/')
146 path = path.rstrip('/')
147
147
148 return safe_str(path)
148 return safe_str(path)
149
149
150 def _get_kind(self, path):
150 def _get_kind(self, path):
151 path = self._fix_path(path)
151 path = self._fix_path(path)
152 if path in self._file_paths:
152 if path in self._file_paths:
153 return NodeKind.FILE
153 return NodeKind.FILE
154 elif path in self._dir_paths:
154 elif path in self._dir_paths:
155 return NodeKind.DIR
155 return NodeKind.DIR
156 else:
156 else:
157 raise ChangesetError("Node does not exist at the given path %r"
157 raise ChangesetError("Node does not exist at the given path %r"
158 % (path))
158 % (path))
159
159
160 def _get_filectx(self, path):
160 def _get_filectx(self, path):
161 path = self._fix_path(path)
161 path = self._fix_path(path)
162 if self._get_kind(path) != NodeKind.FILE:
162 if self._get_kind(path) != NodeKind.FILE:
163 raise ChangesetError("File does not exist for revision %r at "
163 raise ChangesetError("File does not exist for revision %r at "
164 " %r" % (self.revision, path))
164 " %r" % (self.revision, path))
165 return self._ctx.filectx(path)
165 return self._ctx.filectx(path)
166
166
167 def _extract_submodules(self):
167 def _extract_submodules(self):
168 """
168 """
169 returns a dictionary with submodule information from substate file
169 returns a dictionary with submodule information from substate file
170 of hg repository
170 of hg repository
171 """
171 """
172 return self._ctx.substate
172 return self._ctx.substate
173
173
174 def get_file_mode(self, path):
174 def get_file_mode(self, path):
175 """
175 """
176 Returns stat mode of the file at the given ``path``.
176 Returns stat mode of the file at the given ``path``.
177 """
177 """
178 fctx = self._get_filectx(path)
178 fctx = self._get_filectx(path)
179 if 'x' in fctx.flags():
179 if 'x' in fctx.flags():
180 return 0100755
180 return 0100755
181 else:
181 else:
182 return 0100644
182 return 0100644
183
183
184 def get_file_content(self, path):
184 def get_file_content(self, path):
185 """
185 """
186 Returns content of the file at given ``path``.
186 Returns content of the file at given ``path``.
187 """
187 """
188 fctx = self._get_filectx(path)
188 fctx = self._get_filectx(path)
189 return fctx.data()
189 return fctx.data()
190
190
191 def get_file_size(self, path):
191 def get_file_size(self, path):
192 """
192 """
193 Returns size of the file at given ``path``.
193 Returns size of the file at given ``path``.
194 """
194 """
195 fctx = self._get_filectx(path)
195 fctx = self._get_filectx(path)
196 return fctx.size()
196 return fctx.size()
197
197
198 def get_file_changeset(self, path):
198 def get_file_changeset(self, path):
199 """
199 """
200 Returns last commit of the file at the given ``path``.
200 Returns last commit of the file at the given ``path``.
201 """
201 """
202 node = self.get_node(path)
202 node = self.get_node(path)
203 return node.history[0]
203 return node.history[0]
204
204
205 def get_file_history(self, path):
205 def get_file_history(self, path):
206 """
206 """
207 Returns history of file as reversed list of ``Changeset`` objects for
207 Returns history of file as reversed list of ``Changeset`` objects for
208 which file at given ``path`` has been modified.
208 which file at given ``path`` has been modified.
209 """
209 """
210 fctx = self._get_filectx(path)
210 fctx = self._get_filectx(path)
211 nodes = [fctx.filectx(x).node() for x in fctx.filelog()]
211 nodes = [fctx.filectx(x).node() for x in fctx.filelog()]
212 changesets = [self.repository.get_changeset(hex(node))
212 changesets = [self.repository.get_changeset(hex(node))
213 for node in reversed(nodes)]
213 for node in reversed(nodes)]
214 return changesets
214 return changesets
215
215
216 def get_file_annotate(self, path):
216 def get_file_annotate(self, path):
217 """
217 """
218 Returns a list of three element tuples with lineno,changeset and line
218 Returns a list of three element tuples with lineno,changeset and line
219 """
219 """
220 fctx = self._get_filectx(path)
220 fctx = self._get_filectx(path)
221 annotate = []
221 annotate = []
222 for i, annotate_data in enumerate(fctx.annotate()):
222 for i, annotate_data in enumerate(fctx.annotate()):
223 ln_no = i + 1
223 ln_no = i + 1
224 annotate.append((ln_no, self.repository\
224 annotate.append((ln_no, self.repository\
225 .get_changeset(hex(annotate_data[0].node())),
225 .get_changeset(hex(annotate_data[0].node())),
226 annotate_data[1],))
226 annotate_data[1],))
227
227
228 return annotate
228 return annotate
229
229
230 def fill_archive(self, stream=None, kind='tgz', prefix=None,
230 def fill_archive(self, stream=None, kind='tgz', prefix=None,
231 subrepos=False):
231 subrepos=False):
232 """
232 """
233 Fills up given stream.
233 Fills up given stream.
234
234
235 :param stream: file like object.
235 :param stream: file like object.
236 :param kind: one of following: ``zip``, ``tgz`` or ``tbz2``.
236 :param kind: one of following: ``zip``, ``tgz`` or ``tbz2``.
237 Default: ``tgz``.
237 Default: ``tgz``.
238 :param prefix: name of root directory in archive.
238 :param prefix: name of root directory in archive.
239 Default is repository name and changeset's raw_id joined with dash
239 Default is repository name and changeset's raw_id joined with dash
240 (``repo-tip.<KIND>``).
240 (``repo-tip.<KIND>``).
241 :param subrepos: include subrepos in this archive.
241 :param subrepos: include subrepos in this archive.
242
242
243 :raise ImproperArchiveTypeError: If given kind is wrong.
243 :raise ImproperArchiveTypeError: If given kind is wrong.
244 :raise VcsError: If given stream is None
244 :raise VcsError: If given stream is None
245 """
245 """
246
246
247 allowed_kinds = settings.ARCHIVE_SPECS.keys()
247 allowed_kinds = settings.ARCHIVE_SPECS.keys()
248 if kind not in allowed_kinds:
248 if kind not in allowed_kinds:
249 raise ImproperArchiveTypeError('Archive kind not supported use one'
249 raise ImproperArchiveTypeError('Archive kind not supported use one'
250 'of %s', allowed_kinds)
250 'of %s', allowed_kinds)
251
251
252 if stream is None:
252 if stream is None:
253 raise VCSError('You need to pass in a valid stream for filling'
253 raise VCSError('You need to pass in a valid stream for filling'
254 ' with archival data')
254 ' with archival data')
255
255
256 if prefix is None:
256 if prefix is None:
257 prefix = '%s-%s' % (self.repository.name, self.short_id)
257 prefix = '%s-%s' % (self.repository.name, self.short_id)
258 elif prefix.startswith('/'):
258 elif prefix.startswith('/'):
259 raise VCSError("Prefix cannot start with leading slash")
259 raise VCSError("Prefix cannot start with leading slash")
260 elif prefix.strip() == '':
260 elif prefix.strip() == '':
261 raise VCSError("Prefix cannot be empty")
261 raise VCSError("Prefix cannot be empty")
262
262
263 archival.archive(self.repository._repo, stream, self.raw_id,
263 archival.archive(self.repository._repo, stream, self.raw_id,
264 kind, prefix=prefix, subrepos=subrepos)
264 kind, prefix=prefix, subrepos=subrepos)
265
265
266 #stream.close()
267
268 if stream.closed and hasattr(stream, 'name'):
266 if stream.closed and hasattr(stream, 'name'):
269 stream = open(stream.name, 'rb')
267 stream = open(stream.name, 'rb')
270 elif hasattr(stream, 'mode') and 'r' not in stream.mode:
268 elif hasattr(stream, 'mode') and 'r' not in stream.mode:
271 stream = open(stream.name, 'rb')
269 stream = open(stream.name, 'rb')
272 else:
270 else:
273 stream.seek(0)
271 stream.seek(0)
274
272
275 def get_nodes(self, path):
273 def get_nodes(self, path):
276 """
274 """
277 Returns combined ``DirNode`` and ``FileNode`` objects list representing
275 Returns combined ``DirNode`` and ``FileNode`` objects list representing
278 state of changeset at the given ``path``. If node at the given ``path``
276 state of changeset at the given ``path``. If node at the given ``path``
279 is not instance of ``DirNode``, ChangesetError would be raised.
277 is not instance of ``DirNode``, ChangesetError would be raised.
280 """
278 """
281
279
282 if self._get_kind(path) != NodeKind.DIR:
280 if self._get_kind(path) != NodeKind.DIR:
283 raise ChangesetError("Directory does not exist for revision %r at "
281 raise ChangesetError("Directory does not exist for revision %r at "
284 " %r" % (self.revision, path))
282 " %r" % (self.revision, path))
285 path = self._fix_path(path)
283 path = self._fix_path(path)
286
284
287 filenodes = [FileNode(f, changeset=self) for f in self._file_paths
285 filenodes = [FileNode(f, changeset=self) for f in self._file_paths
288 if os.path.dirname(f) == path]
286 if os.path.dirname(f) == path]
289 dirs = path == '' and '' or [d for d in self._dir_paths
287 dirs = path == '' and '' or [d for d in self._dir_paths
290 if d and posixpath.dirname(d) == path]
288 if d and posixpath.dirname(d) == path]
291 dirnodes = [DirNode(d, changeset=self) for d in dirs
289 dirnodes = [DirNode(d, changeset=self) for d in dirs
292 if os.path.dirname(d) == path]
290 if os.path.dirname(d) == path]
293
291
294 als = self.repository.alias
292 als = self.repository.alias
295 for k, vals in self._extract_submodules().iteritems():
293 for k, vals in self._extract_submodules().iteritems():
296 #vals = url,rev,type
294 #vals = url,rev,type
297 loc = vals[0]
295 loc = vals[0]
298 cs = vals[1]
296 cs = vals[1]
299 dirnodes.append(SubModuleNode(k, url=loc, changeset=cs,
297 dirnodes.append(SubModuleNode(k, url=loc, changeset=cs,
300 alias=als))
298 alias=als))
301 nodes = dirnodes + filenodes
299 nodes = dirnodes + filenodes
302 # cache nodes
300 # cache nodes
303 for node in nodes:
301 for node in nodes:
304 self.nodes[node.path] = node
302 self.nodes[node.path] = node
305 nodes.sort()
303 nodes.sort()
306
304
307 return nodes
305 return nodes
308
306
309 def get_node(self, path):
307 def get_node(self, path):
310 """
308 """
311 Returns ``Node`` object from the given ``path``. If there is no node at
309 Returns ``Node`` object from the given ``path``. If there is no node at
312 the given ``path``, ``ChangesetError`` would be raised.
310 the given ``path``, ``ChangesetError`` would be raised.
313 """
311 """
314
312
315 path = self._fix_path(path)
313 path = self._fix_path(path)
316
314
317 if not path in self.nodes:
315 if not path in self.nodes:
318 if path in self._file_paths:
316 if path in self._file_paths:
319 node = FileNode(path, changeset=self)
317 node = FileNode(path, changeset=self)
320 elif path in self._dir_paths or path in self._dir_paths:
318 elif path in self._dir_paths or path in self._dir_paths:
321 if path == '':
319 if path == '':
322 node = RootNode(changeset=self)
320 node = RootNode(changeset=self)
323 else:
321 else:
324 node = DirNode(path, changeset=self)
322 node = DirNode(path, changeset=self)
325 else:
323 else:
326 raise NodeDoesNotExistError("There is no file nor directory "
324 raise NodeDoesNotExistError("There is no file nor directory "
327 "at the given path: %r at revision %r"
325 "at the given path: %r at revision %r"
328 % (path, self.short_id))
326 % (path, self.short_id))
329 # cache node
327 # cache node
330 self.nodes[path] = node
328 self.nodes[path] = node
331 return self.nodes[path]
329 return self.nodes[path]
332
330
333 @LazyProperty
331 @LazyProperty
334 def affected_files(self):
332 def affected_files(self):
335 """
333 """
336 Get's a fast accessible file changes for given changeset
334 Get's a fast accessible file changes for given changeset
337 """
335 """
338 return self._ctx.files()
336 return self._ctx.files()
339
337
340 @property
338 @property
341 def added(self):
339 def added(self):
342 """
340 """
343 Returns list of added ``FileNode`` objects.
341 Returns list of added ``FileNode`` objects.
344 """
342 """
345 return AddedFileNodesGenerator([n for n in self.status[1]], self)
343 return AddedFileNodesGenerator([n for n in self.status[1]], self)
346
344
347 @property
345 @property
348 def changed(self):
346 def changed(self):
349 """
347 """
350 Returns list of modified ``FileNode`` objects.
348 Returns list of modified ``FileNode`` objects.
351 """
349 """
352 return ChangedFileNodesGenerator([n for n in self.status[0]], self)
350 return ChangedFileNodesGenerator([n for n in self.status[0]], self)
353
351
354 @property
352 @property
355 def removed(self):
353 def removed(self):
356 """
354 """
357 Returns list of removed ``FileNode`` objects.
355 Returns list of removed ``FileNode`` objects.
358 """
356 """
359 return RemovedFileNodesGenerator([n for n in self.status[2]], self)
357 return RemovedFileNodesGenerator([n for n in self.status[2]], self)
General Comments 0
You need to be logged in to leave comments. Login now