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