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