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