##// END OF EJS Templates
comments: view edit history of comment should be allowed for anonymous users, as they anyway see comments body.
marcink -
r4489:a9e68bf1 default
parent child Browse files
Show More
@@ -1,782 +1,781 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
34 from rhodecode.lib import diffs, codeblocks
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.allowed_reviewers = reviewers
173 c.allowed_reviewers = reviewers
174 # from associated statuses, check the pull requests, and
174 # from associated statuses, check the pull requests, and
175 # show comments from them
175 # show comments from them
176 for pr in prs:
176 for pr in prs:
177 c.comments.extend(pr.comments)
177 c.comments.extend(pr.comments)
178
178
179 c.unresolved_comments = CommentsModel()\
179 c.unresolved_comments = CommentsModel()\
180 .get_commit_unresolved_todos(commit.raw_id)
180 .get_commit_unresolved_todos(commit.raw_id)
181 c.resolved_comments = CommentsModel()\
181 c.resolved_comments = CommentsModel()\
182 .get_commit_resolved_todos(commit.raw_id)
182 .get_commit_resolved_todos(commit.raw_id)
183
183
184 c.inline_comments_flat = CommentsModel()\
184 c.inline_comments_flat = CommentsModel()\
185 .get_commit_inline_comments(commit.raw_id)
185 .get_commit_inline_comments(commit.raw_id)
186
186
187 review_statuses = ChangesetStatusModel().aggregate_votes_by_user(
187 review_statuses = ChangesetStatusModel().aggregate_votes_by_user(
188 statuses, reviewers)
188 statuses, reviewers)
189
189
190 c.commit_review_status = ChangesetStatus.STATUS_NOT_REVIEWED
190 c.commit_review_status = ChangesetStatus.STATUS_NOT_REVIEWED
191
191
192 c.commit_set_reviewers_data_json = collections.OrderedDict({'reviewers': []})
192 c.commit_set_reviewers_data_json = collections.OrderedDict({'reviewers': []})
193
193
194 for review_obj, member, reasons, mandatory, status in review_statuses:
194 for review_obj, member, reasons, mandatory, status in review_statuses:
195 member_reviewer = h.reviewer_as_json(
195 member_reviewer = h.reviewer_as_json(
196 member, reasons=reasons, mandatory=mandatory,
196 member, reasons=reasons, mandatory=mandatory,
197 user_group=None
197 user_group=None
198 )
198 )
199
199
200 current_review_status = status[0][1].status if status else ChangesetStatus.STATUS_NOT_REVIEWED
200 current_review_status = status[0][1].status if status else ChangesetStatus.STATUS_NOT_REVIEWED
201 member_reviewer['review_status'] = current_review_status
201 member_reviewer['review_status'] = current_review_status
202 member_reviewer['review_status_label'] = h.commit_status_lbl(current_review_status)
202 member_reviewer['review_status_label'] = h.commit_status_lbl(current_review_status)
203 member_reviewer['allowed_to_update'] = False
203 member_reviewer['allowed_to_update'] = False
204 c.commit_set_reviewers_data_json['reviewers'].append(member_reviewer)
204 c.commit_set_reviewers_data_json['reviewers'].append(member_reviewer)
205
205
206 c.commit_set_reviewers_data_json = json.dumps(c.commit_set_reviewers_data_json)
206 c.commit_set_reviewers_data_json = json.dumps(c.commit_set_reviewers_data_json)
207
207
208 # NOTE(marcink): this uses the same voting logic as in pull-requests
208 # NOTE(marcink): this uses the same voting logic as in pull-requests
209 c.commit_review_status = ChangesetStatusModel().calculate_status(review_statuses)
209 c.commit_review_status = ChangesetStatusModel().calculate_status(review_statuses)
210 c.commit_broadcast_channel = u'/repo${}$/commit/{}'.format(
210 c.commit_broadcast_channel = u'/repo${}$/commit/{}'.format(
211 c.repo_name,
211 c.repo_name,
212 commit.raw_id
212 commit.raw_id
213 )
213 )
214
214
215 diff = None
215 diff = None
216 # Iterate over ranges (default commit view is always one commit)
216 # Iterate over ranges (default commit view is always one commit)
217 for commit in c.commit_ranges:
217 for commit in c.commit_ranges:
218 c.changes[commit.raw_id] = []
218 c.changes[commit.raw_id] = []
219
219
220 commit2 = commit
220 commit2 = commit
221 commit1 = commit.first_parent
221 commit1 = commit.first_parent
222
222
223 if method == 'show':
223 if method == 'show':
224 inline_comments = CommentsModel().get_inline_comments(
224 inline_comments = CommentsModel().get_inline_comments(
225 self.db_repo.repo_id, revision=commit.raw_id)
225 self.db_repo.repo_id, revision=commit.raw_id)
226 c.inline_cnt = len(CommentsModel().get_inline_comments_as_list(
226 c.inline_cnt = len(CommentsModel().get_inline_comments_as_list(
227 inline_comments))
227 inline_comments))
228 c.inline_comments = inline_comments
228 c.inline_comments = inline_comments
229
229
230 cache_path = self.rhodecode_vcs_repo.get_create_shadow_cache_pr_path(
230 cache_path = self.rhodecode_vcs_repo.get_create_shadow_cache_pr_path(
231 self.db_repo)
231 self.db_repo)
232 cache_file_path = diff_cache_exist(
232 cache_file_path = diff_cache_exist(
233 cache_path, 'diff', commit.raw_id,
233 cache_path, 'diff', commit.raw_id,
234 hide_whitespace_changes, diff_context, c.fulldiff)
234 hide_whitespace_changes, diff_context, c.fulldiff)
235
235
236 caching_enabled = self._is_diff_cache_enabled(self.db_repo)
236 caching_enabled = self._is_diff_cache_enabled(self.db_repo)
237 force_recache = str2bool(self.request.GET.get('force_recache'))
237 force_recache = str2bool(self.request.GET.get('force_recache'))
238
238
239 cached_diff = None
239 cached_diff = None
240 if caching_enabled:
240 if caching_enabled:
241 cached_diff = load_cached_diff(cache_file_path)
241 cached_diff = load_cached_diff(cache_file_path)
242
242
243 has_proper_diff_cache = cached_diff and cached_diff.get('diff')
243 has_proper_diff_cache = cached_diff and cached_diff.get('diff')
244 if not force_recache and has_proper_diff_cache:
244 if not force_recache and has_proper_diff_cache:
245 diffset = cached_diff['diff']
245 diffset = cached_diff['diff']
246 else:
246 else:
247 vcs_diff = self.rhodecode_vcs_repo.get_diff(
247 vcs_diff = self.rhodecode_vcs_repo.get_diff(
248 commit1, commit2,
248 commit1, commit2,
249 ignore_whitespace=hide_whitespace_changes,
249 ignore_whitespace=hide_whitespace_changes,
250 context=diff_context)
250 context=diff_context)
251
251
252 diff_processor = diffs.DiffProcessor(
252 diff_processor = diffs.DiffProcessor(
253 vcs_diff, format='newdiff', diff_limit=diff_limit,
253 vcs_diff, format='newdiff', diff_limit=diff_limit,
254 file_limit=file_limit, show_full_diff=c.fulldiff)
254 file_limit=file_limit, show_full_diff=c.fulldiff)
255
255
256 _parsed = diff_processor.prepare()
256 _parsed = diff_processor.prepare()
257
257
258 diffset = codeblocks.DiffSet(
258 diffset = codeblocks.DiffSet(
259 repo_name=self.db_repo_name,
259 repo_name=self.db_repo_name,
260 source_node_getter=codeblocks.diffset_node_getter(commit1),
260 source_node_getter=codeblocks.diffset_node_getter(commit1),
261 target_node_getter=codeblocks.diffset_node_getter(commit2))
261 target_node_getter=codeblocks.diffset_node_getter(commit2))
262
262
263 diffset = self.path_filter.render_patchset_filtered(
263 diffset = self.path_filter.render_patchset_filtered(
264 diffset, _parsed, commit1.raw_id, commit2.raw_id)
264 diffset, _parsed, commit1.raw_id, commit2.raw_id)
265
265
266 # save cached diff
266 # save cached diff
267 if caching_enabled:
267 if caching_enabled:
268 cache_diff(cache_file_path, diffset, None)
268 cache_diff(cache_file_path, diffset, None)
269
269
270 c.limited_diff = diffset.limited_diff
270 c.limited_diff = diffset.limited_diff
271 c.changes[commit.raw_id] = diffset
271 c.changes[commit.raw_id] = diffset
272 else:
272 else:
273 # TODO(marcink): no cache usage here...
273 # TODO(marcink): no cache usage here...
274 _diff = self.rhodecode_vcs_repo.get_diff(
274 _diff = self.rhodecode_vcs_repo.get_diff(
275 commit1, commit2,
275 commit1, commit2,
276 ignore_whitespace=hide_whitespace_changes, context=diff_context)
276 ignore_whitespace=hide_whitespace_changes, context=diff_context)
277 diff_processor = diffs.DiffProcessor(
277 diff_processor = diffs.DiffProcessor(
278 _diff, format='newdiff', diff_limit=diff_limit,
278 _diff, format='newdiff', diff_limit=diff_limit,
279 file_limit=file_limit, show_full_diff=c.fulldiff)
279 file_limit=file_limit, show_full_diff=c.fulldiff)
280 # downloads/raw we only need RAW diff nothing else
280 # downloads/raw we only need RAW diff nothing else
281 diff = self.path_filter.get_raw_patch(diff_processor)
281 diff = self.path_filter.get_raw_patch(diff_processor)
282 c.changes[commit.raw_id] = [None, None, None, None, diff, None, None]
282 c.changes[commit.raw_id] = [None, None, None, None, diff, None, None]
283
283
284 # sort comments by how they were generated
284 # sort comments by how they were generated
285 c.comments = sorted(c.comments, key=lambda x: x.comment_id)
285 c.comments = sorted(c.comments, key=lambda x: x.comment_id)
286 c.at_version_num = None
286 c.at_version_num = None
287
287
288 if len(c.commit_ranges) == 1:
288 if len(c.commit_ranges) == 1:
289 c.commit = c.commit_ranges[0]
289 c.commit = c.commit_ranges[0]
290 c.parent_tmpl = ''.join(
290 c.parent_tmpl = ''.join(
291 '# Parent %s\n' % x.raw_id for x in c.commit.parents)
291 '# Parent %s\n' % x.raw_id for x in c.commit.parents)
292
292
293 if method == 'download':
293 if method == 'download':
294 response = Response(diff)
294 response = Response(diff)
295 response.content_type = 'text/plain'
295 response.content_type = 'text/plain'
296 response.content_disposition = (
296 response.content_disposition = (
297 'attachment; filename=%s.diff' % commit_id_range[:12])
297 'attachment; filename=%s.diff' % commit_id_range[:12])
298 return response
298 return response
299 elif method == 'patch':
299 elif method == 'patch':
300 c.diff = safe_unicode(diff)
300 c.diff = safe_unicode(diff)
301 patch = render(
301 patch = render(
302 'rhodecode:templates/changeset/patch_changeset.mako',
302 'rhodecode:templates/changeset/patch_changeset.mako',
303 self._get_template_context(c), self.request)
303 self._get_template_context(c), self.request)
304 response = Response(patch)
304 response = Response(patch)
305 response.content_type = 'text/plain'
305 response.content_type = 'text/plain'
306 return response
306 return response
307 elif method == 'raw':
307 elif method == 'raw':
308 response = Response(diff)
308 response = Response(diff)
309 response.content_type = 'text/plain'
309 response.content_type = 'text/plain'
310 return response
310 return response
311 elif method == 'show':
311 elif method == 'show':
312 if len(c.commit_ranges) == 1:
312 if len(c.commit_ranges) == 1:
313 html = render(
313 html = render(
314 'rhodecode:templates/changeset/changeset.mako',
314 'rhodecode:templates/changeset/changeset.mako',
315 self._get_template_context(c), self.request)
315 self._get_template_context(c), self.request)
316 return Response(html)
316 return Response(html)
317 else:
317 else:
318 c.ancestor = None
318 c.ancestor = None
319 c.target_repo = self.db_repo
319 c.target_repo = self.db_repo
320 html = render(
320 html = render(
321 'rhodecode:templates/changeset/changeset_range.mako',
321 'rhodecode:templates/changeset/changeset_range.mako',
322 self._get_template_context(c), self.request)
322 self._get_template_context(c), self.request)
323 return Response(html)
323 return Response(html)
324
324
325 raise HTTPBadRequest()
325 raise HTTPBadRequest()
326
326
327 @LoginRequired()
327 @LoginRequired()
328 @HasRepoPermissionAnyDecorator(
328 @HasRepoPermissionAnyDecorator(
329 'repository.read', 'repository.write', 'repository.admin')
329 'repository.read', 'repository.write', 'repository.admin')
330 @view_config(
330 @view_config(
331 route_name='repo_commit', request_method='GET',
331 route_name='repo_commit', request_method='GET',
332 renderer=None)
332 renderer=None)
333 def repo_commit_show(self):
333 def repo_commit_show(self):
334 commit_id = self.request.matchdict['commit_id']
334 commit_id = self.request.matchdict['commit_id']
335 return self._commit(commit_id, method='show')
335 return self._commit(commit_id, method='show')
336
336
337 @LoginRequired()
337 @LoginRequired()
338 @HasRepoPermissionAnyDecorator(
338 @HasRepoPermissionAnyDecorator(
339 'repository.read', 'repository.write', 'repository.admin')
339 'repository.read', 'repository.write', 'repository.admin')
340 @view_config(
340 @view_config(
341 route_name='repo_commit_raw', request_method='GET',
341 route_name='repo_commit_raw', request_method='GET',
342 renderer=None)
342 renderer=None)
343 @view_config(
343 @view_config(
344 route_name='repo_commit_raw_deprecated', request_method='GET',
344 route_name='repo_commit_raw_deprecated', request_method='GET',
345 renderer=None)
345 renderer=None)
346 def repo_commit_raw(self):
346 def repo_commit_raw(self):
347 commit_id = self.request.matchdict['commit_id']
347 commit_id = self.request.matchdict['commit_id']
348 return self._commit(commit_id, method='raw')
348 return self._commit(commit_id, method='raw')
349
349
350 @LoginRequired()
350 @LoginRequired()
351 @HasRepoPermissionAnyDecorator(
351 @HasRepoPermissionAnyDecorator(
352 'repository.read', 'repository.write', 'repository.admin')
352 'repository.read', 'repository.write', 'repository.admin')
353 @view_config(
353 @view_config(
354 route_name='repo_commit_patch', request_method='GET',
354 route_name='repo_commit_patch', request_method='GET',
355 renderer=None)
355 renderer=None)
356 def repo_commit_patch(self):
356 def repo_commit_patch(self):
357 commit_id = self.request.matchdict['commit_id']
357 commit_id = self.request.matchdict['commit_id']
358 return self._commit(commit_id, method='patch')
358 return self._commit(commit_id, method='patch')
359
359
360 @LoginRequired()
360 @LoginRequired()
361 @HasRepoPermissionAnyDecorator(
361 @HasRepoPermissionAnyDecorator(
362 'repository.read', 'repository.write', 'repository.admin')
362 'repository.read', 'repository.write', 'repository.admin')
363 @view_config(
363 @view_config(
364 route_name='repo_commit_download', request_method='GET',
364 route_name='repo_commit_download', request_method='GET',
365 renderer=None)
365 renderer=None)
366 def repo_commit_download(self):
366 def repo_commit_download(self):
367 commit_id = self.request.matchdict['commit_id']
367 commit_id = self.request.matchdict['commit_id']
368 return self._commit(commit_id, method='download')
368 return self._commit(commit_id, method='download')
369
369
370 @LoginRequired()
370 @LoginRequired()
371 @NotAnonymous()
371 @NotAnonymous()
372 @HasRepoPermissionAnyDecorator(
372 @HasRepoPermissionAnyDecorator(
373 'repository.read', 'repository.write', 'repository.admin')
373 'repository.read', 'repository.write', 'repository.admin')
374 @CSRFRequired()
374 @CSRFRequired()
375 @view_config(
375 @view_config(
376 route_name='repo_commit_comment_create', request_method='POST',
376 route_name='repo_commit_comment_create', request_method='POST',
377 renderer='json_ext')
377 renderer='json_ext')
378 def repo_commit_comment_create(self):
378 def repo_commit_comment_create(self):
379 _ = self.request.translate
379 _ = self.request.translate
380 commit_id = self.request.matchdict['commit_id']
380 commit_id = self.request.matchdict['commit_id']
381
381
382 c = self.load_default_context()
382 c = self.load_default_context()
383 status = self.request.POST.get('changeset_status', None)
383 status = self.request.POST.get('changeset_status', None)
384 text = self.request.POST.get('text')
384 text = self.request.POST.get('text')
385 comment_type = self.request.POST.get('comment_type')
385 comment_type = self.request.POST.get('comment_type')
386 resolves_comment_id = self.request.POST.get('resolves_comment_id', None)
386 resolves_comment_id = self.request.POST.get('resolves_comment_id', None)
387
387
388 if status:
388 if status:
389 text = text or (_('Status change %(transition_icon)s %(status)s')
389 text = text or (_('Status change %(transition_icon)s %(status)s')
390 % {'transition_icon': '>',
390 % {'transition_icon': '>',
391 'status': ChangesetStatus.get_status_lbl(status)})
391 'status': ChangesetStatus.get_status_lbl(status)})
392
392
393 multi_commit_ids = []
393 multi_commit_ids = []
394 for _commit_id in self.request.POST.get('commit_ids', '').split(','):
394 for _commit_id in self.request.POST.get('commit_ids', '').split(','):
395 if _commit_id not in ['', None, EmptyCommit.raw_id]:
395 if _commit_id not in ['', None, EmptyCommit.raw_id]:
396 if _commit_id not in multi_commit_ids:
396 if _commit_id not in multi_commit_ids:
397 multi_commit_ids.append(_commit_id)
397 multi_commit_ids.append(_commit_id)
398
398
399 commit_ids = multi_commit_ids or [commit_id]
399 commit_ids = multi_commit_ids or [commit_id]
400
400
401 comment = None
401 comment = None
402 for current_id in filter(None, commit_ids):
402 for current_id in filter(None, commit_ids):
403 comment = CommentsModel().create(
403 comment = CommentsModel().create(
404 text=text,
404 text=text,
405 repo=self.db_repo.repo_id,
405 repo=self.db_repo.repo_id,
406 user=self._rhodecode_db_user.user_id,
406 user=self._rhodecode_db_user.user_id,
407 commit_id=current_id,
407 commit_id=current_id,
408 f_path=self.request.POST.get('f_path'),
408 f_path=self.request.POST.get('f_path'),
409 line_no=self.request.POST.get('line'),
409 line_no=self.request.POST.get('line'),
410 status_change=(ChangesetStatus.get_status_lbl(status)
410 status_change=(ChangesetStatus.get_status_lbl(status)
411 if status else None),
411 if status else None),
412 status_change_type=status,
412 status_change_type=status,
413 comment_type=comment_type,
413 comment_type=comment_type,
414 resolves_comment_id=resolves_comment_id,
414 resolves_comment_id=resolves_comment_id,
415 auth_user=self._rhodecode_user
415 auth_user=self._rhodecode_user
416 )
416 )
417
417
418 # get status if set !
418 # get status if set !
419 if status:
419 if status:
420 # if latest status was from pull request and it's closed
420 # if latest status was from pull request and it's closed
421 # disallow changing status !
421 # disallow changing status !
422 # dont_allow_on_closed_pull_request = True !
422 # dont_allow_on_closed_pull_request = True !
423
423
424 try:
424 try:
425 ChangesetStatusModel().set_status(
425 ChangesetStatusModel().set_status(
426 self.db_repo.repo_id,
426 self.db_repo.repo_id,
427 status,
427 status,
428 self._rhodecode_db_user.user_id,
428 self._rhodecode_db_user.user_id,
429 comment,
429 comment,
430 revision=current_id,
430 revision=current_id,
431 dont_allow_on_closed_pull_request=True
431 dont_allow_on_closed_pull_request=True
432 )
432 )
433 except StatusChangeOnClosedPullRequestError:
433 except StatusChangeOnClosedPullRequestError:
434 msg = _('Changing the status of a commit associated with '
434 msg = _('Changing the status of a commit associated with '
435 'a closed pull request is not allowed')
435 'a closed pull request is not allowed')
436 log.exception(msg)
436 log.exception(msg)
437 h.flash(msg, category='warning')
437 h.flash(msg, category='warning')
438 raise HTTPFound(h.route_path(
438 raise HTTPFound(h.route_path(
439 'repo_commit', repo_name=self.db_repo_name,
439 'repo_commit', repo_name=self.db_repo_name,
440 commit_id=current_id))
440 commit_id=current_id))
441
441
442 commit = self.db_repo.get_commit(current_id)
442 commit = self.db_repo.get_commit(current_id)
443 CommentsModel().trigger_commit_comment_hook(
443 CommentsModel().trigger_commit_comment_hook(
444 self.db_repo, self._rhodecode_user, 'create',
444 self.db_repo, self._rhodecode_user, 'create',
445 data={'comment': comment, 'commit': commit})
445 data={'comment': comment, 'commit': commit})
446
446
447 # finalize, commit and redirect
447 # finalize, commit and redirect
448 Session().commit()
448 Session().commit()
449
449
450 data = {
450 data = {
451 'target_id': h.safeid(h.safe_unicode(
451 'target_id': h.safeid(h.safe_unicode(
452 self.request.POST.get('f_path'))),
452 self.request.POST.get('f_path'))),
453 }
453 }
454 if comment:
454 if comment:
455 c.co = comment
455 c.co = comment
456 c.at_version_num = 0
456 c.at_version_num = 0
457 rendered_comment = render(
457 rendered_comment = render(
458 'rhodecode:templates/changeset/changeset_comment_block.mako',
458 'rhodecode:templates/changeset/changeset_comment_block.mako',
459 self._get_template_context(c), self.request)
459 self._get_template_context(c), self.request)
460
460
461 data.update(comment.get_dict())
461 data.update(comment.get_dict())
462 data.update({'rendered_text': rendered_comment})
462 data.update({'rendered_text': rendered_comment})
463
463
464 return data
464 return data
465
465
466 @LoginRequired()
466 @LoginRequired()
467 @NotAnonymous()
467 @NotAnonymous()
468 @HasRepoPermissionAnyDecorator(
468 @HasRepoPermissionAnyDecorator(
469 'repository.read', 'repository.write', 'repository.admin')
469 'repository.read', 'repository.write', 'repository.admin')
470 @CSRFRequired()
470 @CSRFRequired()
471 @view_config(
471 @view_config(
472 route_name='repo_commit_comment_preview', request_method='POST',
472 route_name='repo_commit_comment_preview', request_method='POST',
473 renderer='string', xhr=True)
473 renderer='string', xhr=True)
474 def repo_commit_comment_preview(self):
474 def repo_commit_comment_preview(self):
475 # Technically a CSRF token is not needed as no state changes with this
475 # Technically a CSRF token is not needed as no state changes with this
476 # call. However, as this is a POST is better to have it, so automated
476 # call. However, as this is a POST is better to have it, so automated
477 # tools don't flag it as potential CSRF.
477 # tools don't flag it as potential CSRF.
478 # Post is required because the payload could be bigger than the maximum
478 # Post is required because the payload could be bigger than the maximum
479 # allowed by GET.
479 # allowed by GET.
480
480
481 text = self.request.POST.get('text')
481 text = self.request.POST.get('text')
482 renderer = self.request.POST.get('renderer') or 'rst'
482 renderer = self.request.POST.get('renderer') or 'rst'
483 if text:
483 if text:
484 return h.render(text, renderer=renderer, mentions=True,
484 return h.render(text, renderer=renderer, mentions=True,
485 repo_name=self.db_repo_name)
485 repo_name=self.db_repo_name)
486 return ''
486 return ''
487
487
488 @LoginRequired()
488 @LoginRequired()
489 @NotAnonymous()
490 @HasRepoPermissionAnyDecorator(
489 @HasRepoPermissionAnyDecorator(
491 'repository.read', 'repository.write', 'repository.admin')
490 'repository.read', 'repository.write', 'repository.admin')
492 @CSRFRequired()
491 @CSRFRequired()
493 @view_config(
492 @view_config(
494 route_name='repo_commit_comment_history_view', request_method='POST',
493 route_name='repo_commit_comment_history_view', request_method='POST',
495 renderer='string', xhr=True)
494 renderer='string', xhr=True)
496 def repo_commit_comment_history_view(self):
495 def repo_commit_comment_history_view(self):
497 c = self.load_default_context()
496 c = self.load_default_context()
498
497
499 comment_history_id = self.request.matchdict['comment_history_id']
498 comment_history_id = self.request.matchdict['comment_history_id']
500 comment_history = ChangesetCommentHistory.get_or_404(comment_history_id)
499 comment_history = ChangesetCommentHistory.get_or_404(comment_history_id)
501 is_repo_comment = comment_history.comment.repo.repo_id == self.db_repo.repo_id
500 is_repo_comment = comment_history.comment.repo.repo_id == self.db_repo.repo_id
502
501
503 if is_repo_comment:
502 if is_repo_comment:
504 c.comment_history = comment_history
503 c.comment_history = comment_history
505
504
506 rendered_comment = render(
505 rendered_comment = render(
507 'rhodecode:templates/changeset/comment_history.mako',
506 'rhodecode:templates/changeset/comment_history.mako',
508 self._get_template_context(c)
507 self._get_template_context(c)
509 , self.request)
508 , self.request)
510 return rendered_comment
509 return rendered_comment
511 else:
510 else:
512 log.warning('No permissions for user %s to show comment_history_id: %s',
511 log.warning('No permissions for user %s to show comment_history_id: %s',
513 self._rhodecode_db_user, comment_history_id)
512 self._rhodecode_db_user, comment_history_id)
514 raise HTTPNotFound()
513 raise HTTPNotFound()
515
514
516 @LoginRequired()
515 @LoginRequired()
517 @NotAnonymous()
516 @NotAnonymous()
518 @HasRepoPermissionAnyDecorator(
517 @HasRepoPermissionAnyDecorator(
519 'repository.read', 'repository.write', 'repository.admin')
518 'repository.read', 'repository.write', 'repository.admin')
520 @CSRFRequired()
519 @CSRFRequired()
521 @view_config(
520 @view_config(
522 route_name='repo_commit_comment_attachment_upload', request_method='POST',
521 route_name='repo_commit_comment_attachment_upload', request_method='POST',
523 renderer='json_ext', xhr=True)
522 renderer='json_ext', xhr=True)
524 def repo_commit_comment_attachment_upload(self):
523 def repo_commit_comment_attachment_upload(self):
525 c = self.load_default_context()
524 c = self.load_default_context()
526 upload_key = 'attachment'
525 upload_key = 'attachment'
527
526
528 file_obj = self.request.POST.get(upload_key)
527 file_obj = self.request.POST.get(upload_key)
529
528
530 if file_obj is None:
529 if file_obj is None:
531 self.request.response.status = 400
530 self.request.response.status = 400
532 return {'store_fid': None,
531 return {'store_fid': None,
533 'access_path': None,
532 'access_path': None,
534 'error': '{} data field is missing'.format(upload_key)}
533 'error': '{} data field is missing'.format(upload_key)}
535
534
536 if not hasattr(file_obj, 'filename'):
535 if not hasattr(file_obj, 'filename'):
537 self.request.response.status = 400
536 self.request.response.status = 400
538 return {'store_fid': None,
537 return {'store_fid': None,
539 'access_path': None,
538 'access_path': None,
540 'error': 'filename cannot be read from the data field'}
539 'error': 'filename cannot be read from the data field'}
541
540
542 filename = file_obj.filename
541 filename = file_obj.filename
543 file_display_name = filename
542 file_display_name = filename
544
543
545 metadata = {
544 metadata = {
546 'user_uploaded': {'username': self._rhodecode_user.username,
545 'user_uploaded': {'username': self._rhodecode_user.username,
547 'user_id': self._rhodecode_user.user_id,
546 'user_id': self._rhodecode_user.user_id,
548 'ip': self._rhodecode_user.ip_addr}}
547 'ip': self._rhodecode_user.ip_addr}}
549
548
550 # TODO(marcink): allow .ini configuration for allowed_extensions, and file-size
549 # TODO(marcink): allow .ini configuration for allowed_extensions, and file-size
551 allowed_extensions = [
550 allowed_extensions = [
552 'gif', '.jpeg', '.jpg', '.png', '.docx', '.gz', '.log', '.pdf',
551 'gif', '.jpeg', '.jpg', '.png', '.docx', '.gz', '.log', '.pdf',
553 '.pptx', '.txt', '.xlsx', '.zip']
552 '.pptx', '.txt', '.xlsx', '.zip']
554 max_file_size = 10 * 1024 * 1024 # 10MB, also validated via dropzone.js
553 max_file_size = 10 * 1024 * 1024 # 10MB, also validated via dropzone.js
555
554
556 try:
555 try:
557 storage = store_utils.get_file_storage(self.request.registry.settings)
556 storage = store_utils.get_file_storage(self.request.registry.settings)
558 store_uid, metadata = storage.save_file(
557 store_uid, metadata = storage.save_file(
559 file_obj.file, filename, extra_metadata=metadata,
558 file_obj.file, filename, extra_metadata=metadata,
560 extensions=allowed_extensions, max_filesize=max_file_size)
559 extensions=allowed_extensions, max_filesize=max_file_size)
561 except FileNotAllowedException:
560 except FileNotAllowedException:
562 self.request.response.status = 400
561 self.request.response.status = 400
563 permitted_extensions = ', '.join(allowed_extensions)
562 permitted_extensions = ', '.join(allowed_extensions)
564 error_msg = 'File `{}` is not allowed. ' \
563 error_msg = 'File `{}` is not allowed. ' \
565 'Only following extensions are permitted: {}'.format(
564 'Only following extensions are permitted: {}'.format(
566 filename, permitted_extensions)
565 filename, permitted_extensions)
567 return {'store_fid': None,
566 return {'store_fid': None,
568 'access_path': None,
567 'access_path': None,
569 'error': error_msg}
568 'error': error_msg}
570 except FileOverSizeException:
569 except FileOverSizeException:
571 self.request.response.status = 400
570 self.request.response.status = 400
572 limit_mb = h.format_byte_size_binary(max_file_size)
571 limit_mb = h.format_byte_size_binary(max_file_size)
573 return {'store_fid': None,
572 return {'store_fid': None,
574 'access_path': None,
573 'access_path': None,
575 'error': 'File {} is exceeding allowed limit of {}.'.format(
574 'error': 'File {} is exceeding allowed limit of {}.'.format(
576 filename, limit_mb)}
575 filename, limit_mb)}
577
576
578 try:
577 try:
579 entry = FileStore.create(
578 entry = FileStore.create(
580 file_uid=store_uid, filename=metadata["filename"],
579 file_uid=store_uid, filename=metadata["filename"],
581 file_hash=metadata["sha256"], file_size=metadata["size"],
580 file_hash=metadata["sha256"], file_size=metadata["size"],
582 file_display_name=file_display_name,
581 file_display_name=file_display_name,
583 file_description=u'comment attachment `{}`'.format(safe_unicode(filename)),
582 file_description=u'comment attachment `{}`'.format(safe_unicode(filename)),
584 hidden=True, check_acl=True, user_id=self._rhodecode_user.user_id,
583 hidden=True, check_acl=True, user_id=self._rhodecode_user.user_id,
585 scope_repo_id=self.db_repo.repo_id
584 scope_repo_id=self.db_repo.repo_id
586 )
585 )
587 Session().add(entry)
586 Session().add(entry)
588 Session().commit()
587 Session().commit()
589 log.debug('Stored upload in DB as %s', entry)
588 log.debug('Stored upload in DB as %s', entry)
590 except Exception:
589 except Exception:
591 log.exception('Failed to store file %s', filename)
590 log.exception('Failed to store file %s', filename)
592 self.request.response.status = 400
591 self.request.response.status = 400
593 return {'store_fid': None,
592 return {'store_fid': None,
594 'access_path': None,
593 'access_path': None,
595 'error': 'File {} failed to store in DB.'.format(filename)}
594 'error': 'File {} failed to store in DB.'.format(filename)}
596
595
597 Session().commit()
596 Session().commit()
598
597
599 return {
598 return {
600 'store_fid': store_uid,
599 'store_fid': store_uid,
601 'access_path': h.route_path(
600 'access_path': h.route_path(
602 'download_file', fid=store_uid),
601 'download_file', fid=store_uid),
603 'fqn_access_path': h.route_url(
602 'fqn_access_path': h.route_url(
604 'download_file', fid=store_uid),
603 'download_file', fid=store_uid),
605 'repo_access_path': h.route_path(
604 'repo_access_path': h.route_path(
606 'repo_artifacts_get', repo_name=self.db_repo_name, uid=store_uid),
605 'repo_artifacts_get', repo_name=self.db_repo_name, uid=store_uid),
607 'repo_fqn_access_path': h.route_url(
606 'repo_fqn_access_path': h.route_url(
608 'repo_artifacts_get', repo_name=self.db_repo_name, uid=store_uid),
607 'repo_artifacts_get', repo_name=self.db_repo_name, uid=store_uid),
609 }
608 }
610
609
611 @LoginRequired()
610 @LoginRequired()
612 @NotAnonymous()
611 @NotAnonymous()
613 @HasRepoPermissionAnyDecorator(
612 @HasRepoPermissionAnyDecorator(
614 'repository.read', 'repository.write', 'repository.admin')
613 'repository.read', 'repository.write', 'repository.admin')
615 @CSRFRequired()
614 @CSRFRequired()
616 @view_config(
615 @view_config(
617 route_name='repo_commit_comment_delete', request_method='POST',
616 route_name='repo_commit_comment_delete', request_method='POST',
618 renderer='json_ext')
617 renderer='json_ext')
619 def repo_commit_comment_delete(self):
618 def repo_commit_comment_delete(self):
620 commit_id = self.request.matchdict['commit_id']
619 commit_id = self.request.matchdict['commit_id']
621 comment_id = self.request.matchdict['comment_id']
620 comment_id = self.request.matchdict['comment_id']
622
621
623 comment = ChangesetComment.get_or_404(comment_id)
622 comment = ChangesetComment.get_or_404(comment_id)
624 if not comment:
623 if not comment:
625 log.debug('Comment with id:%s not found, skipping', comment_id)
624 log.debug('Comment with id:%s not found, skipping', comment_id)
626 # comment already deleted in another call probably
625 # comment already deleted in another call probably
627 return True
626 return True
628
627
629 if comment.immutable:
628 if comment.immutable:
630 # don't allow deleting comments that are immutable
629 # don't allow deleting comments that are immutable
631 raise HTTPForbidden()
630 raise HTTPForbidden()
632
631
633 is_repo_admin = h.HasRepoPermissionAny('repository.admin')(self.db_repo_name)
632 is_repo_admin = h.HasRepoPermissionAny('repository.admin')(self.db_repo_name)
634 super_admin = h.HasPermissionAny('hg.admin')()
633 super_admin = h.HasPermissionAny('hg.admin')()
635 comment_owner = (comment.author.user_id == self._rhodecode_db_user.user_id)
634 comment_owner = (comment.author.user_id == self._rhodecode_db_user.user_id)
636 is_repo_comment = comment.repo.repo_id == self.db_repo.repo_id
635 is_repo_comment = comment.repo.repo_id == self.db_repo.repo_id
637 comment_repo_admin = is_repo_admin and is_repo_comment
636 comment_repo_admin = is_repo_admin and is_repo_comment
638
637
639 if super_admin or comment_owner or comment_repo_admin:
638 if super_admin or comment_owner or comment_repo_admin:
640 CommentsModel().delete(comment=comment, auth_user=self._rhodecode_user)
639 CommentsModel().delete(comment=comment, auth_user=self._rhodecode_user)
641 Session().commit()
640 Session().commit()
642 return True
641 return True
643 else:
642 else:
644 log.warning('No permissions for user %s to delete comment_id: %s',
643 log.warning('No permissions for user %s to delete comment_id: %s',
645 self._rhodecode_db_user, comment_id)
644 self._rhodecode_db_user, comment_id)
646 raise HTTPNotFound()
645 raise HTTPNotFound()
647
646
648 @LoginRequired()
647 @LoginRequired()
649 @NotAnonymous()
648 @NotAnonymous()
650 @HasRepoPermissionAnyDecorator(
649 @HasRepoPermissionAnyDecorator(
651 'repository.read', 'repository.write', 'repository.admin')
650 'repository.read', 'repository.write', 'repository.admin')
652 @CSRFRequired()
651 @CSRFRequired()
653 @view_config(
652 @view_config(
654 route_name='repo_commit_comment_edit', request_method='POST',
653 route_name='repo_commit_comment_edit', request_method='POST',
655 renderer='json_ext')
654 renderer='json_ext')
656 def repo_commit_comment_edit(self):
655 def repo_commit_comment_edit(self):
657 self.load_default_context()
656 self.load_default_context()
658
657
659 comment_id = self.request.matchdict['comment_id']
658 comment_id = self.request.matchdict['comment_id']
660 comment = ChangesetComment.get_or_404(comment_id)
659 comment = ChangesetComment.get_or_404(comment_id)
661
660
662 if comment.immutable:
661 if comment.immutable:
663 # don't allow deleting comments that are immutable
662 # don't allow deleting comments that are immutable
664 raise HTTPForbidden()
663 raise HTTPForbidden()
665
664
666 is_repo_admin = h.HasRepoPermissionAny('repository.admin')(self.db_repo_name)
665 is_repo_admin = h.HasRepoPermissionAny('repository.admin')(self.db_repo_name)
667 super_admin = h.HasPermissionAny('hg.admin')()
666 super_admin = h.HasPermissionAny('hg.admin')()
668 comment_owner = (comment.author.user_id == self._rhodecode_db_user.user_id)
667 comment_owner = (comment.author.user_id == self._rhodecode_db_user.user_id)
669 is_repo_comment = comment.repo.repo_id == self.db_repo.repo_id
668 is_repo_comment = comment.repo.repo_id == self.db_repo.repo_id
670 comment_repo_admin = is_repo_admin and is_repo_comment
669 comment_repo_admin = is_repo_admin and is_repo_comment
671
670
672 if super_admin or comment_owner or comment_repo_admin:
671 if super_admin or comment_owner or comment_repo_admin:
673 text = self.request.POST.get('text')
672 text = self.request.POST.get('text')
674 version = self.request.POST.get('version')
673 version = self.request.POST.get('version')
675 if text == comment.text:
674 if text == comment.text:
676 log.warning(
675 log.warning(
677 'Comment(repo): '
676 'Comment(repo): '
678 'Trying to create new version '
677 'Trying to create new version '
679 'with the same comment body {}'.format(
678 'with the same comment body {}'.format(
680 comment_id,
679 comment_id,
681 )
680 )
682 )
681 )
683 raise HTTPNotFound()
682 raise HTTPNotFound()
684
683
685 if version.isdigit():
684 if version.isdigit():
686 version = int(version)
685 version = int(version)
687 else:
686 else:
688 log.warning(
687 log.warning(
689 'Comment(repo): Wrong version type {} {} '
688 'Comment(repo): Wrong version type {} {} '
690 'for comment {}'.format(
689 'for comment {}'.format(
691 version,
690 version,
692 type(version),
691 type(version),
693 comment_id,
692 comment_id,
694 )
693 )
695 )
694 )
696 raise HTTPNotFound()
695 raise HTTPNotFound()
697
696
698 try:
697 try:
699 comment_history = CommentsModel().edit(
698 comment_history = CommentsModel().edit(
700 comment_id=comment_id,
699 comment_id=comment_id,
701 text=text,
700 text=text,
702 auth_user=self._rhodecode_user,
701 auth_user=self._rhodecode_user,
703 version=version,
702 version=version,
704 )
703 )
705 except CommentVersionMismatch:
704 except CommentVersionMismatch:
706 raise HTTPConflict()
705 raise HTTPConflict()
707
706
708 if not comment_history:
707 if not comment_history:
709 raise HTTPNotFound()
708 raise HTTPNotFound()
710
709
711 commit_id = self.request.matchdict['commit_id']
710 commit_id = self.request.matchdict['commit_id']
712 commit = self.db_repo.get_commit(commit_id)
711 commit = self.db_repo.get_commit(commit_id)
713 CommentsModel().trigger_commit_comment_hook(
712 CommentsModel().trigger_commit_comment_hook(
714 self.db_repo, self._rhodecode_user, 'edit',
713 self.db_repo, self._rhodecode_user, 'edit',
715 data={'comment': comment, 'commit': commit})
714 data={'comment': comment, 'commit': commit})
716
715
717 Session().commit()
716 Session().commit()
718 return {
717 return {
719 'comment_history_id': comment_history.comment_history_id,
718 'comment_history_id': comment_history.comment_history_id,
720 'comment_id': comment.comment_id,
719 'comment_id': comment.comment_id,
721 'comment_version': comment_history.version,
720 'comment_version': comment_history.version,
722 'comment_author_username': comment_history.author.username,
721 'comment_author_username': comment_history.author.username,
723 'comment_author_gravatar': h.gravatar_url(comment_history.author.email, 16),
722 'comment_author_gravatar': h.gravatar_url(comment_history.author.email, 16),
724 'comment_created_on': h.age_component(comment_history.created_on,
723 'comment_created_on': h.age_component(comment_history.created_on,
725 time_is_local=True),
724 time_is_local=True),
726 }
725 }
727 else:
726 else:
728 log.warning('No permissions for user %s to edit comment_id: %s',
727 log.warning('No permissions for user %s to edit comment_id: %s',
729 self._rhodecode_db_user, comment_id)
728 self._rhodecode_db_user, comment_id)
730 raise HTTPNotFound()
729 raise HTTPNotFound()
731
730
732 @LoginRequired()
731 @LoginRequired()
733 @HasRepoPermissionAnyDecorator(
732 @HasRepoPermissionAnyDecorator(
734 'repository.read', 'repository.write', 'repository.admin')
733 'repository.read', 'repository.write', 'repository.admin')
735 @view_config(
734 @view_config(
736 route_name='repo_commit_data', request_method='GET',
735 route_name='repo_commit_data', request_method='GET',
737 renderer='json_ext', xhr=True)
736 renderer='json_ext', xhr=True)
738 def repo_commit_data(self):
737 def repo_commit_data(self):
739 commit_id = self.request.matchdict['commit_id']
738 commit_id = self.request.matchdict['commit_id']
740 self.load_default_context()
739 self.load_default_context()
741
740
742 try:
741 try:
743 return self.rhodecode_vcs_repo.get_commit(commit_id=commit_id)
742 return self.rhodecode_vcs_repo.get_commit(commit_id=commit_id)
744 except CommitDoesNotExistError as e:
743 except CommitDoesNotExistError as e:
745 return EmptyCommit(message=str(e))
744 return EmptyCommit(message=str(e))
746
745
747 @LoginRequired()
746 @LoginRequired()
748 @HasRepoPermissionAnyDecorator(
747 @HasRepoPermissionAnyDecorator(
749 'repository.read', 'repository.write', 'repository.admin')
748 'repository.read', 'repository.write', 'repository.admin')
750 @view_config(
749 @view_config(
751 route_name='repo_commit_children', request_method='GET',
750 route_name='repo_commit_children', request_method='GET',
752 renderer='json_ext', xhr=True)
751 renderer='json_ext', xhr=True)
753 def repo_commit_children(self):
752 def repo_commit_children(self):
754 commit_id = self.request.matchdict['commit_id']
753 commit_id = self.request.matchdict['commit_id']
755 self.load_default_context()
754 self.load_default_context()
756
755
757 try:
756 try:
758 commit = self.rhodecode_vcs_repo.get_commit(commit_id=commit_id)
757 commit = self.rhodecode_vcs_repo.get_commit(commit_id=commit_id)
759 children = commit.children
758 children = commit.children
760 except CommitDoesNotExistError:
759 except CommitDoesNotExistError:
761 children = []
760 children = []
762
761
763 result = {"results": children}
762 result = {"results": children}
764 return result
763 return result
765
764
766 @LoginRequired()
765 @LoginRequired()
767 @HasRepoPermissionAnyDecorator(
766 @HasRepoPermissionAnyDecorator(
768 'repository.read', 'repository.write', 'repository.admin')
767 'repository.read', 'repository.write', 'repository.admin')
769 @view_config(
768 @view_config(
770 route_name='repo_commit_parents', request_method='GET',
769 route_name='repo_commit_parents', request_method='GET',
771 renderer='json_ext')
770 renderer='json_ext')
772 def repo_commit_parents(self):
771 def repo_commit_parents(self):
773 commit_id = self.request.matchdict['commit_id']
772 commit_id = self.request.matchdict['commit_id']
774 self.load_default_context()
773 self.load_default_context()
775
774
776 try:
775 try:
777 commit = self.rhodecode_vcs_repo.get_commit(commit_id=commit_id)
776 commit = self.rhodecode_vcs_repo.get_commit(commit_id=commit_id)
778 parents = commit.parents
777 parents = commit.parents
779 except CommitDoesNotExistError:
778 except CommitDoesNotExistError:
780 parents = []
779 parents = []
781 result = {"results": parents}
780 result = {"results": parents}
782 return result
781 return result
General Comments 0
You need to be logged in to leave comments. Login now