##// END OF EJS Templates
Fix for #378
Erwin Kroon -
r2070:c238df8c default
parent child Browse files
Show More
@@ -1,367 +1,367 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.controllers.changeset
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 changeset controller for pylons showoing changes beetween
7 7 revisions
8 8
9 9 :created_on: Apr 25, 2010
10 10 :author: marcink
11 11 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
12 12 :license: GPLv3, see COPYING for more details.
13 13 """
14 14 # This program is free software: you can redistribute it and/or modify
15 15 # it under the terms of the GNU General Public License as published by
16 16 # the Free Software Foundation, either version 3 of the License, or
17 17 # (at your option) any later version.
18 18 #
19 19 # This program is distributed in the hope that it will be useful,
20 20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
21 21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 22 # GNU General Public License for more details.
23 23 #
24 24 # You should have received a copy of the GNU General Public License
25 25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
26 26 import logging
27 27 import traceback
28 28 from collections import defaultdict
29 29 from webob.exc import HTTPForbidden
30 30
31 31 from pylons import tmpl_context as c, url, request, response
32 32 from pylons.i18n.translation import _
33 33 from pylons.controllers.util import redirect
34 34 from pylons.decorators import jsonify
35 35
36 36 from rhodecode.lib.vcs.exceptions import RepositoryError, ChangesetError, \
37 37 ChangesetDoesNotExistError
38 38 from rhodecode.lib.vcs.nodes import FileNode
39 39
40 40 import rhodecode.lib.helpers as h
41 41 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
42 42 from rhodecode.lib.base import BaseRepoController, render
43 43 from rhodecode.lib.utils import EmptyChangeset
44 44 from rhodecode.lib.compat import OrderedDict
45 45 from rhodecode.lib import diffs
46 46 from rhodecode.model.db import ChangesetComment
47 47 from rhodecode.model.comment import ChangesetCommentsModel
48 48 from rhodecode.model.meta import Session
49 49 from rhodecode.lib.diffs import wrapped_diff
50 50
51 51 log = logging.getLogger(__name__)
52 52
53 53
54 54 def anchor_url(revision, path):
55 55 fid = h.FID(revision, path)
56 56 return h.url.current(anchor=fid, **request.GET)
57 57
58 58
59 59 def get_ignore_ws(fid, GET):
60 60 ig_ws_global = request.GET.get('ignorews')
61 61 ig_ws = filter(lambda k: k.startswith('WS'), GET.getall(fid))
62 62 if ig_ws:
63 63 try:
64 64 return int(ig_ws[0].split(':')[-1])
65 65 except:
66 66 pass
67 67 return ig_ws_global
68 68
69 69
70 70 def _ignorews_url(fileid=None):
71 71
72 72 params = defaultdict(list)
73 73 lbl = _('show white space')
74 74 ig_ws = get_ignore_ws(fileid, request.GET)
75 75 ln_ctx = get_line_ctx(fileid, request.GET)
76 76 # global option
77 77 if fileid is None:
78 78 if ig_ws is None:
79 79 params['ignorews'] += [1]
80 80 lbl = _('ignore white space')
81 81 ctx_key = 'context'
82 82 ctx_val = ln_ctx
83 83 # per file options
84 84 else:
85 85 if ig_ws is None:
86 86 params[fileid] += ['WS:1']
87 87 lbl = _('ignore white space')
88 88
89 89 ctx_key = fileid
90 90 ctx_val = 'C:%s' % ln_ctx
91 91 # if we have passed in ln_ctx pass it along to our params
92 92 if ln_ctx:
93 93 params[ctx_key] += [ctx_val]
94 94
95 95 params['anchor'] = fileid
96 img = h.image('/images/icons/text_strikethrough.png', lbl, class_='icon')
96 img = h.image(h.url('/images/icons/text_strikethrough.png'), lbl, class_='icon')
97 97 return h.link_to(img, h.url.current(**params), title=lbl, class_='tooltip')
98 98
99 99
100 100 def get_line_ctx(fid, GET):
101 101 ln_ctx_global = request.GET.get('context')
102 102 ln_ctx = filter(lambda k: k.startswith('C'), GET.getall(fid))
103 103
104 104 if ln_ctx:
105 105 retval = ln_ctx[0].split(':')[-1]
106 106 else:
107 107 retval = ln_ctx_global
108 108
109 109 try:
110 110 return int(retval)
111 111 except:
112 112 return
113 113
114 114
115 115 def _context_url(fileid=None):
116 116 """
117 117 Generates url for context lines
118 118
119 119 :param fileid:
120 120 """
121 121 ig_ws = get_ignore_ws(fileid, request.GET)
122 122 ln_ctx = (get_line_ctx(fileid, request.GET) or 3) * 2
123 123
124 124 params = defaultdict(list)
125 125
126 126 # global option
127 127 if fileid is None:
128 128 if ln_ctx > 0:
129 129 params['context'] += [ln_ctx]
130 130
131 131 if ig_ws:
132 132 ig_ws_key = 'ignorews'
133 133 ig_ws_val = 1
134 134
135 135 # per file option
136 136 else:
137 137 params[fileid] += ['C:%s' % ln_ctx]
138 138 ig_ws_key = fileid
139 139 ig_ws_val = 'WS:%s' % 1
140 140
141 141 if ig_ws:
142 142 params[ig_ws_key] += [ig_ws_val]
143 143
144 144 lbl = _('%s line context') % ln_ctx
145 145
146 146 params['anchor'] = fileid
147 img = h.image('/images/icons/table_add.png', lbl, class_='icon')
147 img = h.image(h.url('/images/icons/table_add.png'), lbl, class_='icon')
148 148 return h.link_to(img, h.url.current(**params), title=lbl, class_='tooltip')
149 149
150 150
151 151 class ChangesetController(BaseRepoController):
152 152
153 153 @LoginRequired()
154 154 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
155 155 'repository.admin')
156 156 def __before__(self):
157 157 super(ChangesetController, self).__before__()
158 158 c.affected_files_cut_off = 60
159 159
160 160 def index(self, revision):
161 161
162 162 c.anchor_url = anchor_url
163 163 c.ignorews_url = _ignorews_url
164 164 c.context_url = _context_url
165 165
166 166 #get ranges of revisions if preset
167 167 rev_range = revision.split('...')[:2]
168 168 enable_comments = True
169 169 try:
170 170 if len(rev_range) == 2:
171 171 enable_comments = False
172 172 rev_start = rev_range[0]
173 173 rev_end = rev_range[1]
174 174 rev_ranges = c.rhodecode_repo.get_changesets(start=rev_start,
175 175 end=rev_end)
176 176 else:
177 177 rev_ranges = [c.rhodecode_repo.get_changeset(revision)]
178 178
179 179 c.cs_ranges = list(rev_ranges)
180 180 if not c.cs_ranges:
181 181 raise RepositoryError('Changeset range returned empty result')
182 182
183 183 except (RepositoryError, ChangesetDoesNotExistError, Exception), e:
184 184 log.error(traceback.format_exc())
185 185 h.flash(str(e), category='warning')
186 186 return redirect(url('home'))
187 187
188 188 c.changes = OrderedDict()
189 189
190 190 c.lines_added = 0 # count of lines added
191 191 c.lines_deleted = 0 # count of lines removes
192 192
193 193 cumulative_diff = 0
194 194 c.cut_off = False # defines if cut off limit is reached
195 195
196 196 c.comments = []
197 197 c.inline_comments = []
198 198 c.inline_cnt = 0
199 199 # Iterate over ranges (default changeset view is always one changeset)
200 200 for changeset in c.cs_ranges:
201 201 c.comments.extend(ChangesetCommentsModel()\
202 202 .get_comments(c.rhodecode_db_repo.repo_id,
203 203 changeset.raw_id))
204 204 inlines = ChangesetCommentsModel()\
205 205 .get_inline_comments(c.rhodecode_db_repo.repo_id,
206 206 changeset.raw_id)
207 207 c.inline_comments.extend(inlines)
208 208 c.changes[changeset.raw_id] = []
209 209 try:
210 210 changeset_parent = changeset.parents[0]
211 211 except IndexError:
212 212 changeset_parent = None
213 213
214 214 #==================================================================
215 215 # ADDED FILES
216 216 #==================================================================
217 217 for node in changeset.added:
218 218 fid = h.FID(revision, node.path)
219 219 line_context_lcl = get_line_ctx(fid, request.GET)
220 220 ign_whitespace_lcl = get_ignore_ws(fid, request.GET)
221 221 lim = self.cut_off_limit
222 222 if cumulative_diff > self.cut_off_limit:
223 223 lim = -1
224 224 size, cs1, cs2, diff, st = wrapped_diff(filenode_old=None,
225 225 filenode_new=node,
226 226 cut_off_limit=lim,
227 227 ignore_whitespace=ign_whitespace_lcl,
228 228 line_context=line_context_lcl,
229 229 enable_comments=enable_comments)
230 230 cumulative_diff += size
231 231 c.lines_added += st[0]
232 232 c.lines_deleted += st[1]
233 233 c.changes[changeset.raw_id].append(('added', node, diff,
234 234 cs1, cs2, st))
235 235
236 236 #==================================================================
237 237 # CHANGED FILES
238 238 #==================================================================
239 239 for node in changeset.changed:
240 240 try:
241 241 filenode_old = changeset_parent.get_node(node.path)
242 242 except ChangesetError:
243 243 log.warning('Unable to fetch parent node for diff')
244 244 filenode_old = FileNode(node.path, '', EmptyChangeset())
245 245
246 246 fid = h.FID(revision, node.path)
247 247 line_context_lcl = get_line_ctx(fid, request.GET)
248 248 ign_whitespace_lcl = get_ignore_ws(fid, request.GET)
249 249 lim = self.cut_off_limit
250 250 if cumulative_diff > self.cut_off_limit:
251 251 lim = -1
252 252 size, cs1, cs2, diff, st = wrapped_diff(filenode_old=filenode_old,
253 253 filenode_new=node,
254 254 cut_off_limit=lim,
255 255 ignore_whitespace=ign_whitespace_lcl,
256 256 line_context=line_context_lcl,
257 257 enable_comments=enable_comments)
258 258 cumulative_diff += size
259 259 c.lines_added += st[0]
260 260 c.lines_deleted += st[1]
261 261 c.changes[changeset.raw_id].append(('changed', node, diff,
262 262 cs1, cs2, st))
263 263
264 264 #==================================================================
265 265 # REMOVED FILES
266 266 #==================================================================
267 267 for node in changeset.removed:
268 268 c.changes[changeset.raw_id].append(('removed', node, None,
269 269 None, None, (0, 0)))
270 270
271 271 # count inline comments
272 272 for path, lines in c.inline_comments:
273 273 for comments in lines.values():
274 274 c.inline_cnt += len(comments)
275 275
276 276 if len(c.cs_ranges) == 1:
277 277 c.changeset = c.cs_ranges[0]
278 278 c.changes = c.changes[c.changeset.raw_id]
279 279
280 280 return render('changeset/changeset.html')
281 281 else:
282 282 return render('changeset/changeset_range.html')
283 283
284 284 def raw_changeset(self, revision):
285 285
286 286 method = request.GET.get('diff', 'show')
287 287 ignore_whitespace = request.GET.get('ignorews') == '1'
288 288 line_context = request.GET.get('context', 3)
289 289 try:
290 290 c.scm_type = c.rhodecode_repo.alias
291 291 c.changeset = c.rhodecode_repo.get_changeset(revision)
292 292 except RepositoryError:
293 293 log.error(traceback.format_exc())
294 294 return redirect(url('home'))
295 295 else:
296 296 try:
297 297 c.changeset_parent = c.changeset.parents[0]
298 298 except IndexError:
299 299 c.changeset_parent = None
300 300 c.changes = []
301 301
302 302 for node in c.changeset.added:
303 303 filenode_old = FileNode(node.path, '')
304 304 if filenode_old.is_binary or node.is_binary:
305 305 diff = _('binary file') + '\n'
306 306 else:
307 307 f_gitdiff = diffs.get_gitdiff(filenode_old, node,
308 308 ignore_whitespace=ignore_whitespace,
309 309 context=line_context)
310 310 diff = diffs.DiffProcessor(f_gitdiff,
311 311 format='gitdiff').raw_diff()
312 312
313 313 cs1 = None
314 314 cs2 = node.last_changeset.raw_id
315 315 c.changes.append(('added', node, diff, cs1, cs2))
316 316
317 317 for node in c.changeset.changed:
318 318 filenode_old = c.changeset_parent.get_node(node.path)
319 319 if filenode_old.is_binary or node.is_binary:
320 320 diff = _('binary file')
321 321 else:
322 322 f_gitdiff = diffs.get_gitdiff(filenode_old, node,
323 323 ignore_whitespace=ignore_whitespace,
324 324 context=line_context)
325 325 diff = diffs.DiffProcessor(f_gitdiff,
326 326 format='gitdiff').raw_diff()
327 327
328 328 cs1 = filenode_old.last_changeset.raw_id
329 329 cs2 = node.last_changeset.raw_id
330 330 c.changes.append(('changed', node, diff, cs1, cs2))
331 331
332 332 response.content_type = 'text/plain'
333 333
334 334 if method == 'download':
335 335 response.content_disposition = 'attachment; filename=%s.patch' \
336 336 % revision
337 337
338 338 c.parent_tmpl = ''.join(['# Parent %s\n' % x.raw_id for x in
339 339 c.changeset.parents])
340 340
341 341 c.diffs = ''
342 342 for x in c.changes:
343 343 c.diffs += x[2]
344 344
345 345 return render('changeset/raw_changeset.html')
346 346
347 347 def comment(self, repo_name, revision):
348 348 ChangesetCommentsModel().create(text=request.POST.get('text'),
349 349 repo_id=c.rhodecode_db_repo.repo_id,
350 350 user_id=c.rhodecode_user.user_id,
351 351 revision=revision,
352 352 f_path=request.POST.get('f_path'),
353 353 line_no=request.POST.get('line'))
354 354 Session.commit()
355 355 return redirect(h.url('changeset_home', repo_name=repo_name,
356 356 revision=revision))
357 357
358 358 @jsonify
359 359 def delete_comment(self, repo_name, comment_id):
360 360 co = ChangesetComment.get(comment_id)
361 361 owner = lambda: co.author.user_id == c.rhodecode_user.user_id
362 362 if h.HasPermissionAny('hg.admin', 'repository.admin')() or owner:
363 363 ChangesetCommentsModel().delete(comment=co)
364 364 Session.commit()
365 365 return True
366 366 else:
367 367 raise HTTPForbidden()
General Comments 0
You need to be logged in to leave comments. Login now