##// END OF EJS Templates
changeset: replace diff/whitespace/context popup menu with icons
Aras Pranckevicius -
r1859:929fc8d9 beta
parent child Browse files
Show More
@@ -1,365 +1,367 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 vcs.exceptions import RepositoryError, ChangesetError, \
36 from vcs.exceptions import RepositoryError, ChangesetError, \
37 ChangesetDoesNotExistError
37 ChangesetDoesNotExistError
38 from vcs.nodes import FileNode
38 from 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, **request.GET)
56 return h.url.current(anchor=fid, **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 return h.link_to(lbl, h.url.current(**params))
96 img = h.image('/images/icons/text_strikethrough.png', lbl, class_='icon')
97 return h.link_to(img, h.url.current(**params), title=lbl)
97
98
98
99
99 def get_line_ctx(fid, GET):
100 def get_line_ctx(fid, GET):
100 ln_ctx_global = request.GET.get('context')
101 ln_ctx_global = request.GET.get('context')
101 ln_ctx = filter(lambda k: k.startswith('C'), GET.getall(fid))
102 ln_ctx = filter(lambda k: k.startswith('C'), GET.getall(fid))
102
103
103 if ln_ctx:
104 if ln_ctx:
104 retval = ln_ctx[0].split(':')[-1]
105 retval = ln_ctx[0].split(':')[-1]
105 else:
106 else:
106 retval = ln_ctx_global
107 retval = ln_ctx_global
107
108
108 try:
109 try:
109 return int(retval)
110 return int(retval)
110 except:
111 except:
111 return
112 return
112
113
113
114
114 def _context_url(fileid=None):
115 def _context_url(fileid=None):
115 """
116 """
116 Generates url for context lines
117 Generates url for context lines
117
118
118 :param fileid:
119 :param fileid:
119 """
120 """
120 ig_ws = get_ignore_ws(fileid, request.GET)
121 ig_ws = get_ignore_ws(fileid, request.GET)
121 ln_ctx = (get_line_ctx(fileid, request.GET) or 3) * 2
122 ln_ctx = (get_line_ctx(fileid, request.GET) or 3) * 2
122
123
123 params = defaultdict(list)
124 params = defaultdict(list)
124
125
125 # global option
126 # global option
126 if fileid is None:
127 if fileid is None:
127 if ln_ctx > 0:
128 if ln_ctx > 0:
128 params['context'] += [ln_ctx]
129 params['context'] += [ln_ctx]
129
130
130 if ig_ws:
131 if ig_ws:
131 ig_ws_key = 'ignorews'
132 ig_ws_key = 'ignorews'
132 ig_ws_val = 1
133 ig_ws_val = 1
133
134
134 # per file option
135 # per file option
135 else:
136 else:
136 params[fileid] += ['C:%s' % ln_ctx]
137 params[fileid] += ['C:%s' % ln_ctx]
137 ig_ws_key = fileid
138 ig_ws_key = fileid
138 ig_ws_val = 'WS:%s' % 1
139 ig_ws_val = 'WS:%s' % 1
139
140
140 if ig_ws:
141 if ig_ws:
141 params[ig_ws_key] += [ig_ws_val]
142 params[ig_ws_key] += [ig_ws_val]
142
143
143 lbl = _('%s line context') % ln_ctx
144 lbl = _('%s line context') % ln_ctx
144
145
145 params['anchor'] = fileid
146 params['anchor'] = fileid
146 return h.link_to(lbl, h.url.current(**params))
147 img = h.image('/images/icons/table_add.png', lbl, class_='icon')
148 return h.link_to(img, h.url.current(**params), title=lbl)
147
149
148
150
149 class ChangesetController(BaseRepoController):
151 class ChangesetController(BaseRepoController):
150
152
151 @LoginRequired()
153 @LoginRequired()
152 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
154 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
153 'repository.admin')
155 'repository.admin')
154 def __before__(self):
156 def __before__(self):
155 super(ChangesetController, self).__before__()
157 super(ChangesetController, self).__before__()
156 c.affected_files_cut_off = 60
158 c.affected_files_cut_off = 60
157
159
158 def index(self, revision):
160 def index(self, revision):
159
161
160 c.anchor_url = anchor_url
162 c.anchor_url = anchor_url
161 c.ignorews_url = _ignorews_url
163 c.ignorews_url = _ignorews_url
162 c.context_url = _context_url
164 c.context_url = _context_url
163
165
164 #get ranges of revisions if preset
166 #get ranges of revisions if preset
165 rev_range = revision.split('...')[:2]
167 rev_range = revision.split('...')[:2]
166 enable_comments = True
168 enable_comments = True
167 try:
169 try:
168 if len(rev_range) == 2:
170 if len(rev_range) == 2:
169 enable_comments = False
171 enable_comments = False
170 rev_start = rev_range[0]
172 rev_start = rev_range[0]
171 rev_end = rev_range[1]
173 rev_end = rev_range[1]
172 rev_ranges = c.rhodecode_repo.get_changesets(start=rev_start,
174 rev_ranges = c.rhodecode_repo.get_changesets(start=rev_start,
173 end=rev_end)
175 end=rev_end)
174 else:
176 else:
175 rev_ranges = [c.rhodecode_repo.get_changeset(revision)]
177 rev_ranges = [c.rhodecode_repo.get_changeset(revision)]
176
178
177 c.cs_ranges = list(rev_ranges)
179 c.cs_ranges = list(rev_ranges)
178 if not c.cs_ranges:
180 if not c.cs_ranges:
179 raise RepositoryError('Changeset range returned empty result')
181 raise RepositoryError('Changeset range returned empty result')
180
182
181 except (RepositoryError, ChangesetDoesNotExistError, Exception), e:
183 except (RepositoryError, ChangesetDoesNotExistError, Exception), e:
182 log.error(traceback.format_exc())
184 log.error(traceback.format_exc())
183 h.flash(str(e), category='warning')
185 h.flash(str(e), category='warning')
184 return redirect(url('home'))
186 return redirect(url('home'))
185
187
186 c.changes = OrderedDict()
188 c.changes = OrderedDict()
187
189
188 c.lines_added = 0 # count of lines added
190 c.lines_added = 0 # count of lines added
189 c.lines_deleted = 0 # count of lines removes
191 c.lines_deleted = 0 # count of lines removes
190
192
191 cumulative_diff = 0
193 cumulative_diff = 0
192 c.cut_off = False # defines if cut off limit is reached
194 c.cut_off = False # defines if cut off limit is reached
193
195
194 c.comments = []
196 c.comments = []
195 c.inline_comments = []
197 c.inline_comments = []
196 c.inline_cnt = 0
198 c.inline_cnt = 0
197 # Iterate over ranges (default changeset view is always one changeset)
199 # Iterate over ranges (default changeset view is always one changeset)
198 for changeset in c.cs_ranges:
200 for changeset in c.cs_ranges:
199 c.comments.extend(ChangesetCommentsModel()\
201 c.comments.extend(ChangesetCommentsModel()\
200 .get_comments(c.rhodecode_db_repo.repo_id,
202 .get_comments(c.rhodecode_db_repo.repo_id,
201 changeset.raw_id))
203 changeset.raw_id))
202 inlines = ChangesetCommentsModel()\
204 inlines = ChangesetCommentsModel()\
203 .get_inline_comments(c.rhodecode_db_repo.repo_id,
205 .get_inline_comments(c.rhodecode_db_repo.repo_id,
204 changeset.raw_id)
206 changeset.raw_id)
205 c.inline_comments.extend(inlines)
207 c.inline_comments.extend(inlines)
206 c.changes[changeset.raw_id] = []
208 c.changes[changeset.raw_id] = []
207 try:
209 try:
208 changeset_parent = changeset.parents[0]
210 changeset_parent = changeset.parents[0]
209 except IndexError:
211 except IndexError:
210 changeset_parent = None
212 changeset_parent = None
211
213
212 #==================================================================
214 #==================================================================
213 # ADDED FILES
215 # ADDED FILES
214 #==================================================================
216 #==================================================================
215 for node in changeset.added:
217 for node in changeset.added:
216 fid = h.FID(revision, node.path)
218 fid = h.FID(revision, node.path)
217 line_context_lcl = get_line_ctx(fid, request.GET)
219 line_context_lcl = get_line_ctx(fid, request.GET)
218 ign_whitespace_lcl = get_ignore_ws(fid, request.GET)
220 ign_whitespace_lcl = get_ignore_ws(fid, request.GET)
219 lim = self.cut_off_limit
221 lim = self.cut_off_limit
220 if cumulative_diff > self.cut_off_limit:
222 if cumulative_diff > self.cut_off_limit:
221 lim = -1
223 lim = -1
222 size, cs1, cs2, diff, st = wrapped_diff(filenode_old=None,
224 size, cs1, cs2, diff, st = wrapped_diff(filenode_old=None,
223 filenode_new=node,
225 filenode_new=node,
224 cut_off_limit=lim,
226 cut_off_limit=lim,
225 ignore_whitespace=ign_whitespace_lcl,
227 ignore_whitespace=ign_whitespace_lcl,
226 line_context=line_context_lcl,
228 line_context=line_context_lcl,
227 enable_comments=enable_comments)
229 enable_comments=enable_comments)
228 cumulative_diff += size
230 cumulative_diff += size
229 c.lines_added += st[0]
231 c.lines_added += st[0]
230 c.lines_deleted += st[1]
232 c.lines_deleted += st[1]
231 c.changes[changeset.raw_id].append(('added', node, diff,
233 c.changes[changeset.raw_id].append(('added', node, diff,
232 cs1, cs2, st))
234 cs1, cs2, st))
233
235
234 #==================================================================
236 #==================================================================
235 # CHANGED FILES
237 # CHANGED FILES
236 #==================================================================
238 #==================================================================
237 for node in changeset.changed:
239 for node in changeset.changed:
238 try:
240 try:
239 filenode_old = changeset_parent.get_node(node.path)
241 filenode_old = changeset_parent.get_node(node.path)
240 except ChangesetError:
242 except ChangesetError:
241 log.warning('Unable to fetch parent node for diff')
243 log.warning('Unable to fetch parent node for diff')
242 filenode_old = FileNode(node.path, '', EmptyChangeset())
244 filenode_old = FileNode(node.path, '', EmptyChangeset())
243
245
244 fid = h.FID(revision, node.path)
246 fid = h.FID(revision, node.path)
245 line_context_lcl = get_line_ctx(fid, request.GET)
247 line_context_lcl = get_line_ctx(fid, request.GET)
246 ign_whitespace_lcl = get_ignore_ws(fid, request.GET)
248 ign_whitespace_lcl = get_ignore_ws(fid, request.GET)
247 lim = self.cut_off_limit
249 lim = self.cut_off_limit
248 if cumulative_diff > self.cut_off_limit:
250 if cumulative_diff > self.cut_off_limit:
249 lim = -1
251 lim = -1
250 size, cs1, cs2, diff, st = wrapped_diff(filenode_old=filenode_old,
252 size, cs1, cs2, diff, st = wrapped_diff(filenode_old=filenode_old,
251 filenode_new=node,
253 filenode_new=node,
252 cut_off_limit=lim,
254 cut_off_limit=lim,
253 ignore_whitespace=ign_whitespace_lcl,
255 ignore_whitespace=ign_whitespace_lcl,
254 line_context=line_context_lcl,
256 line_context=line_context_lcl,
255 enable_comments=enable_comments)
257 enable_comments=enable_comments)
256 cumulative_diff += size
258 cumulative_diff += size
257 c.lines_added += st[0]
259 c.lines_added += st[0]
258 c.lines_deleted += st[1]
260 c.lines_deleted += st[1]
259 c.changes[changeset.raw_id].append(('changed', node, diff,
261 c.changes[changeset.raw_id].append(('changed', node, diff,
260 cs1, cs2, st))
262 cs1, cs2, st))
261
263
262 #==================================================================
264 #==================================================================
263 # REMOVED FILES
265 # REMOVED FILES
264 #==================================================================
266 #==================================================================
265 for node in changeset.removed:
267 for node in changeset.removed:
266 c.changes[changeset.raw_id].append(('removed', node, None,
268 c.changes[changeset.raw_id].append(('removed', node, None,
267 None, None, (0, 0)))
269 None, None, (0, 0)))
268
270
269 # count inline comments
271 # count inline comments
270 for path, lines in c.inline_comments:
272 for path, lines in c.inline_comments:
271 for comments in lines.values():
273 for comments in lines.values():
272 c.inline_cnt += len(comments)
274 c.inline_cnt += len(comments)
273
275
274 if len(c.cs_ranges) == 1:
276 if len(c.cs_ranges) == 1:
275 c.changeset = c.cs_ranges[0]
277 c.changeset = c.cs_ranges[0]
276 c.changes = c.changes[c.changeset.raw_id]
278 c.changes = c.changes[c.changeset.raw_id]
277
279
278 return render('changeset/changeset.html')
280 return render('changeset/changeset.html')
279 else:
281 else:
280 return render('changeset/changeset_range.html')
282 return render('changeset/changeset_range.html')
281
283
282 def raw_changeset(self, revision):
284 def raw_changeset(self, revision):
283
285
284 method = request.GET.get('diff', 'show')
286 method = request.GET.get('diff', 'show')
285 ignore_whitespace = request.GET.get('ignorews') == '1'
287 ignore_whitespace = request.GET.get('ignorews') == '1'
286 line_context = request.GET.get('context', 3)
288 line_context = request.GET.get('context', 3)
287 try:
289 try:
288 c.scm_type = c.rhodecode_repo.alias
290 c.scm_type = c.rhodecode_repo.alias
289 c.changeset = c.rhodecode_repo.get_changeset(revision)
291 c.changeset = c.rhodecode_repo.get_changeset(revision)
290 except RepositoryError:
292 except RepositoryError:
291 log.error(traceback.format_exc())
293 log.error(traceback.format_exc())
292 return redirect(url('home'))
294 return redirect(url('home'))
293 else:
295 else:
294 try:
296 try:
295 c.changeset_parent = c.changeset.parents[0]
297 c.changeset_parent = c.changeset.parents[0]
296 except IndexError:
298 except IndexError:
297 c.changeset_parent = None
299 c.changeset_parent = None
298 c.changes = []
300 c.changes = []
299
301
300 for node in c.changeset.added:
302 for node in c.changeset.added:
301 filenode_old = FileNode(node.path, '')
303 filenode_old = FileNode(node.path, '')
302 if filenode_old.is_binary or node.is_binary:
304 if filenode_old.is_binary or node.is_binary:
303 diff = _('binary file') + '\n'
305 diff = _('binary file') + '\n'
304 else:
306 else:
305 f_gitdiff = diffs.get_gitdiff(filenode_old, node,
307 f_gitdiff = diffs.get_gitdiff(filenode_old, node,
306 ignore_whitespace=ignore_whitespace,
308 ignore_whitespace=ignore_whitespace,
307 context=line_context)
309 context=line_context)
308 diff = diffs.DiffProcessor(f_gitdiff,
310 diff = diffs.DiffProcessor(f_gitdiff,
309 format='gitdiff').raw_diff()
311 format='gitdiff').raw_diff()
310
312
311 cs1 = None
313 cs1 = None
312 cs2 = node.last_changeset.raw_id
314 cs2 = node.last_changeset.raw_id
313 c.changes.append(('added', node, diff, cs1, cs2))
315 c.changes.append(('added', node, diff, cs1, cs2))
314
316
315 for node in c.changeset.changed:
317 for node in c.changeset.changed:
316 filenode_old = c.changeset_parent.get_node(node.path)
318 filenode_old = c.changeset_parent.get_node(node.path)
317 if filenode_old.is_binary or node.is_binary:
319 if filenode_old.is_binary or node.is_binary:
318 diff = _('binary file')
320 diff = _('binary file')
319 else:
321 else:
320 f_gitdiff = diffs.get_gitdiff(filenode_old, node,
322 f_gitdiff = diffs.get_gitdiff(filenode_old, node,
321 ignore_whitespace=ignore_whitespace,
323 ignore_whitespace=ignore_whitespace,
322 context=line_context)
324 context=line_context)
323 diff = diffs.DiffProcessor(f_gitdiff,
325 diff = diffs.DiffProcessor(f_gitdiff,
324 format='gitdiff').raw_diff()
326 format='gitdiff').raw_diff()
325
327
326 cs1 = filenode_old.last_changeset.raw_id
328 cs1 = filenode_old.last_changeset.raw_id
327 cs2 = node.last_changeset.raw_id
329 cs2 = node.last_changeset.raw_id
328 c.changes.append(('changed', node, diff, cs1, cs2))
330 c.changes.append(('changed', node, diff, cs1, cs2))
329
331
330 response.content_type = 'text/plain'
332 response.content_type = 'text/plain'
331
333
332 if method == 'download':
334 if method == 'download':
333 response.content_disposition = 'attachment; filename=%s.patch' \
335 response.content_disposition = 'attachment; filename=%s.patch' \
334 % revision
336 % revision
335
337
336 c.parent_tmpl = ''.join(['# Parent %s\n' % x.raw_id for x in
338 c.parent_tmpl = ''.join(['# Parent %s\n' % x.raw_id for x in
337 c.changeset.parents])
339 c.changeset.parents])
338
340
339 c.diffs = ''
341 c.diffs = ''
340 for x in c.changes:
342 for x in c.changes:
341 c.diffs += x[2]
343 c.diffs += x[2]
342
344
343 return render('changeset/raw_changeset.html')
345 return render('changeset/raw_changeset.html')
344
346
345 def comment(self, repo_name, revision):
347 def comment(self, repo_name, revision):
346 ChangesetCommentsModel().create(text=request.POST.get('text'),
348 ChangesetCommentsModel().create(text=request.POST.get('text'),
347 repo_id=c.rhodecode_db_repo.repo_id,
349 repo_id=c.rhodecode_db_repo.repo_id,
348 user_id=c.rhodecode_user.user_id,
350 user_id=c.rhodecode_user.user_id,
349 revision=revision,
351 revision=revision,
350 f_path=request.POST.get('f_path'),
352 f_path=request.POST.get('f_path'),
351 line_no=request.POST.get('line'))
353 line_no=request.POST.get('line'))
352 Session.commit()
354 Session.commit()
353 return redirect(h.url('changeset_home', repo_name=repo_name,
355 return redirect(h.url('changeset_home', repo_name=repo_name,
354 revision=revision))
356 revision=revision))
355
357
356 @jsonify
358 @jsonify
357 def delete_comment(self, repo_name, comment_id):
359 def delete_comment(self, repo_name, comment_id):
358 co = ChangesetComment.get(comment_id)
360 co = ChangesetComment.get(comment_id)
359 owner = lambda: co.author.user_id == c.rhodecode_user.user_id
361 owner = lambda: co.author.user_id == c.rhodecode_user.user_id
360 if h.HasPermissionAny('hg.admin', 'repository.admin')() or owner:
362 if h.HasPermissionAny('hg.admin', 'repository.admin')() or owner:
361 ChangesetCommentsModel().delete(comment=co)
363 ChangesetCommentsModel().delete(comment=co)
362 Session.commit()
364 Session.commit()
363 return True
365 return True
364 else:
366 else:
365 raise HTTPForbidden()
367 raise HTTPForbidden()
@@ -1,206 +1,189 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2
2
3 <%inherit file="/base/base.html"/>
3 <%inherit file="/base/base.html"/>
4
4
5 <%def name="title()">
5 <%def name="title()">
6 ${c.repo_name} ${_('Changeset')} - r${c.changeset.revision}:${h.short_id(c.changeset.raw_id)} - ${c.rhodecode_name}
6 ${c.repo_name} ${_('Changeset')} - r${c.changeset.revision}:${h.short_id(c.changeset.raw_id)} - ${c.rhodecode_name}
7 </%def>
7 </%def>
8
8
9 <%def name="breadcrumbs_links()">
9 <%def name="breadcrumbs_links()">
10 ${h.link_to(u'Home',h.url('/'))}
10 ${h.link_to(u'Home',h.url('/'))}
11 &raquo;
11 &raquo;
12 ${h.link_to(c.repo_name,h.url('summary_home',repo_name=c.repo_name))}
12 ${h.link_to(c.repo_name,h.url('summary_home',repo_name=c.repo_name))}
13 &raquo;
13 &raquo;
14 ${_('Changeset')} - r${c.changeset.revision}:${h.short_id(c.changeset.raw_id)}
14 ${_('Changeset')} - r${c.changeset.revision}:${h.short_id(c.changeset.raw_id)}
15 </%def>
15 </%def>
16
16
17 <%def name="page_nav()">
17 <%def name="page_nav()">
18 ${self.menu('changelog')}
18 ${self.menu('changelog')}
19 </%def>
19 </%def>
20
20
21 <%def name="main()">
21 <%def name="main()">
22 <div class="box">
22 <div class="box">
23 <!-- box / title -->
23 <!-- box / title -->
24 <div class="title">
24 <div class="title">
25 ${self.breadcrumbs()}
25 ${self.breadcrumbs()}
26 </div>
26 </div>
27 <div class="table">
27 <div class="table">
28 <div class="diffblock">
28 <div class="diffblock">
29 <div class="code-header">
29 <div class="code-header">
30 <div class="date">${c.changeset.revision}:
30 <div class="date">${c.changeset.revision}:
31 ${h.link_to(h.short_id(c.changeset.raw_id),h.url('changeset_home',repo_name=c.repo_name,revision=c.changeset.raw_id))}
31 ${h.link_to(h.short_id(c.changeset.raw_id),h.url('changeset_home',repo_name=c.repo_name,revision=c.changeset.raw_id))}
32 ${c.changeset.date}</div>
32 ${c.changeset.date}</div>
33 <div class="diff-menu-wrapper">
33 <span class="diff-actions">
34 <img class="diff-menu-activate" style="cursor: pointer" alt="diff-menu" src="${h.url('/images/icons/script_gear.png')}" />
34 <a href="${h.url('raw_changeset_home',repo_name=c.repo_name,revision=c.changeset.raw_id,diff='show')}" title="${_('raw diff')}"><img class="icon" src="${h.url('/images/icons/page_white_text.png')}"/></a>
35 <div class="diff-menu" style="display:none">
35 <a href="${h.url('raw_changeset_home',repo_name=c.repo_name,revision=c.changeset.raw_id,diff='download')}" title="${_('download diff')}"><img class="icon" src="${h.url('/images/icons/down_16.png')}"/></a>
36 <ul>
36 ${c.ignorews_url()}
37 <li>${h.link_to(_('raw diff'),h.url('raw_changeset_home',repo_name=c.repo_name,revision=c.changeset.raw_id,diff='show'))}</li>
37 ${c.context_url()}
38 <li>${h.link_to(_('download diff'),h.url('raw_changeset_home',repo_name=c.repo_name,revision=c.changeset.raw_id,diff='download'))}</li>
38 </span>
39 <li>${c.ignorews_url()}</li>
40 <li>${c.context_url()}</li>
41 </ul>
42 </div>
43 </div>
44 <div class="comments-number" style="float:right;padding-right:5px">${len(c.comments)} comment(s) (${c.inline_cnt} ${_('inline')})</div>
39 <div class="comments-number" style="float:right;padding-right:5px">${len(c.comments)} comment(s) (${c.inline_cnt} ${_('inline')})</div>
45 </div>
40 </div>
46 </div>
41 </div>
47 <div id="changeset_content">
42 <div id="changeset_content">
48 <div class="container">
43 <div class="container">
49 <div class="left">
44 <div class="left">
50 <div class="author">
45 <div class="author">
51 <div class="gravatar">
46 <div class="gravatar">
52 <img alt="gravatar" src="${h.gravatar_url(h.email(c.changeset.author),20)}"/>
47 <img alt="gravatar" src="${h.gravatar_url(h.email(c.changeset.author),20)}"/>
53 </div>
48 </div>
54 <span>${h.person(c.changeset.author)}</span><br/>
49 <span>${h.person(c.changeset.author)}</span><br/>
55 <span><a href="mailto:${h.email_or_none(c.changeset.author)}">${h.email_or_none(c.changeset.author)}</a></span><br/>
50 <span><a href="mailto:${h.email_or_none(c.changeset.author)}">${h.email_or_none(c.changeset.author)}</a></span><br/>
56 </div>
51 </div>
57 <div class="message">${h.wrap_paragraphs(c.changeset.message)}</div>
52 <div class="message">${h.wrap_paragraphs(c.changeset.message)}</div>
58 </div>
53 </div>
59 <div class="right">
54 <div class="right">
60 <div class="changes">
55 <div class="changes">
61 % if len(c.changeset.affected_files) <= c.affected_files_cut_off:
56 % if len(c.changeset.affected_files) <= c.affected_files_cut_off:
62 <span class="removed" title="${_('removed')}">${len(c.changeset.removed)}</span>
57 <span class="removed" title="${_('removed')}">${len(c.changeset.removed)}</span>
63 <span class="changed" title="${_('changed')}">${len(c.changeset.changed)}</span>
58 <span class="changed" title="${_('changed')}">${len(c.changeset.changed)}</span>
64 <span class="added" title="${_('added')}">${len(c.changeset.added)}</span>
59 <span class="added" title="${_('added')}">${len(c.changeset.added)}</span>
65 % else:
60 % else:
66 <span class="removed" title="${_('affected %s files') % len(c.changeset.affected_files)}">!</span>
61 <span class="removed" title="${_('affected %s files') % len(c.changeset.affected_files)}">!</span>
67 <span class="changed" title="${_('affected %s files') % len(c.changeset.affected_files)}">!</span>
62 <span class="changed" title="${_('affected %s files') % len(c.changeset.affected_files)}">!</span>
68 <span class="added" title="${_('affected %s files') % len(c.changeset.affected_files)}">!</span>
63 <span class="added" title="${_('affected %s files') % len(c.changeset.affected_files)}">!</span>
69 % endif
64 % endif
70 </div>
65 </div>
71
66
72 %if c.changeset.parents:
67 %if c.changeset.parents:
73 %for p_cs in reversed(c.changeset.parents):
68 %for p_cs in reversed(c.changeset.parents):
74 <div class="parent">${_('Parent')}
69 <div class="parent">${_('Parent')}
75 <span class="changeset_id">${p_cs.revision}:<span class="changeset_hash">${h.link_to(h.short_id(p_cs.raw_id),
70 <span class="changeset_id">${p_cs.revision}:<span class="changeset_hash">${h.link_to(h.short_id(p_cs.raw_id),
76 h.url('changeset_home',repo_name=c.repo_name,revision=p_cs.raw_id),title=p_cs.message)}</span></span>
71 h.url('changeset_home',repo_name=c.repo_name,revision=p_cs.raw_id),title=p_cs.message)}</span></span>
77 </div>
72 </div>
78 %endfor
73 %endfor
79 %else:
74 %else:
80 <div class="parent">${_('No parents')}</div>
75 <div class="parent">${_('No parents')}</div>
81 %endif
76 %endif
82 <span class="logtags">
77 <span class="logtags">
83 %if len(c.changeset.parents)>1:
78 %if len(c.changeset.parents)>1:
84 <span class="merge">${_('merge')}</span>
79 <span class="merge">${_('merge')}</span>
85 %endif
80 %endif
86 <span class="branchtag" title="${'%s %s' % (_('branch'),c.changeset.branch)}">
81 <span class="branchtag" title="${'%s %s' % (_('branch'),c.changeset.branch)}">
87 ${h.link_to(c.changeset.branch,h.url('files_home',repo_name=c.repo_name,revision=c.changeset.raw_id))}</span>
82 ${h.link_to(c.changeset.branch,h.url('files_home',repo_name=c.repo_name,revision=c.changeset.raw_id))}</span>
88 %for tag in c.changeset.tags:
83 %for tag in c.changeset.tags:
89 <span class="tagtag" title="${'%s %s' % (_('tag'),tag)}">
84 <span class="tagtag" title="${'%s %s' % (_('tag'),tag)}">
90 ${h.link_to(tag,h.url('files_home',repo_name=c.repo_name,revision=c.changeset.raw_id))}</span>
85 ${h.link_to(tag,h.url('files_home',repo_name=c.repo_name,revision=c.changeset.raw_id))}</span>
91 %endfor
86 %endfor
92 </span>
87 </span>
93 </div>
88 </div>
94 </div>
89 </div>
95 <span>
90 <span>
96 ${_('%s files affected with %s additions and %s deletions:') % (len(c.changeset.affected_files),c.lines_added,c.lines_deleted)}
91 ${_('%s files affected with %s additions and %s deletions:') % (len(c.changeset.affected_files),c.lines_added,c.lines_deleted)}
97 </span>
92 </span>
98 <div class="cs_files">
93 <div class="cs_files">
99 %for change,filenode,diff,cs1,cs2,stat in c.changes:
94 %for change,filenode,diff,cs1,cs2,stat in c.changes:
100 <div class="cs_${change}">
95 <div class="cs_${change}">
101 <div class="node">
96 <div class="node">
102 %if change != 'removed':
97 %if change != 'removed':
103 ${h.link_to(h.safe_unicode(filenode.path),c.anchor_url(filenode.changeset.raw_id,filenode.path))}
98 ${h.link_to(h.safe_unicode(filenode.path),c.anchor_url(filenode.changeset.raw_id,filenode.path))}
104 %else:
99 %else:
105 ${h.link_to(h.safe_unicode(filenode.path),h.url.current(anchor=h.FID('',filenode.path)))}
100 ${h.link_to(h.safe_unicode(filenode.path),h.url.current(anchor=h.FID('',filenode.path)))}
106 %endif
101 %endif
107 </div>
102 </div>
108 <div class="changes">${h.fancy_file_stats(stat)}</div>
103 <div class="changes">${h.fancy_file_stats(stat)}</div>
109 </div>
104 </div>
110 %endfor
105 %endfor
111 % if c.cut_off:
106 % if c.cut_off:
112 ${_('Changeset was too big and was cut off...')}
107 ${_('Changeset was too big and was cut off...')}
113 % endif
108 % endif
114 </div>
109 </div>
115 </div>
110 </div>
116
111
117 </div>
112 </div>
118
113
119 ## diff block
114 ## diff block
120 <%namespace name="diff_block" file="/changeset/diff_block.html"/>
115 <%namespace name="diff_block" file="/changeset/diff_block.html"/>
121 ${diff_block.diff_block(c.changes)}
116 ${diff_block.diff_block(c.changes)}
122
117
123 ## template for inline comment form
118 ## template for inline comment form
124 <%namespace name="comment" file="/changeset/changeset_file_comment.html"/>
119 <%namespace name="comment" file="/changeset/changeset_file_comment.html"/>
125 ${comment.comment_inline_form(c.changeset)}
120 ${comment.comment_inline_form(c.changeset)}
126
121
127 ${comment.comments(c.changeset)}
122 ${comment.comments(c.changeset)}
128
123
129 <script type="text/javascript">
124 <script type="text/javascript">
130 var deleteComment = function(comment_id){
125 var deleteComment = function(comment_id){
131
126
132 var url = "${url('changeset_comment_delete',repo_name=c.repo_name,comment_id='__COMMENT_ID__')}".replace('__COMMENT_ID__',comment_id);
127 var url = "${url('changeset_comment_delete',repo_name=c.repo_name,comment_id='__COMMENT_ID__')}".replace('__COMMENT_ID__',comment_id);
133 var postData = '_method=delete';
128 var postData = '_method=delete';
134 var success = function(o){
129 var success = function(o){
135 var n = YUD.get('comment-'+comment_id);
130 var n = YUD.get('comment-'+comment_id);
136 n.parentNode.removeChild(n);
131 n.parentNode.removeChild(n);
137 }
132 }
138 ajaxPOST(url,postData,success);
133 ajaxPOST(url,postData,success);
139 }
134 }
140
135
141 YUE.onDOMReady(function(){
136 YUE.onDOMReady(function(){
142
137
143 YUE.on(YUQ('.diff-menu-activate'),'click',function(e){
144 var act = e.currentTarget.nextElementSibling;
145
146 if(YUD.hasClass(act,'active')){
147 YUD.removeClass(act,'active');
148 YUD.setStyle(act,'display','none');
149 }else{
150 YUD.addClass(act,'active');
151 YUD.setStyle(act,'display','');
152 }
153 });
154
155 YUE.on(YUQ('.show-inline-comments'),'change',function(e){
138 YUE.on(YUQ('.show-inline-comments'),'change',function(e){
156 var show = 'none';
139 var show = 'none';
157 var target = e.currentTarget;
140 var target = e.currentTarget;
158 if(target.checked){
141 if(target.checked){
159 var show = ''
142 var show = ''
160 }
143 }
161 var boxid = YUD.getAttribute(target,'id_for');
144 var boxid = YUD.getAttribute(target,'id_for');
162 var comments = YUQ('#{0} .inline-comments'.format(boxid));
145 var comments = YUQ('#{0} .inline-comments'.format(boxid));
163 for(c in comments){
146 for(c in comments){
164 YUD.setStyle(comments[c],'display',show);
147 YUD.setStyle(comments[c],'display',show);
165 }
148 }
166 var btns = YUQ('#{0} .inline-comments-button'.format(boxid));
149 var btns = YUQ('#{0} .inline-comments-button'.format(boxid));
167 for(c in btns){
150 for(c in btns){
168 YUD.setStyle(btns[c],'display',show);
151 YUD.setStyle(btns[c],'display',show);
169 }
152 }
170 })
153 })
171
154
172 YUE.on(YUQ('.line'),'click',function(e){
155 YUE.on(YUQ('.line'),'click',function(e){
173 var tr = e.currentTarget;
156 var tr = e.currentTarget;
174 injectInlineForm(tr);
157 injectInlineForm(tr);
175 });
158 });
176
159
177 // inject comments into they proper positions
160 // inject comments into they proper positions
178 var file_comments = YUQ('.inline-comment-placeholder');
161 var file_comments = YUQ('.inline-comment-placeholder');
179
162
180 for (f in file_comments){
163 for (f in file_comments){
181 var box = file_comments[f];
164 var box = file_comments[f];
182 var inlines = box.children;
165 var inlines = box.children;
183 for(var i=0; i<inlines.length; i++){
166 for(var i=0; i<inlines.length; i++){
184 try{
167 try{
185
168
186 var inline = inlines[i];
169 var inline = inlines[i];
187 var lineno = YUD.getAttribute(inlines[i],'line');
170 var lineno = YUD.getAttribute(inlines[i],'line');
188 var lineid = "{0}_{1}".format(YUD.getAttribute(inline,'target_id'),lineno);
171 var lineid = "{0}_{1}".format(YUD.getAttribute(inline,'target_id'),lineno);
189 var target_line = YUD.get(lineid);
172 var target_line = YUD.get(lineid);
190
173
191 var add = createInlineAddButton(target_line.parentNode,'${_("add another comment")}');
174 var add = createInlineAddButton(target_line.parentNode,'${_("add another comment")}');
192 YUD.insertAfter(add,target_line.parentNode);
175 YUD.insertAfter(add,target_line.parentNode);
193
176
194 var comment = new YAHOO.util.Element(tableTr('inline-comments',inline.innerHTML))
177 var comment = new YAHOO.util.Element(tableTr('inline-comments',inline.innerHTML))
195 YUD.insertAfter(comment,target_line.parentNode);
178 YUD.insertAfter(comment,target_line.parentNode);
196 }catch(e){
179 }catch(e){
197 console.log(e);
180 console.log(e);
198 }
181 }
199 }
182 }
200 }
183 }
201 })
184 })
202
185
203 </script>
186 </script>
204
187
205 </div>
188 </div>
206 </%def>
189 </%def>
@@ -1,46 +1,41 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 ##usage:
2 ##usage:
3 ## <%namespace name="diff_block" file="/changeset/diff_block.html"/>
3 ## <%namespace name="diff_block" file="/changeset/diff_block.html"/>
4 ## ${diff_block.diff_block(changes)}
4 ## ${diff_block.diff_block(changes)}
5 ##
5 ##
6 <%def name="diff_block(changes)">
6 <%def name="diff_block(changes)">
7
7
8 %for change,filenode,diff,cs1,cs2,stat in changes:
8 %for change,filenode,diff,cs1,cs2,stat in changes:
9 %if change !='removed':
9 %if change !='removed':
10 <div id="${h.FID(filenode.changeset.raw_id,filenode.path)}" style="clear:both;height:90px;margin-top:-60px"></div>
10 <div id="${h.FID(filenode.changeset.raw_id,filenode.path)}" style="clear:both;height:90px;margin-top:-60px"></div>
11 <div class="diffblock margined comm">
11 <div class="diffblock margined comm">
12 <div class="code-header">
12 <div class="code-header">
13 <div class="changeset_header">
13 <div class="changeset_header">
14 <div class="changeset_file">
14 <div class="changeset_file">
15 ${h.link_to_if(change!='removed',h.safe_unicode(filenode.path),h.url('files_home',repo_name=c.repo_name,
15 ${h.link_to_if(change!='removed',h.safe_unicode(filenode.path),h.url('files_home',repo_name=c.repo_name,
16 revision=filenode.changeset.raw_id,f_path=h.safe_unicode(filenode.path)))}
16 revision=filenode.changeset.raw_id,f_path=h.safe_unicode(filenode.path)))}
17 </div>
17 </div>
18 <div class="diff-menu-wrapper">
18 <span class="diff-actions">
19 <img class="diff-menu-activate" style="margin-bottom:-6px;cursor: pointer" alt="diff-menu" src="${h.url('/images/icons/script_gear.png')}" />
19 <a href="${h.url('files_diff_home',repo_name=c.repo_name,f_path=h.safe_unicode(filenode.path),diff2=cs2,diff1=cs1,diff='diff',fulldiff=1)}" title="${_('diff')}"><img class="icon" src="${h.url('/images/icons/page_white_text.png')}"/></a>
20 <div class="diff-menu" style="display:none">
20 <a href="${h.url('files_diff_home',repo_name=c.repo_name,f_path=h.safe_unicode(filenode.path),diff2=cs2,diff1=cs1,diff='raw')}" title="${_('raw diff')}"><img class="icon" src="${h.url('/images/icons/page_white_text.png')}"/></a>
21 <ul>
21 <a href="${h.url('files_diff_home',repo_name=c.repo_name,f_path=h.safe_unicode(filenode.path),diff2=cs2,diff1=cs1,diff='download')}" title="${_('download diff')}"><img class="icon" src="${h.url('/images/icons/down_16.png')}"/></a>
22 <li>${h.link_to(_('diff'),h.url('files_diff_home',repo_name=c.repo_name,f_path=h.safe_unicode(filenode.path),diff2=cs2,diff1=cs1,diff='diff',fulldiff=1))}</li>
22 ${c.ignorews_url(h.FID(filenode.changeset.raw_id,filenode.path))}
23 <li>${h.link_to(_('raw diff'),h.url('files_diff_home',repo_name=c.repo_name,f_path=h.safe_unicode(filenode.path),diff2=cs2,diff1=cs1,diff='raw'))}</li>
23 ${c.context_url(h.FID(filenode.changeset.raw_id,filenode.path))}
24 <li>${h.link_to(_('download diff'),h.url('files_diff_home',repo_name=c.repo_name,f_path=h.safe_unicode(filenode.path),diff2=cs2,diff1=cs1,diff='download'))}</li>
24 </span>
25 <li>${c.ignorews_url(h.FID(filenode.changeset.raw_id,filenode.path))}</li>
26 <li>${c.context_url(h.FID(filenode.changeset.raw_id,filenode.path))}</li>
27 </ul>
28 </div>
29 </div>
30 <span style="float:right;margin-top:-3px">
25 <span style="float:right;margin-top:-3px">
31 <label>
26 <label>
32 ${_('show inline comments')}
27 ${_('show inline comments')}
33 ${h.checkbox('',checked="checked",class_="show-inline-comments",id_for=h.FID(filenode.changeset.raw_id,filenode.path))}
28 ${h.checkbox('',checked="checked",class_="show-inline-comments",id_for=h.FID(filenode.changeset.raw_id,filenode.path))}
34 </label>
29 </label>
35 </span>
30 </span>
36 </div>
31 </div>
37 </div>
32 </div>
38 <div class="code-body">
33 <div class="code-body">
39 <div class="full_f_path" path="${h.safe_unicode(filenode.path)}"></div>
34 <div class="full_f_path" path="${h.safe_unicode(filenode.path)}"></div>
40 ${diff|n}
35 ${diff|n}
41 </div>
36 </div>
42 </div>
37 </div>
43 %endif
38 %endif
44 %endfor
39 %endfor
45
40
46 </%def> No newline at end of file
41 </%def>
General Comments 0
You need to be logged in to leave comments. Login now