##// END OF EJS Templates
comments: skip already deleted comments and make sure we don't raise unnecessary errors.
marcink -
r1330:17b0bbae default
parent child Browse files
Show More
@@ -1,475 +1,480 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 commit controller for RhodeCode showing changes between commits
22 commit controller for RhodeCode showing changes between commits
23 """
23 """
24
24
25 import logging
25 import logging
26
26
27 from collections import defaultdict
27 from collections import defaultdict
28 from webob.exc import HTTPForbidden, HTTPBadRequest, HTTPNotFound
28 from webob.exc import HTTPForbidden, HTTPBadRequest, HTTPNotFound
29
29
30 from pylons import tmpl_context as c, request, response
30 from pylons import tmpl_context as c, request, response
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
33
34 from rhodecode.lib import auth
34 from rhodecode.lib import auth
35 from rhodecode.lib import diffs, codeblocks
35 from rhodecode.lib import diffs, codeblocks
36 from rhodecode.lib.auth import (
36 from rhodecode.lib.auth import (
37 LoginRequired, HasRepoPermissionAnyDecorator, NotAnonymous)
37 LoginRequired, HasRepoPermissionAnyDecorator, NotAnonymous)
38 from rhodecode.lib.base import BaseRepoController, render
38 from rhodecode.lib.base import BaseRepoController, render
39 from rhodecode.lib.compat import OrderedDict
39 from rhodecode.lib.compat import OrderedDict
40 from rhodecode.lib.exceptions import StatusChangeOnClosedPullRequestError
40 from rhodecode.lib.exceptions import StatusChangeOnClosedPullRequestError
41 import rhodecode.lib.helpers as h
41 import rhodecode.lib.helpers as h
42 from rhodecode.lib.utils import action_logger, jsonify
42 from rhodecode.lib.utils import action_logger, jsonify
43 from rhodecode.lib.utils2 import safe_unicode
43 from rhodecode.lib.utils2 import safe_unicode
44 from rhodecode.lib.vcs.backends.base import EmptyCommit
44 from rhodecode.lib.vcs.backends.base import EmptyCommit
45 from rhodecode.lib.vcs.exceptions import (
45 from rhodecode.lib.vcs.exceptions import (
46 RepositoryError, CommitDoesNotExistError, NodeDoesNotExistError)
46 RepositoryError, CommitDoesNotExistError, NodeDoesNotExistError)
47 from rhodecode.model.db import ChangesetComment, ChangesetStatus
47 from rhodecode.model.db import ChangesetComment, ChangesetStatus
48 from rhodecode.model.changeset_status import ChangesetStatusModel
48 from rhodecode.model.changeset_status import ChangesetStatusModel
49 from rhodecode.model.comment import CommentsModel
49 from rhodecode.model.comment import CommentsModel
50 from rhodecode.model.meta import Session
50 from rhodecode.model.meta import Session
51 from rhodecode.model.repo import RepoModel
51 from rhodecode.model.repo import RepoModel
52
52
53
53
54 log = logging.getLogger(__name__)
54 log = logging.getLogger(__name__)
55
55
56
56
57 def _update_with_GET(params, GET):
57 def _update_with_GET(params, GET):
58 for k in ['diff1', 'diff2', 'diff']:
58 for k in ['diff1', 'diff2', 'diff']:
59 params[k] += GET.getall(k)
59 params[k] += GET.getall(k)
60
60
61
61
62 def get_ignore_ws(fid, GET):
62 def get_ignore_ws(fid, GET):
63 ig_ws_global = GET.get('ignorews')
63 ig_ws_global = GET.get('ignorews')
64 ig_ws = filter(lambda k: k.startswith('WS'), GET.getall(fid))
64 ig_ws = filter(lambda k: k.startswith('WS'), GET.getall(fid))
65 if ig_ws:
65 if ig_ws:
66 try:
66 try:
67 return int(ig_ws[0].split(':')[-1])
67 return int(ig_ws[0].split(':')[-1])
68 except Exception:
68 except Exception:
69 pass
69 pass
70 return ig_ws_global
70 return ig_ws_global
71
71
72
72
73 def _ignorews_url(GET, fileid=None):
73 def _ignorews_url(GET, fileid=None):
74 fileid = str(fileid) if fileid else None
74 fileid = str(fileid) if fileid else None
75 params = defaultdict(list)
75 params = defaultdict(list)
76 _update_with_GET(params, GET)
76 _update_with_GET(params, GET)
77 label = _('Show whitespace')
77 label = _('Show whitespace')
78 tooltiplbl = _('Show whitespace for all diffs')
78 tooltiplbl = _('Show whitespace for all diffs')
79 ig_ws = get_ignore_ws(fileid, GET)
79 ig_ws = get_ignore_ws(fileid, GET)
80 ln_ctx = get_line_ctx(fileid, GET)
80 ln_ctx = get_line_ctx(fileid, GET)
81
81
82 if ig_ws is None:
82 if ig_ws is None:
83 params['ignorews'] += [1]
83 params['ignorews'] += [1]
84 label = _('Ignore whitespace')
84 label = _('Ignore whitespace')
85 tooltiplbl = _('Ignore whitespace for all diffs')
85 tooltiplbl = _('Ignore whitespace for all diffs')
86 ctx_key = 'context'
86 ctx_key = 'context'
87 ctx_val = ln_ctx
87 ctx_val = ln_ctx
88
88
89 # if we have passed in ln_ctx pass it along to our params
89 # if we have passed in ln_ctx pass it along to our params
90 if ln_ctx:
90 if ln_ctx:
91 params[ctx_key] += [ctx_val]
91 params[ctx_key] += [ctx_val]
92
92
93 if fileid:
93 if fileid:
94 params['anchor'] = 'a_' + fileid
94 params['anchor'] = 'a_' + fileid
95 return h.link_to(label, h.url.current(**params), title=tooltiplbl, class_='tooltip')
95 return h.link_to(label, h.url.current(**params), title=tooltiplbl, class_='tooltip')
96
96
97
97
98 def get_line_ctx(fid, GET):
98 def get_line_ctx(fid, GET):
99 ln_ctx_global = GET.get('context')
99 ln_ctx_global = GET.get('context')
100 if fid:
100 if fid:
101 ln_ctx = filter(lambda k: k.startswith('C'), GET.getall(fid))
101 ln_ctx = filter(lambda k: k.startswith('C'), GET.getall(fid))
102 else:
102 else:
103 _ln_ctx = filter(lambda k: k.startswith('C'), GET)
103 _ln_ctx = filter(lambda k: k.startswith('C'), GET)
104 ln_ctx = GET.get(_ln_ctx[0]) if _ln_ctx else ln_ctx_global
104 ln_ctx = GET.get(_ln_ctx[0]) if _ln_ctx else ln_ctx_global
105 if ln_ctx:
105 if ln_ctx:
106 ln_ctx = [ln_ctx]
106 ln_ctx = [ln_ctx]
107
107
108 if ln_ctx:
108 if ln_ctx:
109 retval = ln_ctx[0].split(':')[-1]
109 retval = ln_ctx[0].split(':')[-1]
110 else:
110 else:
111 retval = ln_ctx_global
111 retval = ln_ctx_global
112
112
113 try:
113 try:
114 return int(retval)
114 return int(retval)
115 except Exception:
115 except Exception:
116 return 3
116 return 3
117
117
118
118
119 def _context_url(GET, fileid=None):
119 def _context_url(GET, fileid=None):
120 """
120 """
121 Generates a url for context lines.
121 Generates a url for context lines.
122
122
123 :param fileid:
123 :param fileid:
124 """
124 """
125
125
126 fileid = str(fileid) if fileid else None
126 fileid = str(fileid) if fileid else None
127 ig_ws = get_ignore_ws(fileid, GET)
127 ig_ws = get_ignore_ws(fileid, GET)
128 ln_ctx = (get_line_ctx(fileid, GET) or 3) * 2
128 ln_ctx = (get_line_ctx(fileid, GET) or 3) * 2
129
129
130 params = defaultdict(list)
130 params = defaultdict(list)
131 _update_with_GET(params, GET)
131 _update_with_GET(params, GET)
132
132
133 if ln_ctx > 0:
133 if ln_ctx > 0:
134 params['context'] += [ln_ctx]
134 params['context'] += [ln_ctx]
135
135
136 if ig_ws:
136 if ig_ws:
137 ig_ws_key = 'ignorews'
137 ig_ws_key = 'ignorews'
138 ig_ws_val = 1
138 ig_ws_val = 1
139 params[ig_ws_key] += [ig_ws_val]
139 params[ig_ws_key] += [ig_ws_val]
140
140
141 lbl = _('Increase context')
141 lbl = _('Increase context')
142 tooltiplbl = _('Increase context for all diffs')
142 tooltiplbl = _('Increase context for all diffs')
143
143
144 if fileid:
144 if fileid:
145 params['anchor'] = 'a_' + fileid
145 params['anchor'] = 'a_' + fileid
146 return h.link_to(lbl, h.url.current(**params), title=tooltiplbl, class_='tooltip')
146 return h.link_to(lbl, h.url.current(**params), title=tooltiplbl, class_='tooltip')
147
147
148
148
149 class ChangesetController(BaseRepoController):
149 class ChangesetController(BaseRepoController):
150
150
151 def __before__(self):
151 def __before__(self):
152 super(ChangesetController, self).__before__()
152 super(ChangesetController, self).__before__()
153 c.affected_files_cut_off = 60
153 c.affected_files_cut_off = 60
154
154
155 def _index(self, commit_id_range, method):
155 def _index(self, commit_id_range, method):
156 c.ignorews_url = _ignorews_url
156 c.ignorews_url = _ignorews_url
157 c.context_url = _context_url
157 c.context_url = _context_url
158 c.fulldiff = fulldiff = request.GET.get('fulldiff')
158 c.fulldiff = fulldiff = request.GET.get('fulldiff')
159
159
160 # fetch global flags of ignore ws or context lines
160 # fetch global flags of ignore ws or context lines
161 context_lcl = get_line_ctx('', request.GET)
161 context_lcl = get_line_ctx('', request.GET)
162 ign_whitespace_lcl = get_ignore_ws('', request.GET)
162 ign_whitespace_lcl = get_ignore_ws('', request.GET)
163
163
164 # diff_limit will cut off the whole diff if the limit is applied
164 # diff_limit will cut off the whole diff if the limit is applied
165 # otherwise it will just hide the big files from the front-end
165 # otherwise it will just hide the big files from the front-end
166 diff_limit = self.cut_off_limit_diff
166 diff_limit = self.cut_off_limit_diff
167 file_limit = self.cut_off_limit_file
167 file_limit = self.cut_off_limit_file
168
168
169 # get ranges of commit ids if preset
169 # get ranges of commit ids if preset
170 commit_range = commit_id_range.split('...')[:2]
170 commit_range = commit_id_range.split('...')[:2]
171
171
172 try:
172 try:
173 pre_load = ['affected_files', 'author', 'branch', 'date',
173 pre_load = ['affected_files', 'author', 'branch', 'date',
174 'message', 'parents']
174 'message', 'parents']
175
175
176 if len(commit_range) == 2:
176 if len(commit_range) == 2:
177 commits = c.rhodecode_repo.get_commits(
177 commits = c.rhodecode_repo.get_commits(
178 start_id=commit_range[0], end_id=commit_range[1],
178 start_id=commit_range[0], end_id=commit_range[1],
179 pre_load=pre_load)
179 pre_load=pre_load)
180 commits = list(commits)
180 commits = list(commits)
181 else:
181 else:
182 commits = [c.rhodecode_repo.get_commit(
182 commits = [c.rhodecode_repo.get_commit(
183 commit_id=commit_id_range, pre_load=pre_load)]
183 commit_id=commit_id_range, pre_load=pre_load)]
184
184
185 c.commit_ranges = commits
185 c.commit_ranges = commits
186 if not c.commit_ranges:
186 if not c.commit_ranges:
187 raise RepositoryError(
187 raise RepositoryError(
188 'The commit range returned an empty result')
188 'The commit range returned an empty result')
189 except CommitDoesNotExistError:
189 except CommitDoesNotExistError:
190 msg = _('No such commit exists for this repository')
190 msg = _('No such commit exists for this repository')
191 h.flash(msg, category='error')
191 h.flash(msg, category='error')
192 raise HTTPNotFound()
192 raise HTTPNotFound()
193 except Exception:
193 except Exception:
194 log.exception("General failure")
194 log.exception("General failure")
195 raise HTTPNotFound()
195 raise HTTPNotFound()
196
196
197 c.changes = OrderedDict()
197 c.changes = OrderedDict()
198 c.lines_added = 0
198 c.lines_added = 0
199 c.lines_deleted = 0
199 c.lines_deleted = 0
200
200
201 # auto collapse if we have more than limit
201 # auto collapse if we have more than limit
202 collapse_limit = diffs.DiffProcessor._collapse_commits_over
202 collapse_limit = diffs.DiffProcessor._collapse_commits_over
203 c.collapse_all_commits = len(c.commit_ranges) > collapse_limit
203 c.collapse_all_commits = len(c.commit_ranges) > collapse_limit
204
204
205 c.commit_statuses = ChangesetStatus.STATUSES
205 c.commit_statuses = ChangesetStatus.STATUSES
206 c.inline_comments = []
206 c.inline_comments = []
207 c.files = []
207 c.files = []
208
208
209 c.statuses = []
209 c.statuses = []
210 c.comments = []
210 c.comments = []
211 if len(c.commit_ranges) == 1:
211 if len(c.commit_ranges) == 1:
212 commit = c.commit_ranges[0]
212 commit = c.commit_ranges[0]
213 c.comments = CommentsModel().get_comments(
213 c.comments = CommentsModel().get_comments(
214 c.rhodecode_db_repo.repo_id,
214 c.rhodecode_db_repo.repo_id,
215 revision=commit.raw_id)
215 revision=commit.raw_id)
216 c.statuses.append(ChangesetStatusModel().get_status(
216 c.statuses.append(ChangesetStatusModel().get_status(
217 c.rhodecode_db_repo.repo_id, commit.raw_id))
217 c.rhodecode_db_repo.repo_id, commit.raw_id))
218 # comments from PR
218 # comments from PR
219 statuses = ChangesetStatusModel().get_statuses(
219 statuses = ChangesetStatusModel().get_statuses(
220 c.rhodecode_db_repo.repo_id, commit.raw_id,
220 c.rhodecode_db_repo.repo_id, commit.raw_id,
221 with_revisions=True)
221 with_revisions=True)
222 prs = set(st.pull_request for st in statuses
222 prs = set(st.pull_request for st in statuses
223 if st.pull_request is not None)
223 if st.pull_request is not None)
224 # from associated statuses, check the pull requests, and
224 # from associated statuses, check the pull requests, and
225 # show comments from them
225 # show comments from them
226 for pr in prs:
226 for pr in prs:
227 c.comments.extend(pr.comments)
227 c.comments.extend(pr.comments)
228
228
229 # Iterate over ranges (default commit view is always one commit)
229 # Iterate over ranges (default commit view is always one commit)
230 for commit in c.commit_ranges:
230 for commit in c.commit_ranges:
231 c.changes[commit.raw_id] = []
231 c.changes[commit.raw_id] = []
232
232
233 commit2 = commit
233 commit2 = commit
234 commit1 = commit.parents[0] if commit.parents else EmptyCommit()
234 commit1 = commit.parents[0] if commit.parents else EmptyCommit()
235
235
236 _diff = c.rhodecode_repo.get_diff(
236 _diff = c.rhodecode_repo.get_diff(
237 commit1, commit2,
237 commit1, commit2,
238 ignore_whitespace=ign_whitespace_lcl, context=context_lcl)
238 ignore_whitespace=ign_whitespace_lcl, context=context_lcl)
239 diff_processor = diffs.DiffProcessor(
239 diff_processor = diffs.DiffProcessor(
240 _diff, format='newdiff', diff_limit=diff_limit,
240 _diff, format='newdiff', diff_limit=diff_limit,
241 file_limit=file_limit, show_full_diff=fulldiff)
241 file_limit=file_limit, show_full_diff=fulldiff)
242
242
243 commit_changes = OrderedDict()
243 commit_changes = OrderedDict()
244 if method == 'show':
244 if method == 'show':
245 _parsed = diff_processor.prepare()
245 _parsed = diff_processor.prepare()
246 c.limited_diff = isinstance(_parsed, diffs.LimitedDiffContainer)
246 c.limited_diff = isinstance(_parsed, diffs.LimitedDiffContainer)
247
247
248 _parsed = diff_processor.prepare()
248 _parsed = diff_processor.prepare()
249
249
250 def _node_getter(commit):
250 def _node_getter(commit):
251 def get_node(fname):
251 def get_node(fname):
252 try:
252 try:
253 return commit.get_node(fname)
253 return commit.get_node(fname)
254 except NodeDoesNotExistError:
254 except NodeDoesNotExistError:
255 return None
255 return None
256 return get_node
256 return get_node
257
257
258 inline_comments = CommentsModel().get_inline_comments(
258 inline_comments = CommentsModel().get_inline_comments(
259 c.rhodecode_db_repo.repo_id, revision=commit.raw_id)
259 c.rhodecode_db_repo.repo_id, revision=commit.raw_id)
260 c.inline_cnt = CommentsModel().get_inline_comments_count(
260 c.inline_cnt = CommentsModel().get_inline_comments_count(
261 inline_comments)
261 inline_comments)
262
262
263 diffset = codeblocks.DiffSet(
263 diffset = codeblocks.DiffSet(
264 repo_name=c.repo_name,
264 repo_name=c.repo_name,
265 source_node_getter=_node_getter(commit1),
265 source_node_getter=_node_getter(commit1),
266 target_node_getter=_node_getter(commit2),
266 target_node_getter=_node_getter(commit2),
267 comments=inline_comments
267 comments=inline_comments
268 ).render_patchset(_parsed, commit1.raw_id, commit2.raw_id)
268 ).render_patchset(_parsed, commit1.raw_id, commit2.raw_id)
269 c.changes[commit.raw_id] = diffset
269 c.changes[commit.raw_id] = diffset
270 else:
270 else:
271 # downloads/raw we only need RAW diff nothing else
271 # downloads/raw we only need RAW diff nothing else
272 diff = diff_processor.as_raw()
272 diff = diff_processor.as_raw()
273 c.changes[commit.raw_id] = [None, None, None, None, diff, None, None]
273 c.changes[commit.raw_id] = [None, None, None, None, diff, None, None]
274
274
275 # sort comments by how they were generated
275 # sort comments by how they were generated
276 c.comments = sorted(c.comments, key=lambda x: x.comment_id)
276 c.comments = sorted(c.comments, key=lambda x: x.comment_id)
277
277
278
278
279 if len(c.commit_ranges) == 1:
279 if len(c.commit_ranges) == 1:
280 c.commit = c.commit_ranges[0]
280 c.commit = c.commit_ranges[0]
281 c.parent_tmpl = ''.join(
281 c.parent_tmpl = ''.join(
282 '# Parent %s\n' % x.raw_id for x in c.commit.parents)
282 '# Parent %s\n' % x.raw_id for x in c.commit.parents)
283 if method == 'download':
283 if method == 'download':
284 response.content_type = 'text/plain'
284 response.content_type = 'text/plain'
285 response.content_disposition = (
285 response.content_disposition = (
286 'attachment; filename=%s.diff' % commit_id_range[:12])
286 'attachment; filename=%s.diff' % commit_id_range[:12])
287 return diff
287 return diff
288 elif method == 'patch':
288 elif method == 'patch':
289 response.content_type = 'text/plain'
289 response.content_type = 'text/plain'
290 c.diff = safe_unicode(diff)
290 c.diff = safe_unicode(diff)
291 return render('changeset/patch_changeset.mako')
291 return render('changeset/patch_changeset.mako')
292 elif method == 'raw':
292 elif method == 'raw':
293 response.content_type = 'text/plain'
293 response.content_type = 'text/plain'
294 return diff
294 return diff
295 elif method == 'show':
295 elif method == 'show':
296 if len(c.commit_ranges) == 1:
296 if len(c.commit_ranges) == 1:
297 return render('changeset/changeset.mako')
297 return render('changeset/changeset.mako')
298 else:
298 else:
299 c.ancestor = None
299 c.ancestor = None
300 c.target_repo = c.rhodecode_db_repo
300 c.target_repo = c.rhodecode_db_repo
301 return render('changeset/changeset_range.mako')
301 return render('changeset/changeset_range.mako')
302
302
303 @LoginRequired()
303 @LoginRequired()
304 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
304 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
305 'repository.admin')
305 'repository.admin')
306 def index(self, revision, method='show'):
306 def index(self, revision, method='show'):
307 return self._index(revision, method=method)
307 return self._index(revision, method=method)
308
308
309 @LoginRequired()
309 @LoginRequired()
310 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
310 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
311 'repository.admin')
311 'repository.admin')
312 def changeset_raw(self, revision):
312 def changeset_raw(self, revision):
313 return self._index(revision, method='raw')
313 return self._index(revision, method='raw')
314
314
315 @LoginRequired()
315 @LoginRequired()
316 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
316 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
317 'repository.admin')
317 'repository.admin')
318 def changeset_patch(self, revision):
318 def changeset_patch(self, revision):
319 return self._index(revision, method='patch')
319 return self._index(revision, method='patch')
320
320
321 @LoginRequired()
321 @LoginRequired()
322 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
322 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
323 'repository.admin')
323 'repository.admin')
324 def changeset_download(self, revision):
324 def changeset_download(self, revision):
325 return self._index(revision, method='download')
325 return self._index(revision, method='download')
326
326
327 @LoginRequired()
327 @LoginRequired()
328 @NotAnonymous()
328 @NotAnonymous()
329 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
329 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
330 'repository.admin')
330 'repository.admin')
331 @auth.CSRFRequired()
331 @auth.CSRFRequired()
332 @jsonify
332 @jsonify
333 def comment(self, repo_name, revision):
333 def comment(self, repo_name, revision):
334 commit_id = revision
334 commit_id = revision
335 status = request.POST.get('changeset_status', None)
335 status = request.POST.get('changeset_status', None)
336 text = request.POST.get('text')
336 text = request.POST.get('text')
337 comment_type = request.POST.get('comment_type')
337 comment_type = request.POST.get('comment_type')
338 resolves_comment_id = request.POST.get('resolves_comment_id', None)
338 resolves_comment_id = request.POST.get('resolves_comment_id', None)
339
339
340 if status:
340 if status:
341 text = text or (_('Status change %(transition_icon)s %(status)s')
341 text = text or (_('Status change %(transition_icon)s %(status)s')
342 % {'transition_icon': '>',
342 % {'transition_icon': '>',
343 'status': ChangesetStatus.get_status_lbl(status)})
343 'status': ChangesetStatus.get_status_lbl(status)})
344
344
345 multi_commit_ids = filter(
345 multi_commit_ids = filter(
346 lambda s: s not in ['', None],
346 lambda s: s not in ['', None],
347 request.POST.get('commit_ids', '').split(','),)
347 request.POST.get('commit_ids', '').split(','),)
348
348
349 commit_ids = multi_commit_ids or [commit_id]
349 commit_ids = multi_commit_ids or [commit_id]
350 comment = None
350 comment = None
351 for current_id in filter(None, commit_ids):
351 for current_id in filter(None, commit_ids):
352 c.co = comment = CommentsModel().create(
352 c.co = comment = CommentsModel().create(
353 text=text,
353 text=text,
354 repo=c.rhodecode_db_repo.repo_id,
354 repo=c.rhodecode_db_repo.repo_id,
355 user=c.rhodecode_user.user_id,
355 user=c.rhodecode_user.user_id,
356 commit_id=current_id,
356 commit_id=current_id,
357 f_path=request.POST.get('f_path'),
357 f_path=request.POST.get('f_path'),
358 line_no=request.POST.get('line'),
358 line_no=request.POST.get('line'),
359 status_change=(ChangesetStatus.get_status_lbl(status)
359 status_change=(ChangesetStatus.get_status_lbl(status)
360 if status else None),
360 if status else None),
361 status_change_type=status,
361 status_change_type=status,
362 comment_type=comment_type,
362 comment_type=comment_type,
363 resolves_comment_id=resolves_comment_id
363 resolves_comment_id=resolves_comment_id
364 )
364 )
365 c.inline_comment = True if comment.line_no else False
365 c.inline_comment = True if comment.line_no else False
366
366
367 # get status if set !
367 # get status if set !
368 if status:
368 if status:
369 # if latest status was from pull request and it's closed
369 # if latest status was from pull request and it's closed
370 # disallow changing status !
370 # disallow changing status !
371 # dont_allow_on_closed_pull_request = True !
371 # dont_allow_on_closed_pull_request = True !
372
372
373 try:
373 try:
374 ChangesetStatusModel().set_status(
374 ChangesetStatusModel().set_status(
375 c.rhodecode_db_repo.repo_id,
375 c.rhodecode_db_repo.repo_id,
376 status,
376 status,
377 c.rhodecode_user.user_id,
377 c.rhodecode_user.user_id,
378 comment,
378 comment,
379 revision=current_id,
379 revision=current_id,
380 dont_allow_on_closed_pull_request=True
380 dont_allow_on_closed_pull_request=True
381 )
381 )
382 except StatusChangeOnClosedPullRequestError:
382 except StatusChangeOnClosedPullRequestError:
383 msg = _('Changing the status of a commit associated with '
383 msg = _('Changing the status of a commit associated with '
384 'a closed pull request is not allowed')
384 'a closed pull request is not allowed')
385 log.exception(msg)
385 log.exception(msg)
386 h.flash(msg, category='warning')
386 h.flash(msg, category='warning')
387 return redirect(h.url(
387 return redirect(h.url(
388 'changeset_home', repo_name=repo_name,
388 'changeset_home', repo_name=repo_name,
389 revision=current_id))
389 revision=current_id))
390
390
391 # finalize, commit and redirect
391 # finalize, commit and redirect
392 Session().commit()
392 Session().commit()
393
393
394 data = {
394 data = {
395 'target_id': h.safeid(h.safe_unicode(request.POST.get('f_path'))),
395 'target_id': h.safeid(h.safe_unicode(request.POST.get('f_path'))),
396 }
396 }
397 if comment:
397 if comment:
398 data.update(comment.get_dict())
398 data.update(comment.get_dict())
399 data.update({'rendered_text':
399 data.update({'rendered_text':
400 render('changeset/changeset_comment_block.mako')})
400 render('changeset/changeset_comment_block.mako')})
401
401
402 return data
402 return data
403
403
404 @LoginRequired()
404 @LoginRequired()
405 @NotAnonymous()
405 @NotAnonymous()
406 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
406 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
407 'repository.admin')
407 'repository.admin')
408 @auth.CSRFRequired()
408 @auth.CSRFRequired()
409 def preview_comment(self):
409 def preview_comment(self):
410 # Technically a CSRF token is not needed as no state changes with this
410 # Technically a CSRF token is not needed as no state changes with this
411 # call. However, as this is a POST is better to have it, so automated
411 # call. However, as this is a POST is better to have it, so automated
412 # tools don't flag it as potential CSRF.
412 # tools don't flag it as potential CSRF.
413 # Post is required because the payload could be bigger than the maximum
413 # Post is required because the payload could be bigger than the maximum
414 # allowed by GET.
414 # allowed by GET.
415 if not request.environ.get('HTTP_X_PARTIAL_XHR'):
415 if not request.environ.get('HTTP_X_PARTIAL_XHR'):
416 raise HTTPBadRequest()
416 raise HTTPBadRequest()
417 text = request.POST.get('text')
417 text = request.POST.get('text')
418 renderer = request.POST.get('renderer') or 'rst'
418 renderer = request.POST.get('renderer') or 'rst'
419 if text:
419 if text:
420 return h.render(text, renderer=renderer, mentions=True)
420 return h.render(text, renderer=renderer, mentions=True)
421 return ''
421 return ''
422
422
423 @LoginRequired()
423 @LoginRequired()
424 @NotAnonymous()
424 @NotAnonymous()
425 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
425 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
426 'repository.admin')
426 'repository.admin')
427 @auth.CSRFRequired()
427 @auth.CSRFRequired()
428 @jsonify
428 @jsonify
429 def delete_comment(self, repo_name, comment_id):
429 def delete_comment(self, repo_name, comment_id):
430 comment = ChangesetComment.get(comment_id)
430 comment = ChangesetComment.get(comment_id)
431 if not comment:
432 log.debug('Comment with id:%s not found, skipping', comment_id)
433 # comment already deleted in another call probably
434 return True
435
431 owner = (comment.author.user_id == c.rhodecode_user.user_id)
436 owner = (comment.author.user_id == c.rhodecode_user.user_id)
432 is_repo_admin = h.HasRepoPermissionAny('repository.admin')(c.repo_name)
437 is_repo_admin = h.HasRepoPermissionAny('repository.admin')(c.repo_name)
433 if h.HasPermissionAny('hg.admin')() or is_repo_admin or owner:
438 if h.HasPermissionAny('hg.admin')() or is_repo_admin or owner:
434 CommentsModel().delete(comment=comment)
439 CommentsModel().delete(comment=comment)
435 Session().commit()
440 Session().commit()
436 return True
441 return True
437 else:
442 else:
438 raise HTTPForbidden()
443 raise HTTPForbidden()
439
444
440 @LoginRequired()
445 @LoginRequired()
441 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
446 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
442 'repository.admin')
447 'repository.admin')
443 @jsonify
448 @jsonify
444 def changeset_info(self, repo_name, revision):
449 def changeset_info(self, repo_name, revision):
445 if request.is_xhr:
450 if request.is_xhr:
446 try:
451 try:
447 return c.rhodecode_repo.get_commit(commit_id=revision)
452 return c.rhodecode_repo.get_commit(commit_id=revision)
448 except CommitDoesNotExistError as e:
453 except CommitDoesNotExistError as e:
449 return EmptyCommit(message=str(e))
454 return EmptyCommit(message=str(e))
450 else:
455 else:
451 raise HTTPBadRequest()
456 raise HTTPBadRequest()
452
457
453 @LoginRequired()
458 @LoginRequired()
454 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
459 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
455 'repository.admin')
460 'repository.admin')
456 @jsonify
461 @jsonify
457 def changeset_children(self, repo_name, revision):
462 def changeset_children(self, repo_name, revision):
458 if request.is_xhr:
463 if request.is_xhr:
459 commit = c.rhodecode_repo.get_commit(commit_id=revision)
464 commit = c.rhodecode_repo.get_commit(commit_id=revision)
460 result = {"results": commit.children}
465 result = {"results": commit.children}
461 return result
466 return result
462 else:
467 else:
463 raise HTTPBadRequest()
468 raise HTTPBadRequest()
464
469
465 @LoginRequired()
470 @LoginRequired()
466 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
471 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
467 'repository.admin')
472 'repository.admin')
468 @jsonify
473 @jsonify
469 def changeset_parents(self, repo_name, revision):
474 def changeset_parents(self, repo_name, revision):
470 if request.is_xhr:
475 if request.is_xhr:
471 commit = c.rhodecode_repo.get_commit(commit_id=revision)
476 commit = c.rhodecode_repo.get_commit(commit_id=revision)
472 result = {"results": commit.parents}
477 result = {"results": commit.parents}
473 return result
478 return result
474 else:
479 else:
475 raise HTTPBadRequest()
480 raise HTTPBadRequest()
General Comments 0
You need to be logged in to leave comments. Login now