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