##// END OF EJS Templates
missing changesets should return 404 not redirect + flash....
marcink -
r3619:03028bf3 beta
parent child Browse files
Show More
@@ -1,404 +1,404 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 from webob.exc import HTTPForbidden, HTTPBadRequest
29 from webob.exc import HTTPForbidden, HTTPBadRequest, HTTPNotFound
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 rhodecode.lib.utils import jsonify
35 35
36 36 from rhodecode.lib.vcs.exceptions import RepositoryError, \
37 37 ChangesetDoesNotExistError
38 38
39 39 import rhodecode.lib.helpers as h
40 40 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
41 41 from rhodecode.lib.base import BaseRepoController, render
42 42 from rhodecode.lib.utils import action_logger
43 43 from rhodecode.lib.compat import OrderedDict
44 44 from rhodecode.lib import diffs
45 45 from rhodecode.model.db import ChangesetComment, ChangesetStatus
46 46 from rhodecode.model.comment import ChangesetCommentsModel
47 47 from rhodecode.model.changeset_status import ChangesetStatusModel
48 48 from rhodecode.model.meta import Session
49 49 from rhodecode.model.repo import RepoModel
50 50 from rhodecode.lib.diffs import LimitedDiffContainer
51 51 from rhodecode.lib.exceptions import StatusChangeOnClosedPullRequestError
52 52 from rhodecode.lib.vcs.backends.base import EmptyChangeset
53 53 from rhodecode.lib.utils2 import safe_unicode
54 54
55 55 log = logging.getLogger(__name__)
56 56
57 57
58 58 def _update_with_GET(params, GET):
59 59 for k in ['diff1', 'diff2', 'diff']:
60 60 params[k] += GET.getall(k)
61 61
62 62
63 63 def anchor_url(revision, path, GET):
64 64 fid = h.FID(revision, path)
65 65 return h.url.current(anchor=fid, **dict(GET))
66 66
67 67
68 68 def get_ignore_ws(fid, GET):
69 69 ig_ws_global = GET.get('ignorews')
70 70 ig_ws = filter(lambda k: k.startswith('WS'), GET.getall(fid))
71 71 if ig_ws:
72 72 try:
73 73 return int(ig_ws[0].split(':')[-1])
74 74 except:
75 75 pass
76 76 return ig_ws_global
77 77
78 78
79 79 def _ignorews_url(GET, fileid=None):
80 80 fileid = str(fileid) if fileid else None
81 81 params = defaultdict(list)
82 82 _update_with_GET(params, GET)
83 83 lbl = _('show white space')
84 84 ig_ws = get_ignore_ws(fileid, GET)
85 85 ln_ctx = get_line_ctx(fileid, GET)
86 86 # global option
87 87 if fileid is None:
88 88 if ig_ws is None:
89 89 params['ignorews'] += [1]
90 90 lbl = _('ignore white space')
91 91 ctx_key = 'context'
92 92 ctx_val = ln_ctx
93 93 # per file options
94 94 else:
95 95 if ig_ws is None:
96 96 params[fileid] += ['WS:1']
97 97 lbl = _('ignore white space')
98 98
99 99 ctx_key = fileid
100 100 ctx_val = 'C:%s' % ln_ctx
101 101 # if we have passed in ln_ctx pass it along to our params
102 102 if ln_ctx:
103 103 params[ctx_key] += [ctx_val]
104 104
105 105 params['anchor'] = fileid
106 106 img = h.image(h.url('/images/icons/text_strikethrough.png'), lbl, class_='icon')
107 107 return h.link_to(img, h.url.current(**params), title=lbl, class_='tooltip')
108 108
109 109
110 110 def get_line_ctx(fid, GET):
111 111 ln_ctx_global = GET.get('context')
112 112 if fid:
113 113 ln_ctx = filter(lambda k: k.startswith('C'), GET.getall(fid))
114 114 else:
115 115 _ln_ctx = filter(lambda k: k.startswith('C'), GET)
116 116 ln_ctx = GET.get(_ln_ctx[0]) if _ln_ctx else ln_ctx_global
117 117 if ln_ctx:
118 118 ln_ctx = [ln_ctx]
119 119
120 120 if ln_ctx:
121 121 retval = ln_ctx[0].split(':')[-1]
122 122 else:
123 123 retval = ln_ctx_global
124 124
125 125 try:
126 126 return int(retval)
127 127 except:
128 128 return 3
129 129
130 130
131 131 def _context_url(GET, fileid=None):
132 132 """
133 133 Generates url for context lines
134 134
135 135 :param fileid:
136 136 """
137 137
138 138 fileid = str(fileid) if fileid else None
139 139 ig_ws = get_ignore_ws(fileid, GET)
140 140 ln_ctx = (get_line_ctx(fileid, GET) or 3) * 2
141 141
142 142 params = defaultdict(list)
143 143 _update_with_GET(params, GET)
144 144
145 145 # global option
146 146 if fileid is None:
147 147 if ln_ctx > 0:
148 148 params['context'] += [ln_ctx]
149 149
150 150 if ig_ws:
151 151 ig_ws_key = 'ignorews'
152 152 ig_ws_val = 1
153 153
154 154 # per file option
155 155 else:
156 156 params[fileid] += ['C:%s' % ln_ctx]
157 157 ig_ws_key = fileid
158 158 ig_ws_val = 'WS:%s' % 1
159 159
160 160 if ig_ws:
161 161 params[ig_ws_key] += [ig_ws_val]
162 162
163 163 lbl = _('%s line context') % ln_ctx
164 164
165 165 params['anchor'] = fileid
166 166 img = h.image(h.url('/images/icons/table_add.png'), lbl, class_='icon')
167 167 return h.link_to(img, h.url.current(**params), title=lbl, class_='tooltip')
168 168
169 169
170 170 class ChangesetController(BaseRepoController):
171 171
172 172 @LoginRequired()
173 173 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
174 174 'repository.admin')
175 175 def __before__(self):
176 176 super(ChangesetController, self).__before__()
177 177 c.affected_files_cut_off = 60
178 178 repo_model = RepoModel()
179 179 c.users_array = repo_model.get_users_js()
180 180 c.users_groups_array = repo_model.get_users_groups_js()
181 181
182 182 def index(self, revision, method='show'):
183 183 c.anchor_url = anchor_url
184 184 c.ignorews_url = _ignorews_url
185 185 c.context_url = _context_url
186 186 c.fulldiff = fulldiff = request.GET.get('fulldiff')
187 187 #get ranges of revisions if preset
188 188 rev_range = revision.split('...')[:2]
189 189 enable_comments = True
190 190 try:
191 191 if len(rev_range) == 2:
192 192 enable_comments = False
193 193 rev_start = rev_range[0]
194 194 rev_end = rev_range[1]
195 195 rev_ranges = c.rhodecode_repo.get_changesets(start=rev_start,
196 196 end=rev_end)
197 197 else:
198 198 rev_ranges = [c.rhodecode_repo.get_changeset(revision)]
199 199
200 200 c.cs_ranges = list(rev_ranges)
201 201 if not c.cs_ranges:
202 202 raise RepositoryError('Changeset range returned empty result')
203 203
204 204 except (RepositoryError, ChangesetDoesNotExistError, Exception), e:
205 205 log.error(traceback.format_exc())
206 206 h.flash(str(e), category='error')
207 return redirect(url('changeset_home', repo_name=c.repo_name))
207 raise HTTPNotFound()
208 208
209 209 c.changes = OrderedDict()
210 210
211 211 c.lines_added = 0 # count of lines added
212 212 c.lines_deleted = 0 # count of lines removes
213 213
214 214 c.changeset_statuses = ChangesetStatus.STATUSES
215 215 c.comments = []
216 216 c.statuses = []
217 217 c.inline_comments = []
218 218 c.inline_cnt = 0
219 219
220 220 # Iterate over ranges (default changeset view is always one changeset)
221 221 for changeset in c.cs_ranges:
222 222 inlines = []
223 223 if method == 'show':
224 224 c.statuses.extend([ChangesetStatusModel().get_status(
225 225 c.rhodecode_db_repo.repo_id, changeset.raw_id)])
226 226
227 227 c.comments.extend(ChangesetCommentsModel()\
228 228 .get_comments(c.rhodecode_db_repo.repo_id,
229 229 revision=changeset.raw_id))
230 230
231 231 #comments from PR
232 232 st = ChangesetStatusModel().get_statuses(
233 233 c.rhodecode_db_repo.repo_id, changeset.raw_id,
234 234 with_revisions=True)
235 235 # from associated statuses, check the pull requests, and
236 236 # show comments from them
237 237
238 238 prs = set([x.pull_request for x in
239 239 filter(lambda x: x.pull_request != None, st)])
240 240
241 241 for pr in prs:
242 242 c.comments.extend(pr.comments)
243 243 inlines = ChangesetCommentsModel()\
244 244 .get_inline_comments(c.rhodecode_db_repo.repo_id,
245 245 revision=changeset.raw_id)
246 246 c.inline_comments.extend(inlines)
247 247
248 248 c.changes[changeset.raw_id] = []
249 249
250 250 cs2 = changeset.raw_id
251 251 cs1 = changeset.parents[0].raw_id if changeset.parents else EmptyChangeset()
252 252 context_lcl = get_line_ctx('', request.GET)
253 253 ign_whitespace_lcl = ign_whitespace_lcl = get_ignore_ws('', request.GET)
254 254
255 255 _diff = c.rhodecode_repo.get_diff(cs1, cs2,
256 256 ignore_whitespace=ign_whitespace_lcl, context=context_lcl)
257 257 diff_limit = self.cut_off_limit if not fulldiff else None
258 258 diff_processor = diffs.DiffProcessor(_diff,
259 259 vcs=c.rhodecode_repo.alias,
260 260 format='gitdiff',
261 261 diff_limit=diff_limit)
262 262 cs_changes = OrderedDict()
263 263 if method == 'show':
264 264 _parsed = diff_processor.prepare()
265 265 c.limited_diff = False
266 266 if isinstance(_parsed, LimitedDiffContainer):
267 267 c.limited_diff = True
268 268 for f in _parsed:
269 269 st = f['stats']
270 270 if st[0] != 'b':
271 271 c.lines_added += st[0]
272 272 c.lines_deleted += st[1]
273 273 fid = h.FID(changeset.raw_id, f['filename'])
274 274 diff = diff_processor.as_html(enable_comments=enable_comments,
275 275 parsed_lines=[f])
276 276 cs_changes[fid] = [cs1, cs2, f['operation'], f['filename'],
277 277 diff, st]
278 278 else:
279 279 # downloads/raw we only need RAW diff nothing else
280 280 diff = diff_processor.as_raw()
281 281 cs_changes[''] = [None, None, None, None, diff, None]
282 282 c.changes[changeset.raw_id] = cs_changes
283 283
284 284 #sort comments by how they were generated
285 285 c.comments = sorted(c.comments, key=lambda x: x.comment_id)
286 286
287 287 # count inline comments
288 288 for __, lines in c.inline_comments:
289 289 for comments in lines.values():
290 290 c.inline_cnt += len(comments)
291 291
292 292 if len(c.cs_ranges) == 1:
293 293 c.changeset = c.cs_ranges[0]
294 294 c.parent_tmpl = ''.join(['# Parent %s\n' % x.raw_id
295 295 for x in c.changeset.parents])
296 296 if method == 'download':
297 297 response.content_type = 'text/plain'
298 298 response.content_disposition = 'attachment; filename=%s.diff' \
299 299 % revision[:12]
300 300 return diff
301 301 elif method == 'patch':
302 302 response.content_type = 'text/plain'
303 303 c.diff = safe_unicode(diff)
304 304 return render('changeset/patch_changeset.html')
305 305 elif method == 'raw':
306 306 response.content_type = 'text/plain'
307 307 return diff
308 308 elif method == 'show':
309 309 if len(c.cs_ranges) == 1:
310 310 return render('changeset/changeset.html')
311 311 else:
312 312 return render('changeset/changeset_range.html')
313 313
314 314 def changeset_raw(self, revision):
315 315 return self.index(revision, method='raw')
316 316
317 317 def changeset_patch(self, revision):
318 318 return self.index(revision, method='patch')
319 319
320 320 def changeset_download(self, revision):
321 321 return self.index(revision, method='download')
322 322
323 323 @jsonify
324 324 def comment(self, repo_name, revision):
325 325 status = request.POST.get('changeset_status')
326 326 change_status = request.POST.get('change_changeset_status')
327 327 text = request.POST.get('text')
328 328 if status and change_status:
329 329 text = text or (_('Status change -> %s')
330 330 % ChangesetStatus.get_status_lbl(status))
331 331
332 332 c.co = comm = ChangesetCommentsModel().create(
333 333 text=text,
334 334 repo=c.rhodecode_db_repo.repo_id,
335 335 user=c.rhodecode_user.user_id,
336 336 revision=revision,
337 337 f_path=request.POST.get('f_path'),
338 338 line_no=request.POST.get('line'),
339 339 status_change=(ChangesetStatus.get_status_lbl(status)
340 340 if status and change_status else None)
341 341 )
342 342
343 343 # get status if set !
344 344 if status and change_status:
345 345 # if latest status was from pull request and it's closed
346 346 # disallow changing status !
347 347 # dont_allow_on_closed_pull_request = True !
348 348
349 349 try:
350 350 ChangesetStatusModel().set_status(
351 351 c.rhodecode_db_repo.repo_id,
352 352 status,
353 353 c.rhodecode_user.user_id,
354 354 comm,
355 355 revision=revision,
356 356 dont_allow_on_closed_pull_request=True
357 357 )
358 358 except StatusChangeOnClosedPullRequestError:
359 359 log.error(traceback.format_exc())
360 360 msg = _('Changing status on a changeset associated with '
361 361 'a closed pull request is not allowed')
362 362 h.flash(msg, category='warning')
363 363 return redirect(h.url('changeset_home', repo_name=repo_name,
364 364 revision=revision))
365 365 action_logger(self.rhodecode_user,
366 366 'user_commented_revision:%s' % revision,
367 367 c.rhodecode_db_repo, self.ip_addr, self.sa)
368 368
369 369 Session().commit()
370 370
371 371 if not request.environ.get('HTTP_X_PARTIAL_XHR'):
372 372 return redirect(h.url('changeset_home', repo_name=repo_name,
373 373 revision=revision))
374 374 #only ajax below
375 375 data = {
376 376 'target_id': h.safeid(h.safe_unicode(request.POST.get('f_path'))),
377 377 }
378 378 if comm:
379 379 data.update(comm.get_dict())
380 380 data.update({'rendered_text':
381 381 render('changeset/changeset_comment_block.html')})
382 382
383 383 return data
384 384
385 385 @jsonify
386 386 def delete_comment(self, repo_name, comment_id):
387 387 co = ChangesetComment.get(comment_id)
388 388 owner = co.author.user_id == c.rhodecode_user.user_id
389 389 if h.HasPermissionAny('hg.admin', 'repository.admin')() or owner:
390 390 ChangesetCommentsModel().delete(comment=co)
391 391 Session().commit()
392 392 return True
393 393 else:
394 394 raise HTTPForbidden()
395 395
396 396 @jsonify
397 397 def changeset_info(self, repo_name, revision):
398 398 if request.is_xhr:
399 399 try:
400 400 return c.rhodecode_repo.get_changeset(revision)
401 401 except ChangesetDoesNotExistError, e:
402 402 return EmptyChangeset(message=str(e))
403 403 else:
404 404 raise HTTPBadRequest()
General Comments 0
You need to be logged in to leave comments. Login now