##// END OF EJS Templates
fixed raw_changeset for git, accidentally it was generated with hg patch headers...
marcink -
r2083:60115135 beta
parent child Browse files
Show More
@@ -1,367 +1,373 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.controllers.changeset
3 rhodecode.controllers.changeset
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 changeset controller for pylons showoing changes beetween
6 changeset controller for pylons showoing changes beetween
7 revisions
7 revisions
8
8
9 :created_on: Apr 25, 2010
9 :created_on: Apr 25, 2010
10 :author: marcink
10 :author: marcink
11 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
12 :license: GPLv3, see COPYING for more details.
12 :license: GPLv3, see COPYING for more details.
13 """
13 """
14 # This program is free software: you can redistribute it and/or modify
14 # This program is free software: you can redistribute it and/or modify
15 # it under the terms of the GNU General Public License as published by
15 # it under the terms of the GNU General Public License as published by
16 # the Free Software Foundation, either version 3 of the License, or
16 # the Free Software Foundation, either version 3 of the License, or
17 # (at your option) any later version.
17 # (at your option) any later version.
18 #
18 #
19 # This program is distributed in the hope that it will be useful,
19 # This program is distributed in the hope that it will be useful,
20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 # GNU General Public License for more details.
22 # GNU General Public License for more details.
23 #
23 #
24 # You should have received a copy of the GNU General Public License
24 # You should have received a copy of the GNU General Public License
25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
26 import logging
26 import logging
27 import traceback
27 import traceback
28 from collections import defaultdict
28 from collections import defaultdict
29 from webob.exc import HTTPForbidden
29 from webob.exc import HTTPForbidden
30
30
31 from pylons import tmpl_context as c, url, request, response
31 from pylons import tmpl_context as c, url, request, response
32 from pylons.i18n.translation import _
32 from pylons.i18n.translation import _
33 from pylons.controllers.util import redirect
33 from pylons.controllers.util import redirect
34 from pylons.decorators import jsonify
34 from pylons.decorators import jsonify
35
35
36 from rhodecode.lib.vcs.exceptions import RepositoryError, ChangesetError, \
36 from rhodecode.lib.vcs.exceptions import RepositoryError, ChangesetError, \
37 ChangesetDoesNotExistError
37 ChangesetDoesNotExistError
38 from rhodecode.lib.vcs.nodes import FileNode
38 from rhodecode.lib.vcs.nodes import FileNode
39
39
40 import rhodecode.lib.helpers as h
40 import rhodecode.lib.helpers as h
41 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
41 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
42 from rhodecode.lib.base import BaseRepoController, render
42 from rhodecode.lib.base import BaseRepoController, render
43 from rhodecode.lib.utils import EmptyChangeset
43 from rhodecode.lib.utils import EmptyChangeset
44 from rhodecode.lib.compat import OrderedDict
44 from rhodecode.lib.compat import OrderedDict
45 from rhodecode.lib import diffs
45 from rhodecode.lib import diffs
46 from rhodecode.model.db import ChangesetComment
46 from rhodecode.model.db import ChangesetComment
47 from rhodecode.model.comment import ChangesetCommentsModel
47 from rhodecode.model.comment import ChangesetCommentsModel
48 from rhodecode.model.meta import Session
48 from rhodecode.model.meta import Session
49 from rhodecode.lib.diffs import wrapped_diff
49 from rhodecode.lib.diffs import wrapped_diff
50
50
51 log = logging.getLogger(__name__)
51 log = logging.getLogger(__name__)
52
52
53
53
54 def anchor_url(revision, path):
54 def anchor_url(revision, path):
55 fid = h.FID(revision, path)
55 fid = h.FID(revision, path)
56 return h.url.current(anchor=fid, **dict(request.GET))
56 return h.url.current(anchor=fid, **dict(request.GET))
57
57
58
58
59 def get_ignore_ws(fid, GET):
59 def get_ignore_ws(fid, GET):
60 ig_ws_global = request.GET.get('ignorews')
60 ig_ws_global = request.GET.get('ignorews')
61 ig_ws = filter(lambda k: k.startswith('WS'), GET.getall(fid))
61 ig_ws = filter(lambda k: k.startswith('WS'), GET.getall(fid))
62 if ig_ws:
62 if ig_ws:
63 try:
63 try:
64 return int(ig_ws[0].split(':')[-1])
64 return int(ig_ws[0].split(':')[-1])
65 except:
65 except:
66 pass
66 pass
67 return ig_ws_global
67 return ig_ws_global
68
68
69
69
70 def _ignorews_url(fileid=None):
70 def _ignorews_url(fileid=None):
71
71
72 params = defaultdict(list)
72 params = defaultdict(list)
73 lbl = _('show white space')
73 lbl = _('show white space')
74 ig_ws = get_ignore_ws(fileid, request.GET)
74 ig_ws = get_ignore_ws(fileid, request.GET)
75 ln_ctx = get_line_ctx(fileid, request.GET)
75 ln_ctx = get_line_ctx(fileid, request.GET)
76 # global option
76 # global option
77 if fileid is None:
77 if fileid is None:
78 if ig_ws is None:
78 if ig_ws is None:
79 params['ignorews'] += [1]
79 params['ignorews'] += [1]
80 lbl = _('ignore white space')
80 lbl = _('ignore white space')
81 ctx_key = 'context'
81 ctx_key = 'context'
82 ctx_val = ln_ctx
82 ctx_val = ln_ctx
83 # per file options
83 # per file options
84 else:
84 else:
85 if ig_ws is None:
85 if ig_ws is None:
86 params[fileid] += ['WS:1']
86 params[fileid] += ['WS:1']
87 lbl = _('ignore white space')
87 lbl = _('ignore white space')
88
88
89 ctx_key = fileid
89 ctx_key = fileid
90 ctx_val = 'C:%s' % ln_ctx
90 ctx_val = 'C:%s' % ln_ctx
91 # if we have passed in ln_ctx pass it along to our params
91 # if we have passed in ln_ctx pass it along to our params
92 if ln_ctx:
92 if ln_ctx:
93 params[ctx_key] += [ctx_val]
93 params[ctx_key] += [ctx_val]
94
94
95 params['anchor'] = fileid
95 params['anchor'] = fileid
96 img = h.image(h.url('/images/icons/text_strikethrough.png'), lbl, class_='icon')
96 img = h.image(h.url('/images/icons/text_strikethrough.png'), lbl, class_='icon')
97 return h.link_to(img, h.url.current(**params), title=lbl, class_='tooltip')
97 return h.link_to(img, h.url.current(**params), title=lbl, class_='tooltip')
98
98
99
99
100 def get_line_ctx(fid, GET):
100 def get_line_ctx(fid, GET):
101 ln_ctx_global = request.GET.get('context')
101 ln_ctx_global = request.GET.get('context')
102 ln_ctx = filter(lambda k: k.startswith('C'), GET.getall(fid))
102 ln_ctx = filter(lambda k: k.startswith('C'), GET.getall(fid))
103
103
104 if ln_ctx:
104 if ln_ctx:
105 retval = ln_ctx[0].split(':')[-1]
105 retval = ln_ctx[0].split(':')[-1]
106 else:
106 else:
107 retval = ln_ctx_global
107 retval = ln_ctx_global
108
108
109 try:
109 try:
110 return int(retval)
110 return int(retval)
111 except:
111 except:
112 return
112 return
113
113
114
114
115 def _context_url(fileid=None):
115 def _context_url(fileid=None):
116 """
116 """
117 Generates url for context lines
117 Generates url for context lines
118
118
119 :param fileid:
119 :param fileid:
120 """
120 """
121 ig_ws = get_ignore_ws(fileid, request.GET)
121 ig_ws = get_ignore_ws(fileid, request.GET)
122 ln_ctx = (get_line_ctx(fileid, request.GET) or 3) * 2
122 ln_ctx = (get_line_ctx(fileid, request.GET) or 3) * 2
123
123
124 params = defaultdict(list)
124 params = defaultdict(list)
125
125
126 # global option
126 # global option
127 if fileid is None:
127 if fileid is None:
128 if ln_ctx > 0:
128 if ln_ctx > 0:
129 params['context'] += [ln_ctx]
129 params['context'] += [ln_ctx]
130
130
131 if ig_ws:
131 if ig_ws:
132 ig_ws_key = 'ignorews'
132 ig_ws_key = 'ignorews'
133 ig_ws_val = 1
133 ig_ws_val = 1
134
134
135 # per file option
135 # per file option
136 else:
136 else:
137 params[fileid] += ['C:%s' % ln_ctx]
137 params[fileid] += ['C:%s' % ln_ctx]
138 ig_ws_key = fileid
138 ig_ws_key = fileid
139 ig_ws_val = 'WS:%s' % 1
139 ig_ws_val = 'WS:%s' % 1
140
140
141 if ig_ws:
141 if ig_ws:
142 params[ig_ws_key] += [ig_ws_val]
142 params[ig_ws_key] += [ig_ws_val]
143
143
144 lbl = _('%s line context') % ln_ctx
144 lbl = _('%s line context') % ln_ctx
145
145
146 params['anchor'] = fileid
146 params['anchor'] = fileid
147 img = h.image(h.url('/images/icons/table_add.png'), lbl, class_='icon')
147 img = h.image(h.url('/images/icons/table_add.png'), lbl, class_='icon')
148 return h.link_to(img, h.url.current(**params), title=lbl, class_='tooltip')
148 return h.link_to(img, h.url.current(**params), title=lbl, class_='tooltip')
149
149
150
150
151 class ChangesetController(BaseRepoController):
151 class ChangesetController(BaseRepoController):
152
152
153 @LoginRequired()
153 @LoginRequired()
154 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
154 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
155 'repository.admin')
155 'repository.admin')
156 def __before__(self):
156 def __before__(self):
157 super(ChangesetController, self).__before__()
157 super(ChangesetController, self).__before__()
158 c.affected_files_cut_off = 60
158 c.affected_files_cut_off = 60
159
159
160 def index(self, revision):
160 def index(self, revision):
161
161
162 c.anchor_url = anchor_url
162 c.anchor_url = anchor_url
163 c.ignorews_url = _ignorews_url
163 c.ignorews_url = _ignorews_url
164 c.context_url = _context_url
164 c.context_url = _context_url
165
165
166 #get ranges of revisions if preset
166 #get ranges of revisions if preset
167 rev_range = revision.split('...')[:2]
167 rev_range = revision.split('...')[:2]
168 enable_comments = True
168 enable_comments = True
169 try:
169 try:
170 if len(rev_range) == 2:
170 if len(rev_range) == 2:
171 enable_comments = False
171 enable_comments = False
172 rev_start = rev_range[0]
172 rev_start = rev_range[0]
173 rev_end = rev_range[1]
173 rev_end = rev_range[1]
174 rev_ranges = c.rhodecode_repo.get_changesets(start=rev_start,
174 rev_ranges = c.rhodecode_repo.get_changesets(start=rev_start,
175 end=rev_end)
175 end=rev_end)
176 else:
176 else:
177 rev_ranges = [c.rhodecode_repo.get_changeset(revision)]
177 rev_ranges = [c.rhodecode_repo.get_changeset(revision)]
178
178
179 c.cs_ranges = list(rev_ranges)
179 c.cs_ranges = list(rev_ranges)
180 if not c.cs_ranges:
180 if not c.cs_ranges:
181 raise RepositoryError('Changeset range returned empty result')
181 raise RepositoryError('Changeset range returned empty result')
182
182
183 except (RepositoryError, ChangesetDoesNotExistError, Exception), e:
183 except (RepositoryError, ChangesetDoesNotExistError, Exception), e:
184 log.error(traceback.format_exc())
184 log.error(traceback.format_exc())
185 h.flash(str(e), category='warning')
185 h.flash(str(e), category='warning')
186 return redirect(url('home'))
186 return redirect(url('home'))
187
187
188 c.changes = OrderedDict()
188 c.changes = OrderedDict()
189
189
190 c.lines_added = 0 # count of lines added
190 c.lines_added = 0 # count of lines added
191 c.lines_deleted = 0 # count of lines removes
191 c.lines_deleted = 0 # count of lines removes
192
192
193 cumulative_diff = 0
193 cumulative_diff = 0
194 c.cut_off = False # defines if cut off limit is reached
194 c.cut_off = False # defines if cut off limit is reached
195
195
196 c.comments = []
196 c.comments = []
197 c.inline_comments = []
197 c.inline_comments = []
198 c.inline_cnt = 0
198 c.inline_cnt = 0
199 # Iterate over ranges (default changeset view is always one changeset)
199 # Iterate over ranges (default changeset view is always one changeset)
200 for changeset in c.cs_ranges:
200 for changeset in c.cs_ranges:
201 c.comments.extend(ChangesetCommentsModel()\
201 c.comments.extend(ChangesetCommentsModel()\
202 .get_comments(c.rhodecode_db_repo.repo_id,
202 .get_comments(c.rhodecode_db_repo.repo_id,
203 changeset.raw_id))
203 changeset.raw_id))
204 inlines = ChangesetCommentsModel()\
204 inlines = ChangesetCommentsModel()\
205 .get_inline_comments(c.rhodecode_db_repo.repo_id,
205 .get_inline_comments(c.rhodecode_db_repo.repo_id,
206 changeset.raw_id)
206 changeset.raw_id)
207 c.inline_comments.extend(inlines)
207 c.inline_comments.extend(inlines)
208 c.changes[changeset.raw_id] = []
208 c.changes[changeset.raw_id] = []
209 try:
209 try:
210 changeset_parent = changeset.parents[0]
210 changeset_parent = changeset.parents[0]
211 except IndexError:
211 except IndexError:
212 changeset_parent = None
212 changeset_parent = None
213
213
214 #==================================================================
214 #==================================================================
215 # ADDED FILES
215 # ADDED FILES
216 #==================================================================
216 #==================================================================
217 for node in changeset.added:
217 for node in changeset.added:
218 fid = h.FID(revision, node.path)
218 fid = h.FID(revision, node.path)
219 line_context_lcl = get_line_ctx(fid, request.GET)
219 line_context_lcl = get_line_ctx(fid, request.GET)
220 ign_whitespace_lcl = get_ignore_ws(fid, request.GET)
220 ign_whitespace_lcl = get_ignore_ws(fid, request.GET)
221 lim = self.cut_off_limit
221 lim = self.cut_off_limit
222 if cumulative_diff > self.cut_off_limit:
222 if cumulative_diff > self.cut_off_limit:
223 lim = -1
223 lim = -1
224 size, cs1, cs2, diff, st = wrapped_diff(filenode_old=None,
224 size, cs1, cs2, diff, st = wrapped_diff(
225 filenode_new=node,
225 filenode_old=None,
226 cut_off_limit=lim,
226 filenode_new=node,
227 ignore_whitespace=ign_whitespace_lcl,
227 cut_off_limit=lim,
228 line_context=line_context_lcl,
228 ignore_whitespace=ign_whitespace_lcl,
229 enable_comments=enable_comments)
229 line_context=line_context_lcl,
230 enable_comments=enable_comments
231 )
230 cumulative_diff += size
232 cumulative_diff += size
231 c.lines_added += st[0]
233 c.lines_added += st[0]
232 c.lines_deleted += st[1]
234 c.lines_deleted += st[1]
233 c.changes[changeset.raw_id].append(('added', node, diff,
235 c.changes[changeset.raw_id].append(
234 cs1, cs2, st))
236 ('added', node, diff, cs1, cs2, st)
237 )
235
238
236 #==================================================================
239 #==================================================================
237 # CHANGED FILES
240 # CHANGED FILES
238 #==================================================================
241 #==================================================================
239 for node in changeset.changed:
242 for node in changeset.changed:
240 try:
243 try:
241 filenode_old = changeset_parent.get_node(node.path)
244 filenode_old = changeset_parent.get_node(node.path)
242 except ChangesetError:
245 except ChangesetError:
243 log.warning('Unable to fetch parent node for diff')
246 log.warning('Unable to fetch parent node for diff')
244 filenode_old = FileNode(node.path, '', EmptyChangeset())
247 filenode_old = FileNode(node.path, '', EmptyChangeset())
245
248
246 fid = h.FID(revision, node.path)
249 fid = h.FID(revision, node.path)
247 line_context_lcl = get_line_ctx(fid, request.GET)
250 line_context_lcl = get_line_ctx(fid, request.GET)
248 ign_whitespace_lcl = get_ignore_ws(fid, request.GET)
251 ign_whitespace_lcl = get_ignore_ws(fid, request.GET)
249 lim = self.cut_off_limit
252 lim = self.cut_off_limit
250 if cumulative_diff > self.cut_off_limit:
253 if cumulative_diff > self.cut_off_limit:
251 lim = -1
254 lim = -1
252 size, cs1, cs2, diff, st = wrapped_diff(filenode_old=filenode_old,
255 size, cs1, cs2, diff, st = wrapped_diff(
253 filenode_new=node,
256 filenode_old=filenode_old,
254 cut_off_limit=lim,
257 filenode_new=node,
255 ignore_whitespace=ign_whitespace_lcl,
258 cut_off_limit=lim,
256 line_context=line_context_lcl,
259 ignore_whitespace=ign_whitespace_lcl,
257 enable_comments=enable_comments)
260 line_context=line_context_lcl,
261 enable_comments=enable_comments
262 )
258 cumulative_diff += size
263 cumulative_diff += size
259 c.lines_added += st[0]
264 c.lines_added += st[0]
260 c.lines_deleted += st[1]
265 c.lines_deleted += st[1]
261 c.changes[changeset.raw_id].append(('changed', node, diff,
266 c.changes[changeset.raw_id].append(
262 cs1, cs2, st))
267 ('changed', node, diff, cs1, cs2, st)
263
268 )
264 #==================================================================
269 #==================================================================
265 # REMOVED FILES
270 # REMOVED FILES
266 #==================================================================
271 #==================================================================
267 for node in changeset.removed:
272 for node in changeset.removed:
268 c.changes[changeset.raw_id].append(('removed', node, None,
273 c.changes[changeset.raw_id].append(
269 None, None, (0, 0)))
274 ('removed', node, None, None, None, (0, 0))
275 )
270
276
271 # count inline comments
277 # count inline comments
272 for path, lines in c.inline_comments:
278 for path, lines in c.inline_comments:
273 for comments in lines.values():
279 for comments in lines.values():
274 c.inline_cnt += len(comments)
280 c.inline_cnt += len(comments)
275
281
276 if len(c.cs_ranges) == 1:
282 if len(c.cs_ranges) == 1:
277 c.changeset = c.cs_ranges[0]
283 c.changeset = c.cs_ranges[0]
278 c.changes = c.changes[c.changeset.raw_id]
284 c.changes = c.changes[c.changeset.raw_id]
279
285
280 return render('changeset/changeset.html')
286 return render('changeset/changeset.html')
281 else:
287 else:
282 return render('changeset/changeset_range.html')
288 return render('changeset/changeset_range.html')
283
289
284 def raw_changeset(self, revision):
290 def raw_changeset(self, revision):
285
291
286 method = request.GET.get('diff', 'show')
292 method = request.GET.get('diff', 'show')
287 ignore_whitespace = request.GET.get('ignorews') == '1'
293 ignore_whitespace = request.GET.get('ignorews') == '1'
288 line_context = request.GET.get('context', 3)
294 line_context = request.GET.get('context', 3)
289 try:
295 try:
290 c.scm_type = c.rhodecode_repo.alias
296 c.scm_type = c.rhodecode_repo.alias
291 c.changeset = c.rhodecode_repo.get_changeset(revision)
297 c.changeset = c.rhodecode_repo.get_changeset(revision)
292 except RepositoryError:
298 except RepositoryError:
293 log.error(traceback.format_exc())
299 log.error(traceback.format_exc())
294 return redirect(url('home'))
300 return redirect(url('home'))
295 else:
301 else:
296 try:
302 try:
297 c.changeset_parent = c.changeset.parents[0]
303 c.changeset_parent = c.changeset.parents[0]
298 except IndexError:
304 except IndexError:
299 c.changeset_parent = None
305 c.changeset_parent = None
300 c.changes = []
306 c.changes = []
301
307
302 for node in c.changeset.added:
308 for node in c.changeset.added:
303 filenode_old = FileNode(node.path, '')
309 filenode_old = FileNode(node.path, '')
304 if filenode_old.is_binary or node.is_binary:
310 if filenode_old.is_binary or node.is_binary:
305 diff = _('binary file') + '\n'
311 diff = _('binary file') + '\n'
306 else:
312 else:
307 f_gitdiff = diffs.get_gitdiff(filenode_old, node,
313 f_gitdiff = diffs.get_gitdiff(filenode_old, node,
308 ignore_whitespace=ignore_whitespace,
314 ignore_whitespace=ignore_whitespace,
309 context=line_context)
315 context=line_context)
310 diff = diffs.DiffProcessor(f_gitdiff,
316 diff = diffs.DiffProcessor(f_gitdiff,
311 format='gitdiff').raw_diff()
317 format='gitdiff').raw_diff()
312
318
313 cs1 = None
319 cs1 = None
314 cs2 = node.last_changeset.raw_id
320 cs2 = node.last_changeset.raw_id
315 c.changes.append(('added', node, diff, cs1, cs2))
321 c.changes.append(('added', node, diff, cs1, cs2))
316
322
317 for node in c.changeset.changed:
323 for node in c.changeset.changed:
318 filenode_old = c.changeset_parent.get_node(node.path)
324 filenode_old = c.changeset_parent.get_node(node.path)
319 if filenode_old.is_binary or node.is_binary:
325 if filenode_old.is_binary or node.is_binary:
320 diff = _('binary file')
326 diff = _('binary file')
321 else:
327 else:
322 f_gitdiff = diffs.get_gitdiff(filenode_old, node,
328 f_gitdiff = diffs.get_gitdiff(filenode_old, node,
323 ignore_whitespace=ignore_whitespace,
329 ignore_whitespace=ignore_whitespace,
324 context=line_context)
330 context=line_context)
325 diff = diffs.DiffProcessor(f_gitdiff,
331 diff = diffs.DiffProcessor(f_gitdiff,
326 format='gitdiff').raw_diff()
332 format='gitdiff').raw_diff()
327
333
328 cs1 = filenode_old.last_changeset.raw_id
334 cs1 = filenode_old.last_changeset.raw_id
329 cs2 = node.last_changeset.raw_id
335 cs2 = node.last_changeset.raw_id
330 c.changes.append(('changed', node, diff, cs1, cs2))
336 c.changes.append(('changed', node, diff, cs1, cs2))
331
337
332 response.content_type = 'text/plain'
338 response.content_type = 'text/plain'
333
339
334 if method == 'download':
340 if method == 'download':
335 response.content_disposition = 'attachment; filename=%s.patch' \
341 response.content_disposition = 'attachment; filename=%s.patch' \
336 % revision
342 % revision
337
343
338 c.parent_tmpl = ''.join(['# Parent %s\n' % x.raw_id for x in
344 c.parent_tmpl = ''.join(['# Parent %s\n' % x.raw_id
339 c.changeset.parents])
345 for x in c.changeset.parents])
340
346
341 c.diffs = ''
347 c.diffs = ''
342 for x in c.changes:
348 for x in c.changes:
343 c.diffs += x[2]
349 c.diffs += x[2]
344
350
345 return render('changeset/raw_changeset.html')
351 return render('changeset/raw_changeset.html')
346
352
347 def comment(self, repo_name, revision):
353 def comment(self, repo_name, revision):
348 ChangesetCommentsModel().create(text=request.POST.get('text'),
354 ChangesetCommentsModel().create(text=request.POST.get('text'),
349 repo_id=c.rhodecode_db_repo.repo_id,
355 repo_id=c.rhodecode_db_repo.repo_id,
350 user_id=c.rhodecode_user.user_id,
356 user_id=c.rhodecode_user.user_id,
351 revision=revision,
357 revision=revision,
352 f_path=request.POST.get('f_path'),
358 f_path=request.POST.get('f_path'),
353 line_no=request.POST.get('line'))
359 line_no=request.POST.get('line'))
354 Session.commit()
360 Session.commit()
355 return redirect(h.url('changeset_home', repo_name=repo_name,
361 return redirect(h.url('changeset_home', repo_name=repo_name,
356 revision=revision))
362 revision=revision))
357
363
358 @jsonify
364 @jsonify
359 def delete_comment(self, repo_name, comment_id):
365 def delete_comment(self, repo_name, comment_id):
360 co = ChangesetComment.get(comment_id)
366 co = ChangesetComment.get(comment_id)
361 owner = lambda: co.author.user_id == c.rhodecode_user.user_id
367 owner = lambda: co.author.user_id == c.rhodecode_user.user_id
362 if h.HasPermissionAny('hg.admin', 'repository.admin')() or owner:
368 if h.HasPermissionAny('hg.admin', 'repository.admin')() or owner:
363 ChangesetCommentsModel().delete(comment=co)
369 ChangesetCommentsModel().delete(comment=co)
364 Session.commit()
370 Session.commit()
365 return True
371 return True
366 else:
372 else:
367 raise HTTPForbidden()
373 raise HTTPForbidden()
@@ -1,493 +1,494 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
29
30 from pylons import request, response, tmpl_context as c, url
30 from pylons import request, response, tmpl_context as c, url
31 from pylons.i18n.translation import _
31 from pylons.i18n.translation import _
32 from pylons.controllers.util import redirect
32 from pylons.controllers.util import redirect
33 from pylons.decorators import jsonify
33 from pylons.decorators import jsonify
34
34
35 from rhodecode.lib.vcs.conf import settings
35 from rhodecode.lib.vcs.conf import settings
36 from rhodecode.lib.vcs.exceptions import RepositoryError, ChangesetDoesNotExistError, \
36 from rhodecode.lib.vcs.exceptions import RepositoryError, ChangesetDoesNotExistError, \
37 EmptyRepositoryError, ImproperArchiveTypeError, VCSError, \
37 EmptyRepositoryError, ImproperArchiveTypeError, VCSError, \
38 NodeAlreadyExistsError
38 NodeAlreadyExistsError
39 from rhodecode.lib.vcs.nodes import FileNode
39 from rhodecode.lib.vcs.nodes import FileNode
40
40
41 from rhodecode.lib.compat import OrderedDict
41 from rhodecode.lib.compat import OrderedDict
42 from rhodecode.lib import convert_line_endings, detect_mode, safe_str
42 from rhodecode.lib import convert_line_endings, detect_mode, safe_str
43 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
43 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
44 from rhodecode.lib.base import BaseRepoController, render
44 from rhodecode.lib.base import BaseRepoController, render
45 from rhodecode.lib.utils import EmptyChangeset
45 from rhodecode.lib.utils import EmptyChangeset
46 from rhodecode.lib import diffs
46 from rhodecode.lib import diffs
47 import rhodecode.lib.helpers as h
47 import rhodecode.lib.helpers as h
48 from rhodecode.model.repo import RepoModel
48 from rhodecode.model.repo import RepoModel
49 from rhodecode.controllers.changeset import anchor_url, _ignorews_url,\
49 from rhodecode.controllers.changeset import anchor_url, _ignorews_url,\
50 _context_url, get_line_ctx, get_ignore_ws
50 _context_url, get_line_ctx, get_ignore_ws
51 from rhodecode.lib.diffs import wrapped_diff
51 from rhodecode.lib.diffs import wrapped_diff
52 from rhodecode.model.scm import ScmModel
52 from rhodecode.model.scm import ScmModel
53
53
54 log = logging.getLogger(__name__)
54 log = logging.getLogger(__name__)
55
55
56
56
57 class FilesController(BaseRepoController):
57 class FilesController(BaseRepoController):
58
58
59 @LoginRequired()
59 @LoginRequired()
60 def __before__(self):
60 def __before__(self):
61 super(FilesController, self).__before__()
61 super(FilesController, self).__before__()
62 c.cut_off_limit = self.cut_off_limit
62 c.cut_off_limit = self.cut_off_limit
63
63
64 def __get_cs_or_redirect(self, rev, repo_name, redirect_after=True):
64 def __get_cs_or_redirect(self, rev, repo_name, redirect_after=True):
65 """
65 """
66 Safe way to get changeset if error occur it redirects to tip with
66 Safe way to get changeset if error occur it redirects to tip with
67 proper message
67 proper message
68
68
69 :param rev: revision to fetch
69 :param rev: revision to fetch
70 :param repo_name: repo name to redirect after
70 :param repo_name: repo name to redirect after
71 """
71 """
72
72
73 try:
73 try:
74 return c.rhodecode_repo.get_changeset(rev)
74 return c.rhodecode_repo.get_changeset(rev)
75 except EmptyRepositoryError, e:
75 except EmptyRepositoryError, e:
76 if not redirect_after:
76 if not redirect_after:
77 return None
77 return None
78 url_ = url('files_add_home',
78 url_ = url('files_add_home',
79 repo_name=c.repo_name,
79 repo_name=c.repo_name,
80 revision=0, f_path='')
80 revision=0, f_path='')
81 add_new = '<a href="%s">[%s]</a>' % (url_, _('add new'))
81 add_new = '<a href="%s">[%s]</a>' % (url_, _('add new'))
82 h.flash(h.literal(_('There are no files yet %s' % add_new)),
82 h.flash(h.literal(_('There are no files yet %s' % add_new)),
83 category='warning')
83 category='warning')
84 redirect(h.url('summary_home', repo_name=repo_name))
84 redirect(h.url('summary_home', repo_name=repo_name))
85
85
86 except RepositoryError, e:
86 except RepositoryError, e:
87 h.flash(str(e), category='warning')
87 h.flash(str(e), category='warning')
88 redirect(h.url('files_home', repo_name=repo_name, revision='tip'))
88 redirect(h.url('files_home', repo_name=repo_name, revision='tip'))
89
89
90 def __get_filenode_or_redirect(self, repo_name, cs, path):
90 def __get_filenode_or_redirect(self, repo_name, cs, path):
91 """
91 """
92 Returns file_node, if error occurs or given path is directory,
92 Returns file_node, if error occurs or given path is directory,
93 it'll redirect to top level path
93 it'll redirect to top level path
94
94
95 :param repo_name: repo_name
95 :param repo_name: repo_name
96 :param cs: given changeset
96 :param cs: given changeset
97 :param path: path to lookup
97 :param path: path to lookup
98 """
98 """
99
99
100 try:
100 try:
101 file_node = cs.get_node(path)
101 file_node = cs.get_node(path)
102 if file_node.is_dir():
102 if file_node.is_dir():
103 raise RepositoryError('given path is a directory')
103 raise RepositoryError('given path is a directory')
104 except RepositoryError, e:
104 except RepositoryError, e:
105 h.flash(str(e), category='warning')
105 h.flash(str(e), category='warning')
106 redirect(h.url('files_home', repo_name=repo_name,
106 redirect(h.url('files_home', repo_name=repo_name,
107 revision=cs.raw_id))
107 revision=cs.raw_id))
108
108
109 return file_node
109 return file_node
110
110
111 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
111 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
112 'repository.admin')
112 'repository.admin')
113 def index(self, repo_name, revision, f_path):
113 def index(self, repo_name, revision, f_path):
114 # redirect to given revision from form if given
114 # redirect to given revision from form if given
115 post_revision = request.POST.get('at_rev', None)
115 post_revision = request.POST.get('at_rev', None)
116 if post_revision:
116 if post_revision:
117 cs = self.__get_cs_or_redirect(post_revision, repo_name)
117 cs = self.__get_cs_or_redirect(post_revision, repo_name)
118 redirect(url('files_home', repo_name=c.repo_name,
118 redirect(url('files_home', repo_name=c.repo_name,
119 revision=cs.raw_id, f_path=f_path))
119 revision=cs.raw_id, f_path=f_path))
120
120
121 c.changeset = self.__get_cs_or_redirect(revision, repo_name)
121 c.changeset = self.__get_cs_or_redirect(revision, repo_name)
122 c.branch = request.GET.get('branch', None)
122 c.branch = request.GET.get('branch', None)
123 c.f_path = f_path
123 c.f_path = f_path
124
124
125 cur_rev = c.changeset.revision
125 cur_rev = c.changeset.revision
126
126
127 # prev link
127 # prev link
128 try:
128 try:
129 prev_rev = c.rhodecode_repo.get_changeset(cur_rev).prev(c.branch)
129 prev_rev = c.rhodecode_repo.get_changeset(cur_rev).prev(c.branch)
130 c.url_prev = url('files_home', repo_name=c.repo_name,
130 c.url_prev = url('files_home', repo_name=c.repo_name,
131 revision=prev_rev.raw_id, f_path=f_path)
131 revision=prev_rev.raw_id, f_path=f_path)
132 if c.branch:
132 if c.branch:
133 c.url_prev += '?branch=%s' % c.branch
133 c.url_prev += '?branch=%s' % c.branch
134 except (ChangesetDoesNotExistError, VCSError):
134 except (ChangesetDoesNotExistError, VCSError):
135 c.url_prev = '#'
135 c.url_prev = '#'
136
136
137 # next link
137 # next link
138 try:
138 try:
139 next_rev = c.rhodecode_repo.get_changeset(cur_rev).next(c.branch)
139 next_rev = c.rhodecode_repo.get_changeset(cur_rev).next(c.branch)
140 c.url_next = url('files_home', repo_name=c.repo_name,
140 c.url_next = url('files_home', repo_name=c.repo_name,
141 revision=next_rev.raw_id, f_path=f_path)
141 revision=next_rev.raw_id, f_path=f_path)
142 if c.branch:
142 if c.branch:
143 c.url_next += '?branch=%s' % c.branch
143 c.url_next += '?branch=%s' % c.branch
144 except (ChangesetDoesNotExistError, VCSError):
144 except (ChangesetDoesNotExistError, VCSError):
145 c.url_next = '#'
145 c.url_next = '#'
146
146
147 # files or dirs
147 # files or dirs
148 try:
148 try:
149 c.file = c.changeset.get_node(f_path)
149 c.file = c.changeset.get_node(f_path)
150
150
151 if c.file.is_file():
151 if c.file.is_file():
152 c.file_history = self._get_node_history(c.changeset, f_path)
152 c.file_history = self._get_node_history(c.changeset, f_path)
153 else:
153 else:
154 c.file_history = []
154 c.file_history = []
155 except RepositoryError, e:
155 except RepositoryError, e:
156 h.flash(str(e), category='warning')
156 h.flash(str(e), category='warning')
157 redirect(h.url('files_home', repo_name=repo_name,
157 redirect(h.url('files_home', repo_name=repo_name,
158 revision=revision))
158 revision=revision))
159
159
160 return render('files/files.html')
160 return render('files/files.html')
161
161
162 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
162 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
163 'repository.admin')
163 'repository.admin')
164 def rawfile(self, repo_name, revision, f_path):
164 def rawfile(self, repo_name, revision, f_path):
165 cs = self.__get_cs_or_redirect(revision, repo_name)
165 cs = self.__get_cs_or_redirect(revision, repo_name)
166 file_node = self.__get_filenode_or_redirect(repo_name, cs, f_path)
166 file_node = self.__get_filenode_or_redirect(repo_name, cs, f_path)
167
167
168 response.content_disposition = 'attachment; filename=%s' % \
168 response.content_disposition = 'attachment; filename=%s' % \
169 safe_str(f_path.split(os.sep)[-1])
169 safe_str(f_path.split(os.sep)[-1])
170
170
171 response.content_type = file_node.mimetype
171 response.content_type = file_node.mimetype
172 return file_node.content
172 return file_node.content
173
173
174 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
174 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
175 'repository.admin')
175 'repository.admin')
176 def raw(self, repo_name, revision, f_path):
176 def raw(self, repo_name, revision, f_path):
177 cs = self.__get_cs_or_redirect(revision, repo_name)
177 cs = self.__get_cs_or_redirect(revision, repo_name)
178 file_node = self.__get_filenode_or_redirect(repo_name, cs, f_path)
178 file_node = self.__get_filenode_or_redirect(repo_name, cs, f_path)
179
179
180 raw_mimetype_mapping = {
180 raw_mimetype_mapping = {
181 # map original mimetype to a mimetype used for "show as raw"
181 # map original mimetype to a mimetype used for "show as raw"
182 # you can also provide a content-disposition to override the
182 # you can also provide a content-disposition to override the
183 # default "attachment" disposition.
183 # default "attachment" disposition.
184 # orig_type: (new_type, new_dispo)
184 # orig_type: (new_type, new_dispo)
185
185
186 # show images inline:
186 # show images inline:
187 'image/x-icon': ('image/x-icon', 'inline'),
187 'image/x-icon': ('image/x-icon', 'inline'),
188 'image/png': ('image/png', 'inline'),
188 'image/png': ('image/png', 'inline'),
189 'image/gif': ('image/gif', 'inline'),
189 'image/gif': ('image/gif', 'inline'),
190 'image/jpeg': ('image/jpeg', 'inline'),
190 'image/jpeg': ('image/jpeg', 'inline'),
191 'image/svg+xml': ('image/svg+xml', 'inline'),
191 'image/svg+xml': ('image/svg+xml', 'inline'),
192 }
192 }
193
193
194 mimetype = file_node.mimetype
194 mimetype = file_node.mimetype
195 try:
195 try:
196 mimetype, dispo = raw_mimetype_mapping[mimetype]
196 mimetype, dispo = raw_mimetype_mapping[mimetype]
197 except KeyError:
197 except KeyError:
198 # we don't know anything special about this, handle it safely
198 # we don't know anything special about this, handle it safely
199 if file_node.is_binary:
199 if file_node.is_binary:
200 # do same as download raw for binary files
200 # do same as download raw for binary files
201 mimetype, dispo = 'application/octet-stream', 'attachment'
201 mimetype, dispo = 'application/octet-stream', 'attachment'
202 else:
202 else:
203 # do not just use the original mimetype, but force text/plain,
203 # do not just use the original mimetype, but force text/plain,
204 # otherwise it would serve text/html and that might be unsafe.
204 # otherwise it would serve text/html and that might be unsafe.
205 # Note: underlying vcs library fakes text/plain mimetype if the
205 # Note: underlying vcs library fakes text/plain mimetype if the
206 # mimetype can not be determined and it thinks it is not
206 # mimetype can not be determined and it thinks it is not
207 # binary.This might lead to erroneous text display in some
207 # binary.This might lead to erroneous text display in some
208 # cases, but helps in other cases, like with text files
208 # cases, but helps in other cases, like with text files
209 # without extension.
209 # without extension.
210 mimetype, dispo = 'text/plain', 'inline'
210 mimetype, dispo = 'text/plain', 'inline'
211
211
212 if dispo == 'attachment':
212 if dispo == 'attachment':
213 dispo = 'attachment; filename=%s' % \
213 dispo = 'attachment; filename=%s' % \
214 safe_str(f_path.split(os.sep)[-1])
214 safe_str(f_path.split(os.sep)[-1])
215
215
216 response.content_disposition = dispo
216 response.content_disposition = dispo
217 response.content_type = mimetype
217 response.content_type = mimetype
218 return file_node.content
218 return file_node.content
219
219
220 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
220 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
221 'repository.admin')
221 'repository.admin')
222 def annotate(self, repo_name, revision, f_path):
222 def annotate(self, repo_name, revision, f_path):
223 c.cs = self.__get_cs_or_redirect(revision, repo_name)
223 c.cs = self.__get_cs_or_redirect(revision, repo_name)
224 c.file = self.__get_filenode_or_redirect(repo_name, c.cs, f_path)
224 c.file = self.__get_filenode_or_redirect(repo_name, c.cs, f_path)
225
225
226 c.file_history = self._get_node_history(c.cs, f_path)
226 c.file_history = self._get_node_history(c.cs, f_path)
227 c.f_path = f_path
227 c.f_path = f_path
228 return render('files/files_annotate.html')
228 return render('files/files_annotate.html')
229
229
230 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
230 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
231 def edit(self, repo_name, revision, f_path):
231 def edit(self, repo_name, revision, f_path):
232 r_post = request.POST
232 r_post = request.POST
233
233
234 c.cs = self.__get_cs_or_redirect(revision, repo_name)
234 c.cs = self.__get_cs_or_redirect(revision, repo_name)
235 c.file = self.__get_filenode_or_redirect(repo_name, c.cs, f_path)
235 c.file = self.__get_filenode_or_redirect(repo_name, c.cs, f_path)
236
236
237 if c.file.is_binary:
237 if c.file.is_binary:
238 return redirect(url('files_home', repo_name=c.repo_name,
238 return redirect(url('files_home', repo_name=c.repo_name,
239 revision=c.cs.raw_id, f_path=f_path))
239 revision=c.cs.raw_id, f_path=f_path))
240
240
241 c.f_path = f_path
241 c.f_path = f_path
242
242
243 if r_post:
243 if r_post:
244
244
245 old_content = c.file.content
245 old_content = c.file.content
246 sl = old_content.splitlines(1)
246 sl = old_content.splitlines(1)
247 first_line = sl[0] if sl else ''
247 first_line = sl[0] if sl else ''
248 # modes: 0 - Unix, 1 - Mac, 2 - DOS
248 # modes: 0 - Unix, 1 - Mac, 2 - DOS
249 mode = detect_mode(first_line, 0)
249 mode = detect_mode(first_line, 0)
250 content = convert_line_endings(r_post.get('content'), mode)
250 content = convert_line_endings(r_post.get('content'), mode)
251
251
252 message = r_post.get('message') or (_('Edited %s via RhodeCode')
252 message = r_post.get('message') or (_('Edited %s via RhodeCode')
253 % (f_path))
253 % (f_path))
254 author = self.rhodecode_user.full_contact
254 author = self.rhodecode_user.full_contact
255
255
256 if content == old_content:
256 if content == old_content:
257 h.flash(_('No changes'),
257 h.flash(_('No changes'),
258 category='warning')
258 category='warning')
259 return redirect(url('changeset_home', repo_name=c.repo_name,
259 return redirect(url('changeset_home', repo_name=c.repo_name,
260 revision='tip'))
260 revision='tip'))
261
261
262 try:
262 try:
263 self.scm_model.commit_change(repo=c.rhodecode_repo,
263 self.scm_model.commit_change(repo=c.rhodecode_repo,
264 repo_name=repo_name, cs=c.cs,
264 repo_name=repo_name, cs=c.cs,
265 user=self.rhodecode_user,
265 user=self.rhodecode_user,
266 author=author, message=message,
266 author=author, message=message,
267 content=content, f_path=f_path)
267 content=content, f_path=f_path)
268 h.flash(_('Successfully committed to %s' % f_path),
268 h.flash(_('Successfully committed to %s' % f_path),
269 category='success')
269 category='success')
270
270
271 except Exception:
271 except Exception:
272 log.error(traceback.format_exc())
272 log.error(traceback.format_exc())
273 h.flash(_('Error occurred during commit'), category='error')
273 h.flash(_('Error occurred during commit'), category='error')
274 return redirect(url('changeset_home',
274 return redirect(url('changeset_home',
275 repo_name=c.repo_name, revision='tip'))
275 repo_name=c.repo_name, revision='tip'))
276
276
277 return render('files/files_edit.html')
277 return render('files/files_edit.html')
278
278
279 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
279 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
280 def add(self, repo_name, revision, f_path):
280 def add(self, repo_name, revision, f_path):
281 r_post = request.POST
281 r_post = request.POST
282 c.cs = self.__get_cs_or_redirect(revision, repo_name,
282 c.cs = self.__get_cs_or_redirect(revision, repo_name,
283 redirect_after=False)
283 redirect_after=False)
284 if c.cs is None:
284 if c.cs is None:
285 c.cs = EmptyChangeset(alias=c.rhodecode_repo.alias)
285 c.cs = EmptyChangeset(alias=c.rhodecode_repo.alias)
286
286
287 c.f_path = f_path
287 c.f_path = f_path
288
288
289 if r_post:
289 if r_post:
290 unix_mode = 0
290 unix_mode = 0
291 content = convert_line_endings(r_post.get('content'), unix_mode)
291 content = convert_line_endings(r_post.get('content'), unix_mode)
292
292
293 message = r_post.get('message') or (_('Added %s via RhodeCode')
293 message = r_post.get('message') or (_('Added %s via RhodeCode')
294 % (f_path))
294 % (f_path))
295 location = r_post.get('location')
295 location = r_post.get('location')
296 filename = r_post.get('filename')
296 filename = r_post.get('filename')
297 file_obj = r_post.get('upload_file', None)
297 file_obj = r_post.get('upload_file', None)
298
298
299 if file_obj is not None and hasattr(file_obj, 'filename'):
299 if file_obj is not None and hasattr(file_obj, 'filename'):
300 filename = file_obj.filename
300 filename = file_obj.filename
301 content = file_obj.file
301 content = file_obj.file
302
302
303 node_path = os.path.join(location, filename)
303 node_path = os.path.join(location, filename)
304 author = self.rhodecode_user.full_contact
304 author = self.rhodecode_user.full_contact
305
305
306 if not content:
306 if not content:
307 h.flash(_('No content'), category='warning')
307 h.flash(_('No content'), category='warning')
308 return redirect(url('changeset_home', repo_name=c.repo_name,
308 return redirect(url('changeset_home', repo_name=c.repo_name,
309 revision='tip'))
309 revision='tip'))
310 if not filename:
310 if not filename:
311 h.flash(_('No filename'), category='warning')
311 h.flash(_('No filename'), category='warning')
312 return redirect(url('changeset_home', repo_name=c.repo_name,
312 return redirect(url('changeset_home', repo_name=c.repo_name,
313 revision='tip'))
313 revision='tip'))
314
314
315 try:
315 try:
316 self.scm_model.create_node(repo=c.rhodecode_repo,
316 self.scm_model.create_node(repo=c.rhodecode_repo,
317 repo_name=repo_name, cs=c.cs,
317 repo_name=repo_name, cs=c.cs,
318 user=self.rhodecode_user,
318 user=self.rhodecode_user,
319 author=author, message=message,
319 author=author, message=message,
320 content=content, f_path=node_path)
320 content=content, f_path=node_path)
321 h.flash(_('Successfully committed to %s' % node_path),
321 h.flash(_('Successfully committed to %s' % node_path),
322 category='success')
322 category='success')
323 except NodeAlreadyExistsError, e:
323 except NodeAlreadyExistsError, e:
324 h.flash(_(e), category='error')
324 h.flash(_(e), category='error')
325 except Exception:
325 except Exception:
326 log.error(traceback.format_exc())
326 log.error(traceback.format_exc())
327 h.flash(_('Error occurred during commit'), category='error')
327 h.flash(_('Error occurred during commit'), category='error')
328 return redirect(url('changeset_home',
328 return redirect(url('changeset_home',
329 repo_name=c.repo_name, revision='tip'))
329 repo_name=c.repo_name, revision='tip'))
330
330
331 return render('files/files_add.html')
331 return render('files/files_add.html')
332
332
333 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
333 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
334 'repository.admin')
334 'repository.admin')
335 def archivefile(self, repo_name, fname):
335 def archivefile(self, repo_name, fname):
336
336
337 fileformat = None
337 fileformat = None
338 revision = None
338 revision = None
339 ext = None
339 ext = None
340 subrepos = request.GET.get('subrepos') == 'true'
340 subrepos = request.GET.get('subrepos') == 'true'
341
341
342 for a_type, ext_data in settings.ARCHIVE_SPECS.items():
342 for a_type, ext_data in settings.ARCHIVE_SPECS.items():
343 archive_spec = fname.split(ext_data[1])
343 archive_spec = fname.split(ext_data[1])
344 if len(archive_spec) == 2 and archive_spec[1] == '':
344 if len(archive_spec) == 2 and archive_spec[1] == '':
345 fileformat = a_type or ext_data[1]
345 fileformat = a_type or ext_data[1]
346 revision = archive_spec[0]
346 revision = archive_spec[0]
347 ext = ext_data[1]
347 ext = ext_data[1]
348
348
349 try:
349 try:
350 dbrepo = RepoModel().get_by_repo_name(repo_name)
350 dbrepo = RepoModel().get_by_repo_name(repo_name)
351 if dbrepo.enable_downloads is False:
351 if dbrepo.enable_downloads is False:
352 return _('downloads disabled')
352 return _('downloads disabled')
353
353
354 if c.rhodecode_repo.alias == 'hg':
354 if c.rhodecode_repo.alias == 'hg':
355 # patch and reset hooks section of UI config to not run any
355 # patch and reset hooks section of UI config to not run any
356 # hooks on fetching archives with subrepos
356 # hooks on fetching archives with subrepos
357 for k, v in c.rhodecode_repo._repo.ui.configitems('hooks'):
357 for k, v in c.rhodecode_repo._repo.ui.configitems('hooks'):
358 c.rhodecode_repo._repo.ui.setconfig('hooks', k, None)
358 c.rhodecode_repo._repo.ui.setconfig('hooks', k, None)
359
359
360 cs = c.rhodecode_repo.get_changeset(revision)
360 cs = c.rhodecode_repo.get_changeset(revision)
361 content_type = settings.ARCHIVE_SPECS[fileformat][0]
361 content_type = settings.ARCHIVE_SPECS[fileformat][0]
362 except ChangesetDoesNotExistError:
362 except ChangesetDoesNotExistError:
363 return _('Unknown revision %s') % revision
363 return _('Unknown revision %s') % revision
364 except EmptyRepositoryError:
364 except EmptyRepositoryError:
365 return _('Empty repository')
365 return _('Empty repository')
366 except (ImproperArchiveTypeError, KeyError):
366 except (ImproperArchiveTypeError, KeyError):
367 return _('Unknown archive type')
367 return _('Unknown archive type')
368
368
369 response.content_type = content_type
369 response.content_type = content_type
370 response.content_disposition = 'attachment; filename=%s-%s%s' \
370 response.content_disposition = 'attachment; filename=%s-%s%s' \
371 % (repo_name, revision, ext)
371 % (repo_name, revision, ext)
372
372
373 import tempfile
373 import tempfile
374 archive = tempfile.mkstemp()[1]
374 archive = tempfile.mkstemp()[1]
375 t = open(archive, 'wb')
375 t = open(archive, 'wb')
376 cs.fill_archive(stream=t, kind=fileformat, subrepos=subrepos)
376 cs.fill_archive(stream=t, kind=fileformat, subrepos=subrepos)
377
377
378 def get_chunked_archive(archive):
378 def get_chunked_archive(archive):
379 stream = open(archive, 'rb')
379 stream = open(archive, 'rb')
380 while True:
380 while True:
381 data = stream.read(4096)
381 data = stream.read(4096)
382 if not data:
382 if not data:
383 os.remove(archive)
383 os.remove(archive)
384 break
384 break
385 yield data
385 yield data
386
386
387 return get_chunked_archive(archive)
387 return get_chunked_archive(archive)
388
388
389 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
389 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
390 'repository.admin')
390 'repository.admin')
391 def diff(self, repo_name, f_path):
391 def diff(self, repo_name, f_path):
392 ignore_whitespace = request.GET.get('ignorews') == '1'
392 ignore_whitespace = request.GET.get('ignorews') == '1'
393 line_context = request.GET.get('context', 3)
393 line_context = request.GET.get('context', 3)
394 diff1 = request.GET.get('diff1', '')
394 diff1 = request.GET.get('diff1', '')
395 diff2 = request.GET.get('diff2', '')
395 diff2 = request.GET.get('diff2', '')
396 c.action = request.GET.get('diff')
396 c.action = request.GET.get('diff')
397 c.no_changes = diff1 == diff2
397 c.no_changes = diff1 == diff2
398 c.f_path = f_path
398 c.f_path = f_path
399 c.big_diff = False
399 c.big_diff = False
400 c.anchor_url = anchor_url
400 c.anchor_url = anchor_url
401 c.ignorews_url = _ignorews_url
401 c.ignorews_url = _ignorews_url
402 c.context_url = _context_url
402 c.context_url = _context_url
403 c.changes = OrderedDict()
403 c.changes = OrderedDict()
404 c.changes[diff2] = []
404 c.changes[diff2] = []
405 try:
405 try:
406 if diff1 not in ['', None, 'None', '0' * 12, '0' * 40]:
406 if diff1 not in ['', None, 'None', '0' * 12, '0' * 40]:
407 c.changeset_1 = c.rhodecode_repo.get_changeset(diff1)
407 c.changeset_1 = c.rhodecode_repo.get_changeset(diff1)
408 node1 = c.changeset_1.get_node(f_path)
408 node1 = c.changeset_1.get_node(f_path)
409 else:
409 else:
410 c.changeset_1 = EmptyChangeset(repo=c.rhodecode_repo)
410 c.changeset_1 = EmptyChangeset(repo=c.rhodecode_repo)
411 node1 = FileNode('.', '', changeset=c.changeset_1)
411 node1 = FileNode('.', '', changeset=c.changeset_1)
412
412
413 if diff2 not in ['', None, 'None', '0' * 12, '0' * 40]:
413 if diff2 not in ['', None, 'None', '0' * 12, '0' * 40]:
414 c.changeset_2 = c.rhodecode_repo.get_changeset(diff2)
414 c.changeset_2 = c.rhodecode_repo.get_changeset(diff2)
415 node2 = c.changeset_2.get_node(f_path)
415 node2 = c.changeset_2.get_node(f_path)
416 else:
416 else:
417 c.changeset_2 = EmptyChangeset(repo=c.rhodecode_repo)
417 c.changeset_2 = EmptyChangeset(repo=c.rhodecode_repo)
418 node2 = FileNode('.', '', changeset=c.changeset_2)
418 node2 = FileNode('.', '', changeset=c.changeset_2)
419 except RepositoryError:
419 except RepositoryError:
420 return redirect(url('files_home', repo_name=c.repo_name,
420 return redirect(url('files_home', repo_name=c.repo_name,
421 f_path=f_path))
421 f_path=f_path))
422
422
423 if c.action == 'download':
423 if c.action == 'download':
424 _diff = diffs.get_gitdiff(node1, node2,
424 _diff = diffs.get_gitdiff(node1, node2,
425 ignore_whitespace=ignore_whitespace,
425 ignore_whitespace=ignore_whitespace,
426 context=line_context)
426 context=line_context)
427 diff = diffs.DiffProcessor(_diff, format='gitdiff')
427 diff = diffs.DiffProcessor(_diff, format='gitdiff')
428
428
429 diff_name = '%s_vs_%s.diff' % (diff1, diff2)
429 diff_name = '%s_vs_%s.diff' % (diff1, diff2)
430 response.content_type = 'text/plain'
430 response.content_type = 'text/plain'
431 response.content_disposition = 'attachment; filename=%s' \
431 response.content_disposition = (
432 % diff_name
432 'attachment; filename=%s' % diff_name
433 )
433 return diff.raw_diff()
434 return diff.raw_diff()
434
435
435 elif c.action == 'raw':
436 elif c.action == 'raw':
436 _diff = diffs.get_gitdiff(node1, node2,
437 _diff = diffs.get_gitdiff(node1, node2,
437 ignore_whitespace=ignore_whitespace,
438 ignore_whitespace=ignore_whitespace,
438 context=line_context)
439 context=line_context)
439 diff = diffs.DiffProcessor(_diff, format='gitdiff')
440 diff = diffs.DiffProcessor(_diff, format='gitdiff')
440 response.content_type = 'text/plain'
441 response.content_type = 'text/plain'
441 return diff.raw_diff()
442 return diff.raw_diff()
442
443
443 else:
444 else:
444 fid = h.FID(diff2, node2.path)
445 fid = h.FID(diff2, node2.path)
445 line_context_lcl = get_line_ctx(fid, request.GET)
446 line_context_lcl = get_line_ctx(fid, request.GET)
446 ign_whitespace_lcl = get_ignore_ws(fid, request.GET)
447 ign_whitespace_lcl = get_ignore_ws(fid, request.GET)
447
448
448 lim = request.GET.get('fulldiff') or self.cut_off_limit
449 lim = request.GET.get('fulldiff') or self.cut_off_limit
449 _, cs1, cs2, diff, st = wrapped_diff(filenode_old=node1,
450 _, cs1, cs2, diff, st = wrapped_diff(filenode_old=node1,
450 filenode_new=node2,
451 filenode_new=node2,
451 cut_off_limit=lim,
452 cut_off_limit=lim,
452 ignore_whitespace=ign_whitespace_lcl,
453 ignore_whitespace=ign_whitespace_lcl,
453 line_context=line_context_lcl,
454 line_context=line_context_lcl,
454 enable_comments=False)
455 enable_comments=False)
455
456
456 c.changes = [('', node2, diff, cs1, cs2, st,)]
457 c.changes = [('', node2, diff, cs1, cs2, st,)]
457
458
458 return render('files/file_diff.html')
459 return render('files/file_diff.html')
459
460
460 def _get_node_history(self, cs, f_path):
461 def _get_node_history(self, cs, f_path):
461 changesets = cs.get_file_history(f_path)
462 changesets = cs.get_file_history(f_path)
462 hist_l = []
463 hist_l = []
463
464
464 changesets_group = ([], _("Changesets"))
465 changesets_group = ([], _("Changesets"))
465 branches_group = ([], _("Branches"))
466 branches_group = ([], _("Branches"))
466 tags_group = ([], _("Tags"))
467 tags_group = ([], _("Tags"))
467 _hg = cs.repository.alias == 'hg'
468 _hg = cs.repository.alias == 'hg'
468 for chs in changesets:
469 for chs in changesets:
469 _branch = '(%s)' % chs.branch if _hg else ''
470 _branch = '(%s)' % chs.branch if _hg else ''
470 n_desc = 'r%s:%s %s' % (chs.revision, chs.short_id, _branch)
471 n_desc = 'r%s:%s %s' % (chs.revision, chs.short_id, _branch)
471 changesets_group[0].append((chs.raw_id, n_desc,))
472 changesets_group[0].append((chs.raw_id, n_desc,))
472
473
473 hist_l.append(changesets_group)
474 hist_l.append(changesets_group)
474
475
475 for name, chs in c.rhodecode_repo.branches.items():
476 for name, chs in c.rhodecode_repo.branches.items():
476 branches_group[0].append((chs, name),)
477 branches_group[0].append((chs, name),)
477 hist_l.append(branches_group)
478 hist_l.append(branches_group)
478
479
479 for name, chs in c.rhodecode_repo.tags.items():
480 for name, chs in c.rhodecode_repo.tags.items():
480 tags_group[0].append((chs, name),)
481 tags_group[0].append((chs, name),)
481 hist_l.append(tags_group)
482 hist_l.append(tags_group)
482
483
483 return hist_l
484 return hist_l
484
485
485 @jsonify
486 @jsonify
486 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
487 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
487 'repository.admin')
488 'repository.admin')
488 def nodelist(self, repo_name, revision, f_path):
489 def nodelist(self, repo_name, revision, f_path):
489 if request.environ.get('HTTP_X_PARTIAL_XHR'):
490 if request.environ.get('HTTP_X_PARTIAL_XHR'):
490 cs = self.__get_cs_or_redirect(revision, repo_name)
491 cs = self.__get_cs_or_redirect(revision, repo_name)
491 _d, _f = ScmModel().get_nodes(repo_name, cs.raw_id, f_path,
492 _d, _f = ScmModel().get_nodes(repo_name, cs.raw_id, f_path,
492 flat=False)
493 flat=False)
493 return _d + _f
494 return _d + _f
@@ -1,8 +1,9 b''
1 %if h.is_hg(c.scm_type):
1 # ${c.scm_type.upper()} changeset patch
2 # ${c.scm_type.upper()} changeset patch
2 # User ${c.changeset.author|n}
3 # User ${c.changeset.author|n}
3 # Date ${c.changeset.date}
4 # Date ${c.changeset.date}
4 # Node ID ${c.changeset.raw_id}
5 # Node ID ${c.changeset.raw_id}
5 ${c.parent_tmpl}
6 ${c.parent_tmpl}
6 ${c.changeset.message}
7 ${c.changeset.message}
7
8 %endif
8 ${c.diffs|n}
9 ${c.diffs|n}
General Comments 0
You need to be logged in to leave comments. Login now