##// END OF EJS Templates
drafts: fixes in draft comments for non-pr view
milka -
r4550:647f7e13 default
parent child Browse files
Show More
@@ -1,795 +1,802 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2020 RhodeCode GmbH
3 # Copyright (C) 2010-2020 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 import logging
21 import logging
22 import collections
22 import collections
23
23
24 from pyramid.httpexceptions import (
24 from pyramid.httpexceptions import (
25 HTTPNotFound, HTTPBadRequest, HTTPFound, HTTPForbidden, HTTPConflict)
25 HTTPNotFound, HTTPBadRequest, HTTPFound, HTTPForbidden, HTTPConflict)
26 from pyramid.view import view_config
26 from pyramid.view import view_config
27 from pyramid.renderers import render
27 from pyramid.renderers import render
28 from pyramid.response import Response
28 from pyramid.response import Response
29
29
30 from rhodecode.apps._base import RepoAppView
30 from rhodecode.apps._base import RepoAppView
31 from rhodecode.apps.file_store import utils as store_utils
31 from rhodecode.apps.file_store import utils as store_utils
32 from rhodecode.apps.file_store.exceptions import FileNotAllowedException, FileOverSizeException
32 from rhodecode.apps.file_store.exceptions import FileNotAllowedException, FileOverSizeException
33
33
34 from rhodecode.lib import diffs, codeblocks, channelstream
34 from rhodecode.lib import diffs, codeblocks, channelstream
35 from rhodecode.lib.auth import (
35 from rhodecode.lib.auth import (
36 LoginRequired, HasRepoPermissionAnyDecorator, NotAnonymous, CSRFRequired)
36 LoginRequired, HasRepoPermissionAnyDecorator, NotAnonymous, CSRFRequired)
37 from rhodecode.lib.ext_json import json
37 from rhodecode.lib.ext_json import json
38 from rhodecode.lib.compat import OrderedDict
38 from rhodecode.lib.compat import OrderedDict
39 from rhodecode.lib.diffs import (
39 from rhodecode.lib.diffs import (
40 cache_diff, load_cached_diff, diff_cache_exist, get_diff_context,
40 cache_diff, load_cached_diff, diff_cache_exist, get_diff_context,
41 get_diff_whitespace_flag)
41 get_diff_whitespace_flag)
42 from rhodecode.lib.exceptions import StatusChangeOnClosedPullRequestError, CommentVersionMismatch
42 from rhodecode.lib.exceptions import StatusChangeOnClosedPullRequestError, CommentVersionMismatch
43 import rhodecode.lib.helpers as h
43 import rhodecode.lib.helpers as h
44 from rhodecode.lib.utils2 import safe_unicode, str2bool, StrictAttributeDict
44 from rhodecode.lib.utils2 import safe_unicode, str2bool, StrictAttributeDict
45 from rhodecode.lib.vcs.backends.base import EmptyCommit
45 from rhodecode.lib.vcs.backends.base import EmptyCommit
46 from rhodecode.lib.vcs.exceptions import (
46 from rhodecode.lib.vcs.exceptions import (
47 RepositoryError, CommitDoesNotExistError)
47 RepositoryError, CommitDoesNotExistError)
48 from rhodecode.model.db import ChangesetComment, ChangesetStatus, FileStore, \
48 from rhodecode.model.db import ChangesetComment, ChangesetStatus, FileStore, \
49 ChangesetCommentHistory
49 ChangesetCommentHistory
50 from rhodecode.model.changeset_status import ChangesetStatusModel
50 from rhodecode.model.changeset_status import ChangesetStatusModel
51 from rhodecode.model.comment import CommentsModel
51 from rhodecode.model.comment import CommentsModel
52 from rhodecode.model.meta import Session
52 from rhodecode.model.meta import Session
53 from rhodecode.model.settings import VcsSettingsModel
53 from rhodecode.model.settings import VcsSettingsModel
54
54
55 log = logging.getLogger(__name__)
55 log = logging.getLogger(__name__)
56
56
57
57
58 def _update_with_GET(params, request):
58 def _update_with_GET(params, request):
59 for k in ['diff1', 'diff2', 'diff']:
59 for k in ['diff1', 'diff2', 'diff']:
60 params[k] += request.GET.getall(k)
60 params[k] += request.GET.getall(k)
61
61
62
62
63 class RepoCommitsView(RepoAppView):
63 class RepoCommitsView(RepoAppView):
64 def load_default_context(self):
64 def load_default_context(self):
65 c = self._get_local_tmpl_context(include_app_defaults=True)
65 c = self._get_local_tmpl_context(include_app_defaults=True)
66 c.rhodecode_repo = self.rhodecode_vcs_repo
66 c.rhodecode_repo = self.rhodecode_vcs_repo
67
67
68 return c
68 return c
69
69
70 def _is_diff_cache_enabled(self, target_repo):
70 def _is_diff_cache_enabled(self, target_repo):
71 caching_enabled = self._get_general_setting(
71 caching_enabled = self._get_general_setting(
72 target_repo, 'rhodecode_diff_cache')
72 target_repo, 'rhodecode_diff_cache')
73 log.debug('Diff caching enabled: %s', caching_enabled)
73 log.debug('Diff caching enabled: %s', caching_enabled)
74 return caching_enabled
74 return caching_enabled
75
75
76 def _commit(self, commit_id_range, method):
76 def _commit(self, commit_id_range, method):
77 _ = self.request.translate
77 _ = self.request.translate
78 c = self.load_default_context()
78 c = self.load_default_context()
79 c.fulldiff = self.request.GET.get('fulldiff')
79 c.fulldiff = self.request.GET.get('fulldiff')
80
80
81 # fetch global flags of ignore ws or context lines
81 # fetch global flags of ignore ws or context lines
82 diff_context = get_diff_context(self.request)
82 diff_context = get_diff_context(self.request)
83 hide_whitespace_changes = get_diff_whitespace_flag(self.request)
83 hide_whitespace_changes = get_diff_whitespace_flag(self.request)
84
84
85 # diff_limit will cut off the whole diff if the limit is applied
85 # diff_limit will cut off the whole diff if the limit is applied
86 # otherwise it will just hide the big files from the front-end
86 # otherwise it will just hide the big files from the front-end
87 diff_limit = c.visual.cut_off_limit_diff
87 diff_limit = c.visual.cut_off_limit_diff
88 file_limit = c.visual.cut_off_limit_file
88 file_limit = c.visual.cut_off_limit_file
89
89
90 # get ranges of commit ids if preset
90 # get ranges of commit ids if preset
91 commit_range = commit_id_range.split('...')[:2]
91 commit_range = commit_id_range.split('...')[:2]
92
92
93 try:
93 try:
94 pre_load = ['affected_files', 'author', 'branch', 'date',
94 pre_load = ['affected_files', 'author', 'branch', 'date',
95 'message', 'parents']
95 'message', 'parents']
96 if self.rhodecode_vcs_repo.alias == 'hg':
96 if self.rhodecode_vcs_repo.alias == 'hg':
97 pre_load += ['hidden', 'obsolete', 'phase']
97 pre_load += ['hidden', 'obsolete', 'phase']
98
98
99 if len(commit_range) == 2:
99 if len(commit_range) == 2:
100 commits = self.rhodecode_vcs_repo.get_commits(
100 commits = self.rhodecode_vcs_repo.get_commits(
101 start_id=commit_range[0], end_id=commit_range[1],
101 start_id=commit_range[0], end_id=commit_range[1],
102 pre_load=pre_load, translate_tags=False)
102 pre_load=pre_load, translate_tags=False)
103 commits = list(commits)
103 commits = list(commits)
104 else:
104 else:
105 commits = [self.rhodecode_vcs_repo.get_commit(
105 commits = [self.rhodecode_vcs_repo.get_commit(
106 commit_id=commit_id_range, pre_load=pre_load)]
106 commit_id=commit_id_range, pre_load=pre_load)]
107
107
108 c.commit_ranges = commits
108 c.commit_ranges = commits
109 if not c.commit_ranges:
109 if not c.commit_ranges:
110 raise RepositoryError('The commit range returned an empty result')
110 raise RepositoryError('The commit range returned an empty result')
111 except CommitDoesNotExistError as e:
111 except CommitDoesNotExistError as e:
112 msg = _('No such commit exists. Org exception: `{}`').format(e)
112 msg = _('No such commit exists. Org exception: `{}`').format(e)
113 h.flash(msg, category='error')
113 h.flash(msg, category='error')
114 raise HTTPNotFound()
114 raise HTTPNotFound()
115 except Exception:
115 except Exception:
116 log.exception("General failure")
116 log.exception("General failure")
117 raise HTTPNotFound()
117 raise HTTPNotFound()
118 single_commit = len(c.commit_ranges) == 1
118 single_commit = len(c.commit_ranges) == 1
119
119
120 c.changes = OrderedDict()
120 c.changes = OrderedDict()
121 c.lines_added = 0
121 c.lines_added = 0
122 c.lines_deleted = 0
122 c.lines_deleted = 0
123
123
124 # auto collapse if we have more than limit
124 # auto collapse if we have more than limit
125 collapse_limit = diffs.DiffProcessor._collapse_commits_over
125 collapse_limit = diffs.DiffProcessor._collapse_commits_over
126 c.collapse_all_commits = len(c.commit_ranges) > collapse_limit
126 c.collapse_all_commits = len(c.commit_ranges) > collapse_limit
127
127
128 c.commit_statuses = ChangesetStatus.STATUSES
128 c.commit_statuses = ChangesetStatus.STATUSES
129 c.inline_comments = []
129 c.inline_comments = []
130 c.files = []
130 c.files = []
131
131
132 c.comments = []
132 c.comments = []
133 c.unresolved_comments = []
133 c.unresolved_comments = []
134 c.resolved_comments = []
134 c.resolved_comments = []
135
135
136 # Single commit
136 # Single commit
137 if single_commit:
137 if single_commit:
138 commit = c.commit_ranges[0]
138 commit = c.commit_ranges[0]
139 c.comments = CommentsModel().get_comments(
139 c.comments = CommentsModel().get_comments(
140 self.db_repo.repo_id,
140 self.db_repo.repo_id,
141 revision=commit.raw_id)
141 revision=commit.raw_id)
142
142
143 # comments from PR
143 # comments from PR
144 statuses = ChangesetStatusModel().get_statuses(
144 statuses = ChangesetStatusModel().get_statuses(
145 self.db_repo.repo_id, commit.raw_id,
145 self.db_repo.repo_id, commit.raw_id,
146 with_revisions=True)
146 with_revisions=True)
147
147
148 prs = set()
148 prs = set()
149 reviewers = list()
149 reviewers = list()
150 reviewers_duplicates = set() # to not have duplicates from multiple votes
150 reviewers_duplicates = set() # to not have duplicates from multiple votes
151 for c_status in statuses:
151 for c_status in statuses:
152
152
153 # extract associated pull-requests from votes
153 # extract associated pull-requests from votes
154 if c_status.pull_request:
154 if c_status.pull_request:
155 prs.add(c_status.pull_request)
155 prs.add(c_status.pull_request)
156
156
157 # extract reviewers
157 # extract reviewers
158 _user_id = c_status.author.user_id
158 _user_id = c_status.author.user_id
159 if _user_id not in reviewers_duplicates:
159 if _user_id not in reviewers_duplicates:
160 reviewers.append(
160 reviewers.append(
161 StrictAttributeDict({
161 StrictAttributeDict({
162 'user': c_status.author,
162 'user': c_status.author,
163
163
164 # fake attributed for commit, page that we don't have
164 # fake attributed for commit, page that we don't have
165 # but we share the display with PR page
165 # but we share the display with PR page
166 'mandatory': False,
166 'mandatory': False,
167 'reasons': [],
167 'reasons': [],
168 'rule_user_group_data': lambda: None
168 'rule_user_group_data': lambda: None
169 })
169 })
170 )
170 )
171 reviewers_duplicates.add(_user_id)
171 reviewers_duplicates.add(_user_id)
172
172
173 c.reviewers_count = len(reviewers)
173 c.reviewers_count = len(reviewers)
174 c.observers_count = 0
174 c.observers_count = 0
175
175
176 # from associated statuses, check the pull requests, and
176 # from associated statuses, check the pull requests, and
177 # show comments from them
177 # show comments from them
178 for pr in prs:
178 for pr in prs:
179 c.comments.extend(pr.comments)
179 c.comments.extend(pr.comments)
180
180
181 c.unresolved_comments = CommentsModel()\
181 c.unresolved_comments = CommentsModel()\
182 .get_commit_unresolved_todos(commit.raw_id)
182 .get_commit_unresolved_todos(commit.raw_id)
183 c.resolved_comments = CommentsModel()\
183 c.resolved_comments = CommentsModel()\
184 .get_commit_resolved_todos(commit.raw_id)
184 .get_commit_resolved_todos(commit.raw_id)
185
185
186 c.inline_comments_flat = CommentsModel()\
186 c.inline_comments_flat = CommentsModel()\
187 .get_commit_inline_comments(commit.raw_id)
187 .get_commit_inline_comments(commit.raw_id)
188
188
189 review_statuses = ChangesetStatusModel().aggregate_votes_by_user(
189 review_statuses = ChangesetStatusModel().aggregate_votes_by_user(
190 statuses, reviewers)
190 statuses, reviewers)
191
191
192 c.commit_review_status = ChangesetStatus.STATUS_NOT_REVIEWED
192 c.commit_review_status = ChangesetStatus.STATUS_NOT_REVIEWED
193
193
194 c.commit_set_reviewers_data_json = collections.OrderedDict({'reviewers': []})
194 c.commit_set_reviewers_data_json = collections.OrderedDict({'reviewers': []})
195
195
196 for review_obj, member, reasons, mandatory, status in review_statuses:
196 for review_obj, member, reasons, mandatory, status in review_statuses:
197 member_reviewer = h.reviewer_as_json(
197 member_reviewer = h.reviewer_as_json(
198 member, reasons=reasons, mandatory=mandatory, role=None,
198 member, reasons=reasons, mandatory=mandatory, role=None,
199 user_group=None
199 user_group=None
200 )
200 )
201
201
202 current_review_status = status[0][1].status if status else ChangesetStatus.STATUS_NOT_REVIEWED
202 current_review_status = status[0][1].status if status else ChangesetStatus.STATUS_NOT_REVIEWED
203 member_reviewer['review_status'] = current_review_status
203 member_reviewer['review_status'] = current_review_status
204 member_reviewer['review_status_label'] = h.commit_status_lbl(current_review_status)
204 member_reviewer['review_status_label'] = h.commit_status_lbl(current_review_status)
205 member_reviewer['allowed_to_update'] = False
205 member_reviewer['allowed_to_update'] = False
206 c.commit_set_reviewers_data_json['reviewers'].append(member_reviewer)
206 c.commit_set_reviewers_data_json['reviewers'].append(member_reviewer)
207
207
208 c.commit_set_reviewers_data_json = json.dumps(c.commit_set_reviewers_data_json)
208 c.commit_set_reviewers_data_json = json.dumps(c.commit_set_reviewers_data_json)
209
209
210 # NOTE(marcink): this uses the same voting logic as in pull-requests
210 # NOTE(marcink): this uses the same voting logic as in pull-requests
211 c.commit_review_status = ChangesetStatusModel().calculate_status(review_statuses)
211 c.commit_review_status = ChangesetStatusModel().calculate_status(review_statuses)
212 c.commit_broadcast_channel = channelstream.comment_channel(c.repo_name, commit_obj=commit)
212 c.commit_broadcast_channel = channelstream.comment_channel(c.repo_name, commit_obj=commit)
213
213
214 diff = None
214 diff = None
215 # Iterate over ranges (default commit view is always one commit)
215 # Iterate over ranges (default commit view is always one commit)
216 for commit in c.commit_ranges:
216 for commit in c.commit_ranges:
217 c.changes[commit.raw_id] = []
217 c.changes[commit.raw_id] = []
218
218
219 commit2 = commit
219 commit2 = commit
220 commit1 = commit.first_parent
220 commit1 = commit.first_parent
221
221
222 if method == 'show':
222 if method == 'show':
223 inline_comments = CommentsModel().get_inline_comments(
223 inline_comments = CommentsModel().get_inline_comments(
224 self.db_repo.repo_id, revision=commit.raw_id)
224 self.db_repo.repo_id, revision=commit.raw_id)
225 c.inline_cnt = len(CommentsModel().get_inline_comments_as_list(
225 c.inline_cnt = len(CommentsModel().get_inline_comments_as_list(
226 inline_comments))
226 inline_comments))
227 c.inline_comments = inline_comments
227 c.inline_comments = inline_comments
228
228
229 cache_path = self.rhodecode_vcs_repo.get_create_shadow_cache_pr_path(
229 cache_path = self.rhodecode_vcs_repo.get_create_shadow_cache_pr_path(
230 self.db_repo)
230 self.db_repo)
231 cache_file_path = diff_cache_exist(
231 cache_file_path = diff_cache_exist(
232 cache_path, 'diff', commit.raw_id,
232 cache_path, 'diff', commit.raw_id,
233 hide_whitespace_changes, diff_context, c.fulldiff)
233 hide_whitespace_changes, diff_context, c.fulldiff)
234
234
235 caching_enabled = self._is_diff_cache_enabled(self.db_repo)
235 caching_enabled = self._is_diff_cache_enabled(self.db_repo)
236 force_recache = str2bool(self.request.GET.get('force_recache'))
236 force_recache = str2bool(self.request.GET.get('force_recache'))
237
237
238 cached_diff = None
238 cached_diff = None
239 if caching_enabled:
239 if caching_enabled:
240 cached_diff = load_cached_diff(cache_file_path)
240 cached_diff = load_cached_diff(cache_file_path)
241
241
242 has_proper_diff_cache = cached_diff and cached_diff.get('diff')
242 has_proper_diff_cache = cached_diff and cached_diff.get('diff')
243 if not force_recache and has_proper_diff_cache:
243 if not force_recache and has_proper_diff_cache:
244 diffset = cached_diff['diff']
244 diffset = cached_diff['diff']
245 else:
245 else:
246 vcs_diff = self.rhodecode_vcs_repo.get_diff(
246 vcs_diff = self.rhodecode_vcs_repo.get_diff(
247 commit1, commit2,
247 commit1, commit2,
248 ignore_whitespace=hide_whitespace_changes,
248 ignore_whitespace=hide_whitespace_changes,
249 context=diff_context)
249 context=diff_context)
250
250
251 diff_processor = diffs.DiffProcessor(
251 diff_processor = diffs.DiffProcessor(
252 vcs_diff, format='newdiff', diff_limit=diff_limit,
252 vcs_diff, format='newdiff', diff_limit=diff_limit,
253 file_limit=file_limit, show_full_diff=c.fulldiff)
253 file_limit=file_limit, show_full_diff=c.fulldiff)
254
254
255 _parsed = diff_processor.prepare()
255 _parsed = diff_processor.prepare()
256
256
257 diffset = codeblocks.DiffSet(
257 diffset = codeblocks.DiffSet(
258 repo_name=self.db_repo_name,
258 repo_name=self.db_repo_name,
259 source_node_getter=codeblocks.diffset_node_getter(commit1),
259 source_node_getter=codeblocks.diffset_node_getter(commit1),
260 target_node_getter=codeblocks.diffset_node_getter(commit2))
260 target_node_getter=codeblocks.diffset_node_getter(commit2))
261
261
262 diffset = self.path_filter.render_patchset_filtered(
262 diffset = self.path_filter.render_patchset_filtered(
263 diffset, _parsed, commit1.raw_id, commit2.raw_id)
263 diffset, _parsed, commit1.raw_id, commit2.raw_id)
264
264
265 # save cached diff
265 # save cached diff
266 if caching_enabled:
266 if caching_enabled:
267 cache_diff(cache_file_path, diffset, None)
267 cache_diff(cache_file_path, diffset, None)
268
268
269 c.limited_diff = diffset.limited_diff
269 c.limited_diff = diffset.limited_diff
270 c.changes[commit.raw_id] = diffset
270 c.changes[commit.raw_id] = diffset
271 else:
271 else:
272 # TODO(marcink): no cache usage here...
272 # TODO(marcink): no cache usage here...
273 _diff = self.rhodecode_vcs_repo.get_diff(
273 _diff = self.rhodecode_vcs_repo.get_diff(
274 commit1, commit2,
274 commit1, commit2,
275 ignore_whitespace=hide_whitespace_changes, context=diff_context)
275 ignore_whitespace=hide_whitespace_changes, context=diff_context)
276 diff_processor = diffs.DiffProcessor(
276 diff_processor = diffs.DiffProcessor(
277 _diff, format='newdiff', diff_limit=diff_limit,
277 _diff, format='newdiff', diff_limit=diff_limit,
278 file_limit=file_limit, show_full_diff=c.fulldiff)
278 file_limit=file_limit, show_full_diff=c.fulldiff)
279 # downloads/raw we only need RAW diff nothing else
279 # downloads/raw we only need RAW diff nothing else
280 diff = self.path_filter.get_raw_patch(diff_processor)
280 diff = self.path_filter.get_raw_patch(diff_processor)
281 c.changes[commit.raw_id] = [None, None, None, None, diff, None, None]
281 c.changes[commit.raw_id] = [None, None, None, None, diff, None, None]
282
282
283 # sort comments by how they were generated
283 # sort comments by how they were generated
284 c.comments = sorted(c.comments, key=lambda x: x.comment_id)
284 c.comments = sorted(c.comments, key=lambda x: x.comment_id)
285 c.at_version_num = None
285 c.at_version_num = None
286
286
287 if len(c.commit_ranges) == 1:
287 if len(c.commit_ranges) == 1:
288 c.commit = c.commit_ranges[0]
288 c.commit = c.commit_ranges[0]
289 c.parent_tmpl = ''.join(
289 c.parent_tmpl = ''.join(
290 '# Parent %s\n' % x.raw_id for x in c.commit.parents)
290 '# Parent %s\n' % x.raw_id for x in c.commit.parents)
291
291
292 if method == 'download':
292 if method == 'download':
293 response = Response(diff)
293 response = Response(diff)
294 response.content_type = 'text/plain'
294 response.content_type = 'text/plain'
295 response.content_disposition = (
295 response.content_disposition = (
296 'attachment; filename=%s.diff' % commit_id_range[:12])
296 'attachment; filename=%s.diff' % commit_id_range[:12])
297 return response
297 return response
298 elif method == 'patch':
298 elif method == 'patch':
299 c.diff = safe_unicode(diff)
299 c.diff = safe_unicode(diff)
300 patch = render(
300 patch = render(
301 'rhodecode:templates/changeset/patch_changeset.mako',
301 'rhodecode:templates/changeset/patch_changeset.mako',
302 self._get_template_context(c), self.request)
302 self._get_template_context(c), self.request)
303 response = Response(patch)
303 response = Response(patch)
304 response.content_type = 'text/plain'
304 response.content_type = 'text/plain'
305 return response
305 return response
306 elif method == 'raw':
306 elif method == 'raw':
307 response = Response(diff)
307 response = Response(diff)
308 response.content_type = 'text/plain'
308 response.content_type = 'text/plain'
309 return response
309 return response
310 elif method == 'show':
310 elif method == 'show':
311 if len(c.commit_ranges) == 1:
311 if len(c.commit_ranges) == 1:
312 html = render(
312 html = render(
313 'rhodecode:templates/changeset/changeset.mako',
313 'rhodecode:templates/changeset/changeset.mako',
314 self._get_template_context(c), self.request)
314 self._get_template_context(c), self.request)
315 return Response(html)
315 return Response(html)
316 else:
316 else:
317 c.ancestor = None
317 c.ancestor = None
318 c.target_repo = self.db_repo
318 c.target_repo = self.db_repo
319 html = render(
319 html = render(
320 'rhodecode:templates/changeset/changeset_range.mako',
320 'rhodecode:templates/changeset/changeset_range.mako',
321 self._get_template_context(c), self.request)
321 self._get_template_context(c), self.request)
322 return Response(html)
322 return Response(html)
323
323
324 raise HTTPBadRequest()
324 raise HTTPBadRequest()
325
325
326 @LoginRequired()
326 @LoginRequired()
327 @HasRepoPermissionAnyDecorator(
327 @HasRepoPermissionAnyDecorator(
328 'repository.read', 'repository.write', 'repository.admin')
328 'repository.read', 'repository.write', 'repository.admin')
329 @view_config(
329 @view_config(
330 route_name='repo_commit', request_method='GET',
330 route_name='repo_commit', request_method='GET',
331 renderer=None)
331 renderer=None)
332 def repo_commit_show(self):
332 def repo_commit_show(self):
333 commit_id = self.request.matchdict['commit_id']
333 commit_id = self.request.matchdict['commit_id']
334 return self._commit(commit_id, method='show')
334 return self._commit(commit_id, method='show')
335
335
336 @LoginRequired()
336 @LoginRequired()
337 @HasRepoPermissionAnyDecorator(
337 @HasRepoPermissionAnyDecorator(
338 'repository.read', 'repository.write', 'repository.admin')
338 'repository.read', 'repository.write', 'repository.admin')
339 @view_config(
339 @view_config(
340 route_name='repo_commit_raw', request_method='GET',
340 route_name='repo_commit_raw', request_method='GET',
341 renderer=None)
341 renderer=None)
342 @view_config(
342 @view_config(
343 route_name='repo_commit_raw_deprecated', request_method='GET',
343 route_name='repo_commit_raw_deprecated', request_method='GET',
344 renderer=None)
344 renderer=None)
345 def repo_commit_raw(self):
345 def repo_commit_raw(self):
346 commit_id = self.request.matchdict['commit_id']
346 commit_id = self.request.matchdict['commit_id']
347 return self._commit(commit_id, method='raw')
347 return self._commit(commit_id, method='raw')
348
348
349 @LoginRequired()
349 @LoginRequired()
350 @HasRepoPermissionAnyDecorator(
350 @HasRepoPermissionAnyDecorator(
351 'repository.read', 'repository.write', 'repository.admin')
351 'repository.read', 'repository.write', 'repository.admin')
352 @view_config(
352 @view_config(
353 route_name='repo_commit_patch', request_method='GET',
353 route_name='repo_commit_patch', request_method='GET',
354 renderer=None)
354 renderer=None)
355 def repo_commit_patch(self):
355 def repo_commit_patch(self):
356 commit_id = self.request.matchdict['commit_id']
356 commit_id = self.request.matchdict['commit_id']
357 return self._commit(commit_id, method='patch')
357 return self._commit(commit_id, method='patch')
358
358
359 @LoginRequired()
359 @LoginRequired()
360 @HasRepoPermissionAnyDecorator(
360 @HasRepoPermissionAnyDecorator(
361 'repository.read', 'repository.write', 'repository.admin')
361 'repository.read', 'repository.write', 'repository.admin')
362 @view_config(
362 @view_config(
363 route_name='repo_commit_download', request_method='GET',
363 route_name='repo_commit_download', request_method='GET',
364 renderer=None)
364 renderer=None)
365 def repo_commit_download(self):
365 def repo_commit_download(self):
366 commit_id = self.request.matchdict['commit_id']
366 commit_id = self.request.matchdict['commit_id']
367 return self._commit(commit_id, method='download')
367 return self._commit(commit_id, method='download')
368
368
369 @LoginRequired()
369 @LoginRequired()
370 @NotAnonymous()
370 @NotAnonymous()
371 @HasRepoPermissionAnyDecorator(
371 @HasRepoPermissionAnyDecorator(
372 'repository.read', 'repository.write', 'repository.admin')
372 'repository.read', 'repository.write', 'repository.admin')
373 @CSRFRequired()
373 @CSRFRequired()
374 @view_config(
374 @view_config(
375 route_name='repo_commit_comment_create', request_method='POST',
375 route_name='repo_commit_comment_create', request_method='POST',
376 renderer='json_ext')
376 renderer='json_ext')
377 def repo_commit_comment_create(self):
377 def repo_commit_comment_create(self):
378 _ = self.request.translate
378 _ = self.request.translate
379 commit_id = self.request.matchdict['commit_id']
379 commit_id = self.request.matchdict['commit_id']
380
380
381 c = self.load_default_context()
381 c = self.load_default_context()
382 status = self.request.POST.get('changeset_status', None)
382 status = self.request.POST.get('changeset_status', None)
383 is_draft = str2bool(self.request.POST.get('draft'))
383 text = self.request.POST.get('text')
384 text = self.request.POST.get('text')
384 comment_type = self.request.POST.get('comment_type')
385 comment_type = self.request.POST.get('comment_type')
385 resolves_comment_id = self.request.POST.get('resolves_comment_id', None)
386 resolves_comment_id = self.request.POST.get('resolves_comment_id', None)
386 f_path = self.request.POST.get('f_path')
387 f_path = self.request.POST.get('f_path')
387 line_no = self.request.POST.get('line')
388 line_no = self.request.POST.get('line')
388 target_elem_id = 'file-{}'.format(h.safeid(h.safe_unicode(f_path)))
389 target_elem_id = 'file-{}'.format(h.safeid(h.safe_unicode(f_path)))
389
390
390 if status:
391 if status:
391 text = text or (_('Status change %(transition_icon)s %(status)s')
392 text = text or (_('Status change %(transition_icon)s %(status)s')
392 % {'transition_icon': '>',
393 % {'transition_icon': '>',
393 'status': ChangesetStatus.get_status_lbl(status)})
394 'status': ChangesetStatus.get_status_lbl(status)})
394
395
395 multi_commit_ids = []
396 multi_commit_ids = []
396 for _commit_id in self.request.POST.get('commit_ids', '').split(','):
397 for _commit_id in self.request.POST.get('commit_ids', '').split(','):
397 if _commit_id not in ['', None, EmptyCommit.raw_id]:
398 if _commit_id not in ['', None, EmptyCommit.raw_id]:
398 if _commit_id not in multi_commit_ids:
399 if _commit_id not in multi_commit_ids:
399 multi_commit_ids.append(_commit_id)
400 multi_commit_ids.append(_commit_id)
400
401
401 commit_ids = multi_commit_ids or [commit_id]
402 commit_ids = multi_commit_ids or [commit_id]
402
403
403 comment = None
404 data = {}
405 # Multiple comments for each passed commit id
404 for current_id in filter(None, commit_ids):
406 for current_id in filter(None, commit_ids):
405 comment = CommentsModel().create(
407 comment = CommentsModel().create(
406 text=text,
408 text=text,
407 repo=self.db_repo.repo_id,
409 repo=self.db_repo.repo_id,
408 user=self._rhodecode_db_user.user_id,
410 user=self._rhodecode_db_user.user_id,
409 commit_id=current_id,
411 commit_id=current_id,
410 f_path=f_path,
412 f_path=f_path,
411 line_no=line_no,
413 line_no=line_no,
412 status_change=(ChangesetStatus.get_status_lbl(status)
414 status_change=(ChangesetStatus.get_status_lbl(status)
413 if status else None),
415 if status else None),
414 status_change_type=status,
416 status_change_type=status,
415 comment_type=comment_type,
417 comment_type=comment_type,
418 is_draft=is_draft,
416 resolves_comment_id=resolves_comment_id,
419 resolves_comment_id=resolves_comment_id,
417 auth_user=self._rhodecode_user
420 auth_user=self._rhodecode_user,
421 send_email=not is_draft, # skip notification for draft comments
418 )
422 )
419 is_inline = comment.is_inline
423 is_inline = comment.is_inline
420
424
421 # get status if set !
425 # get status if set !
422 if status:
426 if status:
423 # if latest status was from pull request and it's closed
427 # if latest status was from pull request and it's closed
424 # disallow changing status !
428 # disallow changing status !
425 # dont_allow_on_closed_pull_request = True !
429 # dont_allow_on_closed_pull_request = True !
426
430
427 try:
431 try:
428 ChangesetStatusModel().set_status(
432 ChangesetStatusModel().set_status(
429 self.db_repo.repo_id,
433 self.db_repo.repo_id,
430 status,
434 status,
431 self._rhodecode_db_user.user_id,
435 self._rhodecode_db_user.user_id,
432 comment,
436 comment,
433 revision=current_id,
437 revision=current_id,
434 dont_allow_on_closed_pull_request=True
438 dont_allow_on_closed_pull_request=True
435 )
439 )
436 except StatusChangeOnClosedPullRequestError:
440 except StatusChangeOnClosedPullRequestError:
437 msg = _('Changing the status of a commit associated with '
441 msg = _('Changing the status of a commit associated with '
438 'a closed pull request is not allowed')
442 'a closed pull request is not allowed')
439 log.exception(msg)
443 log.exception(msg)
440 h.flash(msg, category='warning')
444 h.flash(msg, category='warning')
441 raise HTTPFound(h.route_path(
445 raise HTTPFound(h.route_path(
442 'repo_commit', repo_name=self.db_repo_name,
446 'repo_commit', repo_name=self.db_repo_name,
443 commit_id=current_id))
447 commit_id=current_id))
444
448
445 commit = self.db_repo.get_commit(current_id)
449 # skip notifications for drafts
446 CommentsModel().trigger_commit_comment_hook(
450 if not is_draft:
447 self.db_repo, self._rhodecode_user, 'create',
451 commit = self.db_repo.get_commit(current_id)
448 data={'comment': comment, 'commit': commit})
452 CommentsModel().trigger_commit_comment_hook(
453 self.db_repo, self._rhodecode_user, 'create',
454 data={'comment': comment, 'commit': commit})
449
455
450 # finalize, commit and redirect
451 Session().commit()
452
453 data = {}
454 if comment:
455 comment_id = comment.comment_id
456 comment_id = comment.comment_id
456 data[comment_id] = {
457 data[comment_id] = {
457 'target_id': target_elem_id
458 'target_id': target_elem_id
458 }
459 }
459 c.co = comment
460 c.co = comment
460 c.at_version_num = 0
461 c.at_version_num = 0
462 c.is_new = True
461 rendered_comment = render(
463 rendered_comment = render(
462 'rhodecode:templates/changeset/changeset_comment_block.mako',
464 'rhodecode:templates/changeset/changeset_comment_block.mako',
463 self._get_template_context(c), self.request)
465 self._get_template_context(c), self.request)
464
466
465 data[comment_id].update(comment.get_dict())
467 data[comment_id].update(comment.get_dict())
466 data[comment_id].update({'rendered_text': rendered_comment})
468 data[comment_id].update({'rendered_text': rendered_comment})
467
469
468 comment_broadcast_channel = channelstream.comment_channel(
470 # skip channelstream for draft comments
469 self.db_repo_name, commit_obj=commit)
471 if not is_draft:
472 comment_broadcast_channel = channelstream.comment_channel(
473 self.db_repo_name, commit_obj=commit)
470
474
471 comment_data = data
475 comment_data = data
472 comment_type = 'inline' if is_inline else 'general'
476 comment_type = 'inline' if is_inline else 'general'
473 channelstream.comment_channelstream_push(
477 channelstream.comment_channelstream_push(
474 self.request, comment_broadcast_channel, self._rhodecode_user,
478 self.request, comment_broadcast_channel, self._rhodecode_user,
475 _('posted a new {} comment').format(comment_type),
479 _('posted a new {} comment').format(comment_type),
476 comment_data=comment_data)
480 comment_data=comment_data)
481
482 # finalize, commit and redirect
483 Session().commit()
477
484
478 return data
485 return data
479
486
480 @LoginRequired()
487 @LoginRequired()
481 @NotAnonymous()
488 @NotAnonymous()
482 @HasRepoPermissionAnyDecorator(
489 @HasRepoPermissionAnyDecorator(
483 'repository.read', 'repository.write', 'repository.admin')
490 'repository.read', 'repository.write', 'repository.admin')
484 @CSRFRequired()
491 @CSRFRequired()
485 @view_config(
492 @view_config(
486 route_name='repo_commit_comment_preview', request_method='POST',
493 route_name='repo_commit_comment_preview', request_method='POST',
487 renderer='string', xhr=True)
494 renderer='string', xhr=True)
488 def repo_commit_comment_preview(self):
495 def repo_commit_comment_preview(self):
489 # Technically a CSRF token is not needed as no state changes with this
496 # Technically a CSRF token is not needed as no state changes with this
490 # call. However, as this is a POST is better to have it, so automated
497 # call. However, as this is a POST is better to have it, so automated
491 # tools don't flag it as potential CSRF.
498 # tools don't flag it as potential CSRF.
492 # Post is required because the payload could be bigger than the maximum
499 # Post is required because the payload could be bigger than the maximum
493 # allowed by GET.
500 # allowed by GET.
494
501
495 text = self.request.POST.get('text')
502 text = self.request.POST.get('text')
496 renderer = self.request.POST.get('renderer') or 'rst'
503 renderer = self.request.POST.get('renderer') or 'rst'
497 if text:
504 if text:
498 return h.render(text, renderer=renderer, mentions=True,
505 return h.render(text, renderer=renderer, mentions=True,
499 repo_name=self.db_repo_name)
506 repo_name=self.db_repo_name)
500 return ''
507 return ''
501
508
502 @LoginRequired()
509 @LoginRequired()
503 @HasRepoPermissionAnyDecorator(
510 @HasRepoPermissionAnyDecorator(
504 'repository.read', 'repository.write', 'repository.admin')
511 'repository.read', 'repository.write', 'repository.admin')
505 @CSRFRequired()
512 @CSRFRequired()
506 @view_config(
513 @view_config(
507 route_name='repo_commit_comment_history_view', request_method='POST',
514 route_name='repo_commit_comment_history_view', request_method='POST',
508 renderer='string', xhr=True)
515 renderer='string', xhr=True)
509 def repo_commit_comment_history_view(self):
516 def repo_commit_comment_history_view(self):
510 c = self.load_default_context()
517 c = self.load_default_context()
511
518
512 comment_history_id = self.request.matchdict['comment_history_id']
519 comment_history_id = self.request.matchdict['comment_history_id']
513 comment_history = ChangesetCommentHistory.get_or_404(comment_history_id)
520 comment_history = ChangesetCommentHistory.get_or_404(comment_history_id)
514 is_repo_comment = comment_history.comment.repo.repo_id == self.db_repo.repo_id
521 is_repo_comment = comment_history.comment.repo.repo_id == self.db_repo.repo_id
515
522
516 if is_repo_comment:
523 if is_repo_comment:
517 c.comment_history = comment_history
524 c.comment_history = comment_history
518
525
519 rendered_comment = render(
526 rendered_comment = render(
520 'rhodecode:templates/changeset/comment_history.mako',
527 'rhodecode:templates/changeset/comment_history.mako',
521 self._get_template_context(c)
528 self._get_template_context(c)
522 , self.request)
529 , self.request)
523 return rendered_comment
530 return rendered_comment
524 else:
531 else:
525 log.warning('No permissions for user %s to show comment_history_id: %s',
532 log.warning('No permissions for user %s to show comment_history_id: %s',
526 self._rhodecode_db_user, comment_history_id)
533 self._rhodecode_db_user, comment_history_id)
527 raise HTTPNotFound()
534 raise HTTPNotFound()
528
535
529 @LoginRequired()
536 @LoginRequired()
530 @NotAnonymous()
537 @NotAnonymous()
531 @HasRepoPermissionAnyDecorator(
538 @HasRepoPermissionAnyDecorator(
532 'repository.read', 'repository.write', 'repository.admin')
539 'repository.read', 'repository.write', 'repository.admin')
533 @CSRFRequired()
540 @CSRFRequired()
534 @view_config(
541 @view_config(
535 route_name='repo_commit_comment_attachment_upload', request_method='POST',
542 route_name='repo_commit_comment_attachment_upload', request_method='POST',
536 renderer='json_ext', xhr=True)
543 renderer='json_ext', xhr=True)
537 def repo_commit_comment_attachment_upload(self):
544 def repo_commit_comment_attachment_upload(self):
538 c = self.load_default_context()
545 c = self.load_default_context()
539 upload_key = 'attachment'
546 upload_key = 'attachment'
540
547
541 file_obj = self.request.POST.get(upload_key)
548 file_obj = self.request.POST.get(upload_key)
542
549
543 if file_obj is None:
550 if file_obj is None:
544 self.request.response.status = 400
551 self.request.response.status = 400
545 return {'store_fid': None,
552 return {'store_fid': None,
546 'access_path': None,
553 'access_path': None,
547 'error': '{} data field is missing'.format(upload_key)}
554 'error': '{} data field is missing'.format(upload_key)}
548
555
549 if not hasattr(file_obj, 'filename'):
556 if not hasattr(file_obj, 'filename'):
550 self.request.response.status = 400
557 self.request.response.status = 400
551 return {'store_fid': None,
558 return {'store_fid': None,
552 'access_path': None,
559 'access_path': None,
553 'error': 'filename cannot be read from the data field'}
560 'error': 'filename cannot be read from the data field'}
554
561
555 filename = file_obj.filename
562 filename = file_obj.filename
556 file_display_name = filename
563 file_display_name = filename
557
564
558 metadata = {
565 metadata = {
559 'user_uploaded': {'username': self._rhodecode_user.username,
566 'user_uploaded': {'username': self._rhodecode_user.username,
560 'user_id': self._rhodecode_user.user_id,
567 'user_id': self._rhodecode_user.user_id,
561 'ip': self._rhodecode_user.ip_addr}}
568 'ip': self._rhodecode_user.ip_addr}}
562
569
563 # TODO(marcink): allow .ini configuration for allowed_extensions, and file-size
570 # TODO(marcink): allow .ini configuration for allowed_extensions, and file-size
564 allowed_extensions = [
571 allowed_extensions = [
565 'gif', '.jpeg', '.jpg', '.png', '.docx', '.gz', '.log', '.pdf',
572 'gif', '.jpeg', '.jpg', '.png', '.docx', '.gz', '.log', '.pdf',
566 '.pptx', '.txt', '.xlsx', '.zip']
573 '.pptx', '.txt', '.xlsx', '.zip']
567 max_file_size = 10 * 1024 * 1024 # 10MB, also validated via dropzone.js
574 max_file_size = 10 * 1024 * 1024 # 10MB, also validated via dropzone.js
568
575
569 try:
576 try:
570 storage = store_utils.get_file_storage(self.request.registry.settings)
577 storage = store_utils.get_file_storage(self.request.registry.settings)
571 store_uid, metadata = storage.save_file(
578 store_uid, metadata = storage.save_file(
572 file_obj.file, filename, extra_metadata=metadata,
579 file_obj.file, filename, extra_metadata=metadata,
573 extensions=allowed_extensions, max_filesize=max_file_size)
580 extensions=allowed_extensions, max_filesize=max_file_size)
574 except FileNotAllowedException:
581 except FileNotAllowedException:
575 self.request.response.status = 400
582 self.request.response.status = 400
576 permitted_extensions = ', '.join(allowed_extensions)
583 permitted_extensions = ', '.join(allowed_extensions)
577 error_msg = 'File `{}` is not allowed. ' \
584 error_msg = 'File `{}` is not allowed. ' \
578 'Only following extensions are permitted: {}'.format(
585 'Only following extensions are permitted: {}'.format(
579 filename, permitted_extensions)
586 filename, permitted_extensions)
580 return {'store_fid': None,
587 return {'store_fid': None,
581 'access_path': None,
588 'access_path': None,
582 'error': error_msg}
589 'error': error_msg}
583 except FileOverSizeException:
590 except FileOverSizeException:
584 self.request.response.status = 400
591 self.request.response.status = 400
585 limit_mb = h.format_byte_size_binary(max_file_size)
592 limit_mb = h.format_byte_size_binary(max_file_size)
586 return {'store_fid': None,
593 return {'store_fid': None,
587 'access_path': None,
594 'access_path': None,
588 'error': 'File {} is exceeding allowed limit of {}.'.format(
595 'error': 'File {} is exceeding allowed limit of {}.'.format(
589 filename, limit_mb)}
596 filename, limit_mb)}
590
597
591 try:
598 try:
592 entry = FileStore.create(
599 entry = FileStore.create(
593 file_uid=store_uid, filename=metadata["filename"],
600 file_uid=store_uid, filename=metadata["filename"],
594 file_hash=metadata["sha256"], file_size=metadata["size"],
601 file_hash=metadata["sha256"], file_size=metadata["size"],
595 file_display_name=file_display_name,
602 file_display_name=file_display_name,
596 file_description=u'comment attachment `{}`'.format(safe_unicode(filename)),
603 file_description=u'comment attachment `{}`'.format(safe_unicode(filename)),
597 hidden=True, check_acl=True, user_id=self._rhodecode_user.user_id,
604 hidden=True, check_acl=True, user_id=self._rhodecode_user.user_id,
598 scope_repo_id=self.db_repo.repo_id
605 scope_repo_id=self.db_repo.repo_id
599 )
606 )
600 Session().add(entry)
607 Session().add(entry)
601 Session().commit()
608 Session().commit()
602 log.debug('Stored upload in DB as %s', entry)
609 log.debug('Stored upload in DB as %s', entry)
603 except Exception:
610 except Exception:
604 log.exception('Failed to store file %s', filename)
611 log.exception('Failed to store file %s', filename)
605 self.request.response.status = 400
612 self.request.response.status = 400
606 return {'store_fid': None,
613 return {'store_fid': None,
607 'access_path': None,
614 'access_path': None,
608 'error': 'File {} failed to store in DB.'.format(filename)}
615 'error': 'File {} failed to store in DB.'.format(filename)}
609
616
610 Session().commit()
617 Session().commit()
611
618
612 return {
619 return {
613 'store_fid': store_uid,
620 'store_fid': store_uid,
614 'access_path': h.route_path(
621 'access_path': h.route_path(
615 'download_file', fid=store_uid),
622 'download_file', fid=store_uid),
616 'fqn_access_path': h.route_url(
623 'fqn_access_path': h.route_url(
617 'download_file', fid=store_uid),
624 'download_file', fid=store_uid),
618 'repo_access_path': h.route_path(
625 'repo_access_path': h.route_path(
619 'repo_artifacts_get', repo_name=self.db_repo_name, uid=store_uid),
626 'repo_artifacts_get', repo_name=self.db_repo_name, uid=store_uid),
620 'repo_fqn_access_path': h.route_url(
627 'repo_fqn_access_path': h.route_url(
621 'repo_artifacts_get', repo_name=self.db_repo_name, uid=store_uid),
628 'repo_artifacts_get', repo_name=self.db_repo_name, uid=store_uid),
622 }
629 }
623
630
624 @LoginRequired()
631 @LoginRequired()
625 @NotAnonymous()
632 @NotAnonymous()
626 @HasRepoPermissionAnyDecorator(
633 @HasRepoPermissionAnyDecorator(
627 'repository.read', 'repository.write', 'repository.admin')
634 'repository.read', 'repository.write', 'repository.admin')
628 @CSRFRequired()
635 @CSRFRequired()
629 @view_config(
636 @view_config(
630 route_name='repo_commit_comment_delete', request_method='POST',
637 route_name='repo_commit_comment_delete', request_method='POST',
631 renderer='json_ext')
638 renderer='json_ext')
632 def repo_commit_comment_delete(self):
639 def repo_commit_comment_delete(self):
633 commit_id = self.request.matchdict['commit_id']
640 commit_id = self.request.matchdict['commit_id']
634 comment_id = self.request.matchdict['comment_id']
641 comment_id = self.request.matchdict['comment_id']
635
642
636 comment = ChangesetComment.get_or_404(comment_id)
643 comment = ChangesetComment.get_or_404(comment_id)
637 if not comment:
644 if not comment:
638 log.debug('Comment with id:%s not found, skipping', comment_id)
645 log.debug('Comment with id:%s not found, skipping', comment_id)
639 # comment already deleted in another call probably
646 # comment already deleted in another call probably
640 return True
647 return True
641
648
642 if comment.immutable:
649 if comment.immutable:
643 # don't allow deleting comments that are immutable
650 # don't allow deleting comments that are immutable
644 raise HTTPForbidden()
651 raise HTTPForbidden()
645
652
646 is_repo_admin = h.HasRepoPermissionAny('repository.admin')(self.db_repo_name)
653 is_repo_admin = h.HasRepoPermissionAny('repository.admin')(self.db_repo_name)
647 super_admin = h.HasPermissionAny('hg.admin')()
654 super_admin = h.HasPermissionAny('hg.admin')()
648 comment_owner = (comment.author.user_id == self._rhodecode_db_user.user_id)
655 comment_owner = (comment.author.user_id == self._rhodecode_db_user.user_id)
649 is_repo_comment = comment.repo.repo_id == self.db_repo.repo_id
656 is_repo_comment = comment.repo.repo_id == self.db_repo.repo_id
650 comment_repo_admin = is_repo_admin and is_repo_comment
657 comment_repo_admin = is_repo_admin and is_repo_comment
651
658
652 if super_admin or comment_owner or comment_repo_admin:
659 if super_admin or comment_owner or comment_repo_admin:
653 CommentsModel().delete(comment=comment, auth_user=self._rhodecode_user)
660 CommentsModel().delete(comment=comment, auth_user=self._rhodecode_user)
654 Session().commit()
661 Session().commit()
655 return True
662 return True
656 else:
663 else:
657 log.warning('No permissions for user %s to delete comment_id: %s',
664 log.warning('No permissions for user %s to delete comment_id: %s',
658 self._rhodecode_db_user, comment_id)
665 self._rhodecode_db_user, comment_id)
659 raise HTTPNotFound()
666 raise HTTPNotFound()
660
667
661 @LoginRequired()
668 @LoginRequired()
662 @NotAnonymous()
669 @NotAnonymous()
663 @HasRepoPermissionAnyDecorator(
670 @HasRepoPermissionAnyDecorator(
664 'repository.read', 'repository.write', 'repository.admin')
671 'repository.read', 'repository.write', 'repository.admin')
665 @CSRFRequired()
672 @CSRFRequired()
666 @view_config(
673 @view_config(
667 route_name='repo_commit_comment_edit', request_method='POST',
674 route_name='repo_commit_comment_edit', request_method='POST',
668 renderer='json_ext')
675 renderer='json_ext')
669 def repo_commit_comment_edit(self):
676 def repo_commit_comment_edit(self):
670 self.load_default_context()
677 self.load_default_context()
671
678
672 comment_id = self.request.matchdict['comment_id']
679 comment_id = self.request.matchdict['comment_id']
673 comment = ChangesetComment.get_or_404(comment_id)
680 comment = ChangesetComment.get_or_404(comment_id)
674
681
675 if comment.immutable:
682 if comment.immutable:
676 # don't allow deleting comments that are immutable
683 # don't allow deleting comments that are immutable
677 raise HTTPForbidden()
684 raise HTTPForbidden()
678
685
679 is_repo_admin = h.HasRepoPermissionAny('repository.admin')(self.db_repo_name)
686 is_repo_admin = h.HasRepoPermissionAny('repository.admin')(self.db_repo_name)
680 super_admin = h.HasPermissionAny('hg.admin')()
687 super_admin = h.HasPermissionAny('hg.admin')()
681 comment_owner = (comment.author.user_id == self._rhodecode_db_user.user_id)
688 comment_owner = (comment.author.user_id == self._rhodecode_db_user.user_id)
682 is_repo_comment = comment.repo.repo_id == self.db_repo.repo_id
689 is_repo_comment = comment.repo.repo_id == self.db_repo.repo_id
683 comment_repo_admin = is_repo_admin and is_repo_comment
690 comment_repo_admin = is_repo_admin and is_repo_comment
684
691
685 if super_admin or comment_owner or comment_repo_admin:
692 if super_admin or comment_owner or comment_repo_admin:
686 text = self.request.POST.get('text')
693 text = self.request.POST.get('text')
687 version = self.request.POST.get('version')
694 version = self.request.POST.get('version')
688 if text == comment.text:
695 if text == comment.text:
689 log.warning(
696 log.warning(
690 'Comment(repo): '
697 'Comment(repo): '
691 'Trying to create new version '
698 'Trying to create new version '
692 'with the same comment body {}'.format(
699 'with the same comment body {}'.format(
693 comment_id,
700 comment_id,
694 )
701 )
695 )
702 )
696 raise HTTPNotFound()
703 raise HTTPNotFound()
697
704
698 if version.isdigit():
705 if version.isdigit():
699 version = int(version)
706 version = int(version)
700 else:
707 else:
701 log.warning(
708 log.warning(
702 'Comment(repo): Wrong version type {} {} '
709 'Comment(repo): Wrong version type {} {} '
703 'for comment {}'.format(
710 'for comment {}'.format(
704 version,
711 version,
705 type(version),
712 type(version),
706 comment_id,
713 comment_id,
707 )
714 )
708 )
715 )
709 raise HTTPNotFound()
716 raise HTTPNotFound()
710
717
711 try:
718 try:
712 comment_history = CommentsModel().edit(
719 comment_history = CommentsModel().edit(
713 comment_id=comment_id,
720 comment_id=comment_id,
714 text=text,
721 text=text,
715 auth_user=self._rhodecode_user,
722 auth_user=self._rhodecode_user,
716 version=version,
723 version=version,
717 )
724 )
718 except CommentVersionMismatch:
725 except CommentVersionMismatch:
719 raise HTTPConflict()
726 raise HTTPConflict()
720
727
721 if not comment_history:
728 if not comment_history:
722 raise HTTPNotFound()
729 raise HTTPNotFound()
723
730
724 commit_id = self.request.matchdict['commit_id']
731 commit_id = self.request.matchdict['commit_id']
725 commit = self.db_repo.get_commit(commit_id)
732 commit = self.db_repo.get_commit(commit_id)
726 CommentsModel().trigger_commit_comment_hook(
733 CommentsModel().trigger_commit_comment_hook(
727 self.db_repo, self._rhodecode_user, 'edit',
734 self.db_repo, self._rhodecode_user, 'edit',
728 data={'comment': comment, 'commit': commit})
735 data={'comment': comment, 'commit': commit})
729
736
730 Session().commit()
737 Session().commit()
731 return {
738 return {
732 'comment_history_id': comment_history.comment_history_id,
739 'comment_history_id': comment_history.comment_history_id,
733 'comment_id': comment.comment_id,
740 'comment_id': comment.comment_id,
734 'comment_version': comment_history.version,
741 'comment_version': comment_history.version,
735 'comment_author_username': comment_history.author.username,
742 'comment_author_username': comment_history.author.username,
736 'comment_author_gravatar': h.gravatar_url(comment_history.author.email, 16),
743 'comment_author_gravatar': h.gravatar_url(comment_history.author.email, 16),
737 'comment_created_on': h.age_component(comment_history.created_on,
744 'comment_created_on': h.age_component(comment_history.created_on,
738 time_is_local=True),
745 time_is_local=True),
739 }
746 }
740 else:
747 else:
741 log.warning('No permissions for user %s to edit comment_id: %s',
748 log.warning('No permissions for user %s to edit comment_id: %s',
742 self._rhodecode_db_user, comment_id)
749 self._rhodecode_db_user, comment_id)
743 raise HTTPNotFound()
750 raise HTTPNotFound()
744
751
745 @LoginRequired()
752 @LoginRequired()
746 @HasRepoPermissionAnyDecorator(
753 @HasRepoPermissionAnyDecorator(
747 'repository.read', 'repository.write', 'repository.admin')
754 'repository.read', 'repository.write', 'repository.admin')
748 @view_config(
755 @view_config(
749 route_name='repo_commit_data', request_method='GET',
756 route_name='repo_commit_data', request_method='GET',
750 renderer='json_ext', xhr=True)
757 renderer='json_ext', xhr=True)
751 def repo_commit_data(self):
758 def repo_commit_data(self):
752 commit_id = self.request.matchdict['commit_id']
759 commit_id = self.request.matchdict['commit_id']
753 self.load_default_context()
760 self.load_default_context()
754
761
755 try:
762 try:
756 return self.rhodecode_vcs_repo.get_commit(commit_id=commit_id)
763 return self.rhodecode_vcs_repo.get_commit(commit_id=commit_id)
757 except CommitDoesNotExistError as e:
764 except CommitDoesNotExistError as e:
758 return EmptyCommit(message=str(e))
765 return EmptyCommit(message=str(e))
759
766
760 @LoginRequired()
767 @LoginRequired()
761 @HasRepoPermissionAnyDecorator(
768 @HasRepoPermissionAnyDecorator(
762 'repository.read', 'repository.write', 'repository.admin')
769 'repository.read', 'repository.write', 'repository.admin')
763 @view_config(
770 @view_config(
764 route_name='repo_commit_children', request_method='GET',
771 route_name='repo_commit_children', request_method='GET',
765 renderer='json_ext', xhr=True)
772 renderer='json_ext', xhr=True)
766 def repo_commit_children(self):
773 def repo_commit_children(self):
767 commit_id = self.request.matchdict['commit_id']
774 commit_id = self.request.matchdict['commit_id']
768 self.load_default_context()
775 self.load_default_context()
769
776
770 try:
777 try:
771 commit = self.rhodecode_vcs_repo.get_commit(commit_id=commit_id)
778 commit = self.rhodecode_vcs_repo.get_commit(commit_id=commit_id)
772 children = commit.children
779 children = commit.children
773 except CommitDoesNotExistError:
780 except CommitDoesNotExistError:
774 children = []
781 children = []
775
782
776 result = {"results": children}
783 result = {"results": children}
777 return result
784 return result
778
785
779 @LoginRequired()
786 @LoginRequired()
780 @HasRepoPermissionAnyDecorator(
787 @HasRepoPermissionAnyDecorator(
781 'repository.read', 'repository.write', 'repository.admin')
788 'repository.read', 'repository.write', 'repository.admin')
782 @view_config(
789 @view_config(
783 route_name='repo_commit_parents', request_method='GET',
790 route_name='repo_commit_parents', request_method='GET',
784 renderer='json_ext')
791 renderer='json_ext')
785 def repo_commit_parents(self):
792 def repo_commit_parents(self):
786 commit_id = self.request.matchdict['commit_id']
793 commit_id = self.request.matchdict['commit_id']
787 self.load_default_context()
794 self.load_default_context()
788
795
789 try:
796 try:
790 commit = self.rhodecode_vcs_repo.get_commit(commit_id=commit_id)
797 commit = self.rhodecode_vcs_repo.get_commit(commit_id=commit_id)
791 parents = commit.parents
798 parents = commit.parents
792 except CommitDoesNotExistError:
799 except CommitDoesNotExistError:
793 parents = []
800 parents = []
794 result = {"results": parents}
801 result = {"results": parents}
795 return result
802 return result
General Comments 0
You need to be logged in to leave comments. Login now