##// END OF EJS Templates
pull-requests: allow super-admins to force change state of locked PRs....
marcink -
r4233:95c3d867 stable
parent child Browse files
Show More

The requested changes are too big and content was truncated. Show full diff

@@ -1,1479 +1,1493 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2019 RhodeCode GmbH
3 # Copyright (C) 2011-2019 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 import formencode
24 import formencode
25 import formencode.htmlfill
25 import formencode.htmlfill
26 import peppercorn
26 import peppercorn
27 from pyramid.httpexceptions import (
27 from pyramid.httpexceptions import (
28 HTTPFound, HTTPNotFound, HTTPForbidden, HTTPBadRequest)
28 HTTPFound, HTTPNotFound, HTTPForbidden, HTTPBadRequest)
29 from pyramid.view import view_config
29 from pyramid.view import view_config
30 from pyramid.renderers import render
30 from pyramid.renderers import render
31
31
32 from rhodecode.apps._base import RepoAppView, DataGridAppView
32 from rhodecode.apps._base import RepoAppView, DataGridAppView
33
33
34 from rhodecode.lib import helpers as h, diffs, codeblocks, channelstream
34 from rhodecode.lib import helpers as h, diffs, codeblocks, channelstream
35 from rhodecode.lib.base import vcs_operation_context
35 from rhodecode.lib.base import vcs_operation_context
36 from rhodecode.lib.diffs import load_cached_diff, cache_diff, diff_cache_exist
36 from rhodecode.lib.diffs import load_cached_diff, cache_diff, diff_cache_exist
37 from rhodecode.lib.ext_json import json
37 from rhodecode.lib.ext_json import json
38 from rhodecode.lib.auth import (
38 from rhodecode.lib.auth import (
39 LoginRequired, HasRepoPermissionAny, HasRepoPermissionAnyDecorator,
39 LoginRequired, HasRepoPermissionAny, HasRepoPermissionAnyDecorator,
40 NotAnonymous, CSRFRequired)
40 NotAnonymous, CSRFRequired)
41 from rhodecode.lib.utils2 import str2bool, safe_str, safe_unicode
41 from rhodecode.lib.utils2 import str2bool, safe_str, safe_unicode
42 from rhodecode.lib.vcs.backends.base import EmptyCommit, UpdateFailureReason
42 from rhodecode.lib.vcs.backends.base import EmptyCommit, UpdateFailureReason
43 from rhodecode.lib.vcs.exceptions import (CommitDoesNotExistError,
43 from rhodecode.lib.vcs.exceptions import (CommitDoesNotExistError,
44 RepositoryRequirementError, EmptyRepositoryError)
44 RepositoryRequirementError, EmptyRepositoryError)
45 from rhodecode.model.changeset_status import ChangesetStatusModel
45 from rhodecode.model.changeset_status import ChangesetStatusModel
46 from rhodecode.model.comment import CommentsModel
46 from rhodecode.model.comment import CommentsModel
47 from rhodecode.model.db import (func, or_, PullRequest, PullRequestVersion,
47 from rhodecode.model.db import (func, or_, PullRequest, PullRequestVersion,
48 ChangesetComment, ChangesetStatus, Repository)
48 ChangesetComment, ChangesetStatus, Repository)
49 from rhodecode.model.forms import PullRequestForm
49 from rhodecode.model.forms import PullRequestForm
50 from rhodecode.model.meta import Session
50 from rhodecode.model.meta import Session
51 from rhodecode.model.pull_request import PullRequestModel, MergeCheck
51 from rhodecode.model.pull_request import PullRequestModel, MergeCheck
52 from rhodecode.model.scm import ScmModel
52 from rhodecode.model.scm import ScmModel
53
53
54 log = logging.getLogger(__name__)
54 log = logging.getLogger(__name__)
55
55
56
56
57 class RepoPullRequestsView(RepoAppView, DataGridAppView):
57 class RepoPullRequestsView(RepoAppView, DataGridAppView):
58
58
59 def load_default_context(self):
59 def load_default_context(self):
60 c = self._get_local_tmpl_context(include_app_defaults=True)
60 c = self._get_local_tmpl_context(include_app_defaults=True)
61 c.REVIEW_STATUS_APPROVED = ChangesetStatus.STATUS_APPROVED
61 c.REVIEW_STATUS_APPROVED = ChangesetStatus.STATUS_APPROVED
62 c.REVIEW_STATUS_REJECTED = ChangesetStatus.STATUS_REJECTED
62 c.REVIEW_STATUS_REJECTED = ChangesetStatus.STATUS_REJECTED
63 # backward compat., we use for OLD PRs a plain renderer
63 # backward compat., we use for OLD PRs a plain renderer
64 c.renderer = 'plain'
64 c.renderer = 'plain'
65 return c
65 return c
66
66
67 def _get_pull_requests_list(
67 def _get_pull_requests_list(
68 self, repo_name, source, filter_type, opened_by, statuses):
68 self, repo_name, source, filter_type, opened_by, statuses):
69
69
70 draw, start, limit = self._extract_chunk(self.request)
70 draw, start, limit = self._extract_chunk(self.request)
71 search_q, order_by, order_dir = self._extract_ordering(self.request)
71 search_q, order_by, order_dir = self._extract_ordering(self.request)
72 _render = self.request.get_partial_renderer(
72 _render = self.request.get_partial_renderer(
73 'rhodecode:templates/data_table/_dt_elements.mako')
73 'rhodecode:templates/data_table/_dt_elements.mako')
74
74
75 # pagination
75 # pagination
76
76
77 if filter_type == 'awaiting_review':
77 if filter_type == 'awaiting_review':
78 pull_requests = PullRequestModel().get_awaiting_review(
78 pull_requests = PullRequestModel().get_awaiting_review(
79 repo_name, search_q=search_q, source=source, opened_by=opened_by,
79 repo_name, search_q=search_q, source=source, opened_by=opened_by,
80 statuses=statuses, offset=start, length=limit,
80 statuses=statuses, offset=start, length=limit,
81 order_by=order_by, order_dir=order_dir)
81 order_by=order_by, order_dir=order_dir)
82 pull_requests_total_count = PullRequestModel().count_awaiting_review(
82 pull_requests_total_count = PullRequestModel().count_awaiting_review(
83 repo_name, search_q=search_q, source=source, statuses=statuses,
83 repo_name, search_q=search_q, source=source, statuses=statuses,
84 opened_by=opened_by)
84 opened_by=opened_by)
85 elif filter_type == 'awaiting_my_review':
85 elif filter_type == 'awaiting_my_review':
86 pull_requests = PullRequestModel().get_awaiting_my_review(
86 pull_requests = PullRequestModel().get_awaiting_my_review(
87 repo_name, search_q=search_q, source=source, opened_by=opened_by,
87 repo_name, search_q=search_q, source=source, opened_by=opened_by,
88 user_id=self._rhodecode_user.user_id, statuses=statuses,
88 user_id=self._rhodecode_user.user_id, statuses=statuses,
89 offset=start, length=limit, order_by=order_by,
89 offset=start, length=limit, order_by=order_by,
90 order_dir=order_dir)
90 order_dir=order_dir)
91 pull_requests_total_count = PullRequestModel().count_awaiting_my_review(
91 pull_requests_total_count = PullRequestModel().count_awaiting_my_review(
92 repo_name, search_q=search_q, source=source, user_id=self._rhodecode_user.user_id,
92 repo_name, search_q=search_q, source=source, user_id=self._rhodecode_user.user_id,
93 statuses=statuses, opened_by=opened_by)
93 statuses=statuses, opened_by=opened_by)
94 else:
94 else:
95 pull_requests = PullRequestModel().get_all(
95 pull_requests = PullRequestModel().get_all(
96 repo_name, search_q=search_q, source=source, opened_by=opened_by,
96 repo_name, search_q=search_q, source=source, opened_by=opened_by,
97 statuses=statuses, offset=start, length=limit,
97 statuses=statuses, offset=start, length=limit,
98 order_by=order_by, order_dir=order_dir)
98 order_by=order_by, order_dir=order_dir)
99 pull_requests_total_count = PullRequestModel().count_all(
99 pull_requests_total_count = PullRequestModel().count_all(
100 repo_name, search_q=search_q, source=source, statuses=statuses,
100 repo_name, search_q=search_q, source=source, statuses=statuses,
101 opened_by=opened_by)
101 opened_by=opened_by)
102
102
103 data = []
103 data = []
104 comments_model = CommentsModel()
104 comments_model = CommentsModel()
105 for pr in pull_requests:
105 for pr in pull_requests:
106 comments = comments_model.get_all_comments(
106 comments = comments_model.get_all_comments(
107 self.db_repo.repo_id, pull_request=pr)
107 self.db_repo.repo_id, pull_request=pr)
108
108
109 data.append({
109 data.append({
110 'name': _render('pullrequest_name',
110 'name': _render('pullrequest_name',
111 pr.pull_request_id, pr.pull_request_state,
111 pr.pull_request_id, pr.pull_request_state,
112 pr.work_in_progress, pr.target_repo.repo_name),
112 pr.work_in_progress, pr.target_repo.repo_name),
113 'name_raw': pr.pull_request_id,
113 'name_raw': pr.pull_request_id,
114 'status': _render('pullrequest_status',
114 'status': _render('pullrequest_status',
115 pr.calculated_review_status()),
115 pr.calculated_review_status()),
116 'title': _render('pullrequest_title', pr.title, pr.description),
116 'title': _render('pullrequest_title', pr.title, pr.description),
117 'description': h.escape(pr.description),
117 'description': h.escape(pr.description),
118 'updated_on': _render('pullrequest_updated_on',
118 'updated_on': _render('pullrequest_updated_on',
119 h.datetime_to_time(pr.updated_on)),
119 h.datetime_to_time(pr.updated_on)),
120 'updated_on_raw': h.datetime_to_time(pr.updated_on),
120 'updated_on_raw': h.datetime_to_time(pr.updated_on),
121 'created_on': _render('pullrequest_updated_on',
121 'created_on': _render('pullrequest_updated_on',
122 h.datetime_to_time(pr.created_on)),
122 h.datetime_to_time(pr.created_on)),
123 'created_on_raw': h.datetime_to_time(pr.created_on),
123 'created_on_raw': h.datetime_to_time(pr.created_on),
124 'state': pr.pull_request_state,
124 'state': pr.pull_request_state,
125 'author': _render('pullrequest_author',
125 'author': _render('pullrequest_author',
126 pr.author.full_contact, ),
126 pr.author.full_contact, ),
127 'author_raw': pr.author.full_name,
127 'author_raw': pr.author.full_name,
128 'comments': _render('pullrequest_comments', len(comments)),
128 'comments': _render('pullrequest_comments', len(comments)),
129 'comments_raw': len(comments),
129 'comments_raw': len(comments),
130 'closed': pr.is_closed(),
130 'closed': pr.is_closed(),
131 })
131 })
132
132
133 data = ({
133 data = ({
134 'draw': draw,
134 'draw': draw,
135 'data': data,
135 'data': data,
136 'recordsTotal': pull_requests_total_count,
136 'recordsTotal': pull_requests_total_count,
137 'recordsFiltered': pull_requests_total_count,
137 'recordsFiltered': pull_requests_total_count,
138 })
138 })
139 return data
139 return data
140
140
141 @LoginRequired()
141 @LoginRequired()
142 @HasRepoPermissionAnyDecorator(
142 @HasRepoPermissionAnyDecorator(
143 'repository.read', 'repository.write', 'repository.admin')
143 'repository.read', 'repository.write', 'repository.admin')
144 @view_config(
144 @view_config(
145 route_name='pullrequest_show_all', request_method='GET',
145 route_name='pullrequest_show_all', request_method='GET',
146 renderer='rhodecode:templates/pullrequests/pullrequests.mako')
146 renderer='rhodecode:templates/pullrequests/pullrequests.mako')
147 def pull_request_list(self):
147 def pull_request_list(self):
148 c = self.load_default_context()
148 c = self.load_default_context()
149
149
150 req_get = self.request.GET
150 req_get = self.request.GET
151 c.source = str2bool(req_get.get('source'))
151 c.source = str2bool(req_get.get('source'))
152 c.closed = str2bool(req_get.get('closed'))
152 c.closed = str2bool(req_get.get('closed'))
153 c.my = str2bool(req_get.get('my'))
153 c.my = str2bool(req_get.get('my'))
154 c.awaiting_review = str2bool(req_get.get('awaiting_review'))
154 c.awaiting_review = str2bool(req_get.get('awaiting_review'))
155 c.awaiting_my_review = str2bool(req_get.get('awaiting_my_review'))
155 c.awaiting_my_review = str2bool(req_get.get('awaiting_my_review'))
156
156
157 c.active = 'open'
157 c.active = 'open'
158 if c.my:
158 if c.my:
159 c.active = 'my'
159 c.active = 'my'
160 if c.closed:
160 if c.closed:
161 c.active = 'closed'
161 c.active = 'closed'
162 if c.awaiting_review and not c.source:
162 if c.awaiting_review and not c.source:
163 c.active = 'awaiting'
163 c.active = 'awaiting'
164 if c.source and not c.awaiting_review:
164 if c.source and not c.awaiting_review:
165 c.active = 'source'
165 c.active = 'source'
166 if c.awaiting_my_review:
166 if c.awaiting_my_review:
167 c.active = 'awaiting_my'
167 c.active = 'awaiting_my'
168
168
169 return self._get_template_context(c)
169 return self._get_template_context(c)
170
170
171 @LoginRequired()
171 @LoginRequired()
172 @HasRepoPermissionAnyDecorator(
172 @HasRepoPermissionAnyDecorator(
173 'repository.read', 'repository.write', 'repository.admin')
173 'repository.read', 'repository.write', 'repository.admin')
174 @view_config(
174 @view_config(
175 route_name='pullrequest_show_all_data', request_method='GET',
175 route_name='pullrequest_show_all_data', request_method='GET',
176 renderer='json_ext', xhr=True)
176 renderer='json_ext', xhr=True)
177 def pull_request_list_data(self):
177 def pull_request_list_data(self):
178 self.load_default_context()
178 self.load_default_context()
179
179
180 # additional filters
180 # additional filters
181 req_get = self.request.GET
181 req_get = self.request.GET
182 source = str2bool(req_get.get('source'))
182 source = str2bool(req_get.get('source'))
183 closed = str2bool(req_get.get('closed'))
183 closed = str2bool(req_get.get('closed'))
184 my = str2bool(req_get.get('my'))
184 my = str2bool(req_get.get('my'))
185 awaiting_review = str2bool(req_get.get('awaiting_review'))
185 awaiting_review = str2bool(req_get.get('awaiting_review'))
186 awaiting_my_review = str2bool(req_get.get('awaiting_my_review'))
186 awaiting_my_review = str2bool(req_get.get('awaiting_my_review'))
187
187
188 filter_type = 'awaiting_review' if awaiting_review \
188 filter_type = 'awaiting_review' if awaiting_review \
189 else 'awaiting_my_review' if awaiting_my_review \
189 else 'awaiting_my_review' if awaiting_my_review \
190 else None
190 else None
191
191
192 opened_by = None
192 opened_by = None
193 if my:
193 if my:
194 opened_by = [self._rhodecode_user.user_id]
194 opened_by = [self._rhodecode_user.user_id]
195
195
196 statuses = [PullRequest.STATUS_NEW, PullRequest.STATUS_OPEN]
196 statuses = [PullRequest.STATUS_NEW, PullRequest.STATUS_OPEN]
197 if closed:
197 if closed:
198 statuses = [PullRequest.STATUS_CLOSED]
198 statuses = [PullRequest.STATUS_CLOSED]
199
199
200 data = self._get_pull_requests_list(
200 data = self._get_pull_requests_list(
201 repo_name=self.db_repo_name, source=source,
201 repo_name=self.db_repo_name, source=source,
202 filter_type=filter_type, opened_by=opened_by, statuses=statuses)
202 filter_type=filter_type, opened_by=opened_by, statuses=statuses)
203
203
204 return data
204 return data
205
205
206 def _is_diff_cache_enabled(self, target_repo):
206 def _is_diff_cache_enabled(self, target_repo):
207 caching_enabled = self._get_general_setting(
207 caching_enabled = self._get_general_setting(
208 target_repo, 'rhodecode_diff_cache')
208 target_repo, 'rhodecode_diff_cache')
209 log.debug('Diff caching enabled: %s', caching_enabled)
209 log.debug('Diff caching enabled: %s', caching_enabled)
210 return caching_enabled
210 return caching_enabled
211
211
212 def _get_diffset(self, source_repo_name, source_repo,
212 def _get_diffset(self, source_repo_name, source_repo,
213 source_ref_id, target_ref_id,
213 source_ref_id, target_ref_id,
214 target_commit, source_commit, diff_limit, file_limit,
214 target_commit, source_commit, diff_limit, file_limit,
215 fulldiff, hide_whitespace_changes, diff_context):
215 fulldiff, hide_whitespace_changes, diff_context):
216
216
217 vcs_diff = PullRequestModel().get_diff(
217 vcs_diff = PullRequestModel().get_diff(
218 source_repo, source_ref_id, target_ref_id,
218 source_repo, source_ref_id, target_ref_id,
219 hide_whitespace_changes, diff_context)
219 hide_whitespace_changes, diff_context)
220
220
221 diff_processor = diffs.DiffProcessor(
221 diff_processor = diffs.DiffProcessor(
222 vcs_diff, format='newdiff', diff_limit=diff_limit,
222 vcs_diff, format='newdiff', diff_limit=diff_limit,
223 file_limit=file_limit, show_full_diff=fulldiff)
223 file_limit=file_limit, show_full_diff=fulldiff)
224
224
225 _parsed = diff_processor.prepare()
225 _parsed = diff_processor.prepare()
226
226
227 diffset = codeblocks.DiffSet(
227 diffset = codeblocks.DiffSet(
228 repo_name=self.db_repo_name,
228 repo_name=self.db_repo_name,
229 source_repo_name=source_repo_name,
229 source_repo_name=source_repo_name,
230 source_node_getter=codeblocks.diffset_node_getter(target_commit),
230 source_node_getter=codeblocks.diffset_node_getter(target_commit),
231 target_node_getter=codeblocks.diffset_node_getter(source_commit),
231 target_node_getter=codeblocks.diffset_node_getter(source_commit),
232 )
232 )
233 diffset = self.path_filter.render_patchset_filtered(
233 diffset = self.path_filter.render_patchset_filtered(
234 diffset, _parsed, target_commit.raw_id, source_commit.raw_id)
234 diffset, _parsed, target_commit.raw_id, source_commit.raw_id)
235
235
236 return diffset
236 return diffset
237
237
238 def _get_range_diffset(self, source_scm, source_repo,
238 def _get_range_diffset(self, source_scm, source_repo,
239 commit1, commit2, diff_limit, file_limit,
239 commit1, commit2, diff_limit, file_limit,
240 fulldiff, hide_whitespace_changes, diff_context):
240 fulldiff, hide_whitespace_changes, diff_context):
241 vcs_diff = source_scm.get_diff(
241 vcs_diff = source_scm.get_diff(
242 commit1, commit2,
242 commit1, commit2,
243 ignore_whitespace=hide_whitespace_changes,
243 ignore_whitespace=hide_whitespace_changes,
244 context=diff_context)
244 context=diff_context)
245
245
246 diff_processor = diffs.DiffProcessor(
246 diff_processor = diffs.DiffProcessor(
247 vcs_diff, format='newdiff', diff_limit=diff_limit,
247 vcs_diff, format='newdiff', diff_limit=diff_limit,
248 file_limit=file_limit, show_full_diff=fulldiff)
248 file_limit=file_limit, show_full_diff=fulldiff)
249
249
250 _parsed = diff_processor.prepare()
250 _parsed = diff_processor.prepare()
251
251
252 diffset = codeblocks.DiffSet(
252 diffset = codeblocks.DiffSet(
253 repo_name=source_repo.repo_name,
253 repo_name=source_repo.repo_name,
254 source_node_getter=codeblocks.diffset_node_getter(commit1),
254 source_node_getter=codeblocks.diffset_node_getter(commit1),
255 target_node_getter=codeblocks.diffset_node_getter(commit2))
255 target_node_getter=codeblocks.diffset_node_getter(commit2))
256
256
257 diffset = self.path_filter.render_patchset_filtered(
257 diffset = self.path_filter.render_patchset_filtered(
258 diffset, _parsed, commit1.raw_id, commit2.raw_id)
258 diffset, _parsed, commit1.raw_id, commit2.raw_id)
259
259
260 return diffset
260 return diffset
261
261
262 @LoginRequired()
262 @LoginRequired()
263 @HasRepoPermissionAnyDecorator(
263 @HasRepoPermissionAnyDecorator(
264 'repository.read', 'repository.write', 'repository.admin')
264 'repository.read', 'repository.write', 'repository.admin')
265 @view_config(
265 @view_config(
266 route_name='pullrequest_show', request_method='GET',
266 route_name='pullrequest_show', request_method='GET',
267 renderer='rhodecode:templates/pullrequests/pullrequest_show.mako')
267 renderer='rhodecode:templates/pullrequests/pullrequest_show.mako')
268 def pull_request_show(self):
268 def pull_request_show(self):
269 _ = self.request.translate
269 _ = self.request.translate
270 c = self.load_default_context()
270 c = self.load_default_context()
271
271
272 pull_request = PullRequest.get_or_404(
272 pull_request = PullRequest.get_or_404(
273 self.request.matchdict['pull_request_id'])
273 self.request.matchdict['pull_request_id'])
274 pull_request_id = pull_request.pull_request_id
274 pull_request_id = pull_request.pull_request_id
275
275
276 c.state_progressing = pull_request.is_state_changing()
276 c.state_progressing = pull_request.is_state_changing()
277
277
278 _new_state = {
279 'created': PullRequest.STATE_CREATED,
280 }.get(self.request.GET.get('force_state'))
281 if c.is_super_admin and _new_state:
282 with pull_request.set_state(PullRequest.STATE_UPDATING, final_state=_new_state):
283 h.flash(
284 _('Pull Request state was force changed to `{}`').format(_new_state),
285 category='success')
286 Session().commit()
287
288 raise HTTPFound(h.route_path(
289 'pullrequest_show', repo_name=self.db_repo_name,
290 pull_request_id=pull_request_id))
291
278 version = self.request.GET.get('version')
292 version = self.request.GET.get('version')
279 from_version = self.request.GET.get('from_version') or version
293 from_version = self.request.GET.get('from_version') or version
280 merge_checks = self.request.GET.get('merge_checks')
294 merge_checks = self.request.GET.get('merge_checks')
281 c.fulldiff = str2bool(self.request.GET.get('fulldiff'))
295 c.fulldiff = str2bool(self.request.GET.get('fulldiff'))
282
296
283 # fetch global flags of ignore ws or context lines
297 # fetch global flags of ignore ws or context lines
284 diff_context = diffs.get_diff_context(self.request)
298 diff_context = diffs.get_diff_context(self.request)
285 hide_whitespace_changes = diffs.get_diff_whitespace_flag(self.request)
299 hide_whitespace_changes = diffs.get_diff_whitespace_flag(self.request)
286
300
287 force_refresh = str2bool(self.request.GET.get('force_refresh'))
301 force_refresh = str2bool(self.request.GET.get('force_refresh'))
288
302
289 (pull_request_latest,
303 (pull_request_latest,
290 pull_request_at_ver,
304 pull_request_at_ver,
291 pull_request_display_obj,
305 pull_request_display_obj,
292 at_version) = PullRequestModel().get_pr_version(
306 at_version) = PullRequestModel().get_pr_version(
293 pull_request_id, version=version)
307 pull_request_id, version=version)
294 pr_closed = pull_request_latest.is_closed()
308 pr_closed = pull_request_latest.is_closed()
295
309
296 if pr_closed and (version or from_version):
310 if pr_closed and (version or from_version):
297 # not allow to browse versions
311 # not allow to browse versions
298 raise HTTPFound(h.route_path(
312 raise HTTPFound(h.route_path(
299 'pullrequest_show', repo_name=self.db_repo_name,
313 'pullrequest_show', repo_name=self.db_repo_name,
300 pull_request_id=pull_request_id))
314 pull_request_id=pull_request_id))
301
315
302 versions = pull_request_display_obj.versions()
316 versions = pull_request_display_obj.versions()
303 # used to store per-commit range diffs
317 # used to store per-commit range diffs
304 c.changes = collections.OrderedDict()
318 c.changes = collections.OrderedDict()
305 c.range_diff_on = self.request.GET.get('range-diff') == "1"
319 c.range_diff_on = self.request.GET.get('range-diff') == "1"
306
320
307 c.at_version = at_version
321 c.at_version = at_version
308 c.at_version_num = (at_version
322 c.at_version_num = (at_version
309 if at_version and at_version != 'latest'
323 if at_version and at_version != 'latest'
310 else None)
324 else None)
311 c.at_version_pos = ChangesetComment.get_index_from_version(
325 c.at_version_pos = ChangesetComment.get_index_from_version(
312 c.at_version_num, versions)
326 c.at_version_num, versions)
313
327
314 (prev_pull_request_latest,
328 (prev_pull_request_latest,
315 prev_pull_request_at_ver,
329 prev_pull_request_at_ver,
316 prev_pull_request_display_obj,
330 prev_pull_request_display_obj,
317 prev_at_version) = PullRequestModel().get_pr_version(
331 prev_at_version) = PullRequestModel().get_pr_version(
318 pull_request_id, version=from_version)
332 pull_request_id, version=from_version)
319
333
320 c.from_version = prev_at_version
334 c.from_version = prev_at_version
321 c.from_version_num = (prev_at_version
335 c.from_version_num = (prev_at_version
322 if prev_at_version and prev_at_version != 'latest'
336 if prev_at_version and prev_at_version != 'latest'
323 else None)
337 else None)
324 c.from_version_pos = ChangesetComment.get_index_from_version(
338 c.from_version_pos = ChangesetComment.get_index_from_version(
325 c.from_version_num, versions)
339 c.from_version_num, versions)
326
340
327 # define if we're in COMPARE mode or VIEW at version mode
341 # define if we're in COMPARE mode or VIEW at version mode
328 compare = at_version != prev_at_version
342 compare = at_version != prev_at_version
329
343
330 # pull_requests repo_name we opened it against
344 # pull_requests repo_name we opened it against
331 # ie. target_repo must match
345 # ie. target_repo must match
332 if self.db_repo_name != pull_request_at_ver.target_repo.repo_name:
346 if self.db_repo_name != pull_request_at_ver.target_repo.repo_name:
333 raise HTTPNotFound()
347 raise HTTPNotFound()
334
348
335 c.shadow_clone_url = PullRequestModel().get_shadow_clone_url(
349 c.shadow_clone_url = PullRequestModel().get_shadow_clone_url(
336 pull_request_at_ver)
350 pull_request_at_ver)
337
351
338 c.pull_request = pull_request_display_obj
352 c.pull_request = pull_request_display_obj
339 c.renderer = pull_request_at_ver.description_renderer or c.renderer
353 c.renderer = pull_request_at_ver.description_renderer or c.renderer
340 c.pull_request_latest = pull_request_latest
354 c.pull_request_latest = pull_request_latest
341
355
342 if compare or (at_version and not at_version == 'latest'):
356 if compare or (at_version and not at_version == 'latest'):
343 c.allowed_to_change_status = False
357 c.allowed_to_change_status = False
344 c.allowed_to_update = False
358 c.allowed_to_update = False
345 c.allowed_to_merge = False
359 c.allowed_to_merge = False
346 c.allowed_to_delete = False
360 c.allowed_to_delete = False
347 c.allowed_to_comment = False
361 c.allowed_to_comment = False
348 c.allowed_to_close = False
362 c.allowed_to_close = False
349 else:
363 else:
350 can_change_status = PullRequestModel().check_user_change_status(
364 can_change_status = PullRequestModel().check_user_change_status(
351 pull_request_at_ver, self._rhodecode_user)
365 pull_request_at_ver, self._rhodecode_user)
352 c.allowed_to_change_status = can_change_status and not pr_closed
366 c.allowed_to_change_status = can_change_status and not pr_closed
353
367
354 c.allowed_to_update = PullRequestModel().check_user_update(
368 c.allowed_to_update = PullRequestModel().check_user_update(
355 pull_request_latest, self._rhodecode_user) and not pr_closed
369 pull_request_latest, self._rhodecode_user) and not pr_closed
356 c.allowed_to_merge = PullRequestModel().check_user_merge(
370 c.allowed_to_merge = PullRequestModel().check_user_merge(
357 pull_request_latest, self._rhodecode_user) and not pr_closed
371 pull_request_latest, self._rhodecode_user) and not pr_closed
358 c.allowed_to_delete = PullRequestModel().check_user_delete(
372 c.allowed_to_delete = PullRequestModel().check_user_delete(
359 pull_request_latest, self._rhodecode_user) and not pr_closed
373 pull_request_latest, self._rhodecode_user) and not pr_closed
360 c.allowed_to_comment = not pr_closed
374 c.allowed_to_comment = not pr_closed
361 c.allowed_to_close = c.allowed_to_merge and not pr_closed
375 c.allowed_to_close = c.allowed_to_merge and not pr_closed
362
376
363 c.forbid_adding_reviewers = False
377 c.forbid_adding_reviewers = False
364 c.forbid_author_to_review = False
378 c.forbid_author_to_review = False
365 c.forbid_commit_author_to_review = False
379 c.forbid_commit_author_to_review = False
366
380
367 if pull_request_latest.reviewer_data and \
381 if pull_request_latest.reviewer_data and \
368 'rules' in pull_request_latest.reviewer_data:
382 'rules' in pull_request_latest.reviewer_data:
369 rules = pull_request_latest.reviewer_data['rules'] or {}
383 rules = pull_request_latest.reviewer_data['rules'] or {}
370 try:
384 try:
371 c.forbid_adding_reviewers = rules.get(
385 c.forbid_adding_reviewers = rules.get(
372 'forbid_adding_reviewers')
386 'forbid_adding_reviewers')
373 c.forbid_author_to_review = rules.get(
387 c.forbid_author_to_review = rules.get(
374 'forbid_author_to_review')
388 'forbid_author_to_review')
375 c.forbid_commit_author_to_review = rules.get(
389 c.forbid_commit_author_to_review = rules.get(
376 'forbid_commit_author_to_review')
390 'forbid_commit_author_to_review')
377 except Exception:
391 except Exception:
378 pass
392 pass
379
393
380 # check merge capabilities
394 # check merge capabilities
381 _merge_check = MergeCheck.validate(
395 _merge_check = MergeCheck.validate(
382 pull_request_latest, auth_user=self._rhodecode_user,
396 pull_request_latest, auth_user=self._rhodecode_user,
383 translator=self.request.translate,
397 translator=self.request.translate,
384 force_shadow_repo_refresh=force_refresh)
398 force_shadow_repo_refresh=force_refresh)
385 c.pr_merge_errors = _merge_check.error_details
399 c.pr_merge_errors = _merge_check.error_details
386 c.pr_merge_possible = not _merge_check.failed
400 c.pr_merge_possible = not _merge_check.failed
387 c.pr_merge_message = _merge_check.merge_msg
401 c.pr_merge_message = _merge_check.merge_msg
388
402
389 c.pr_merge_info = MergeCheck.get_merge_conditions(
403 c.pr_merge_info = MergeCheck.get_merge_conditions(
390 pull_request_latest, translator=self.request.translate)
404 pull_request_latest, translator=self.request.translate)
391
405
392 c.pull_request_review_status = _merge_check.review_status
406 c.pull_request_review_status = _merge_check.review_status
393 if merge_checks:
407 if merge_checks:
394 self.request.override_renderer = \
408 self.request.override_renderer = \
395 'rhodecode:templates/pullrequests/pullrequest_merge_checks.mako'
409 'rhodecode:templates/pullrequests/pullrequest_merge_checks.mako'
396 return self._get_template_context(c)
410 return self._get_template_context(c)
397
411
398 comments_model = CommentsModel()
412 comments_model = CommentsModel()
399
413
400 # reviewers and statuses
414 # reviewers and statuses
401 c.pull_request_reviewers = pull_request_at_ver.reviewers_statuses()
415 c.pull_request_reviewers = pull_request_at_ver.reviewers_statuses()
402 allowed_reviewers = [x[0].user_id for x in c.pull_request_reviewers]
416 allowed_reviewers = [x[0].user_id for x in c.pull_request_reviewers]
403
417
404 # GENERAL COMMENTS with versions #
418 # GENERAL COMMENTS with versions #
405 q = comments_model._all_general_comments_of_pull_request(pull_request_latest)
419 q = comments_model._all_general_comments_of_pull_request(pull_request_latest)
406 q = q.order_by(ChangesetComment.comment_id.asc())
420 q = q.order_by(ChangesetComment.comment_id.asc())
407 general_comments = q
421 general_comments = q
408
422
409 # pick comments we want to render at current version
423 # pick comments we want to render at current version
410 c.comment_versions = comments_model.aggregate_comments(
424 c.comment_versions = comments_model.aggregate_comments(
411 general_comments, versions, c.at_version_num)
425 general_comments, versions, c.at_version_num)
412 c.comments = c.comment_versions[c.at_version_num]['until']
426 c.comments = c.comment_versions[c.at_version_num]['until']
413
427
414 # INLINE COMMENTS with versions #
428 # INLINE COMMENTS with versions #
415 q = comments_model._all_inline_comments_of_pull_request(pull_request_latest)
429 q = comments_model._all_inline_comments_of_pull_request(pull_request_latest)
416 q = q.order_by(ChangesetComment.comment_id.asc())
430 q = q.order_by(ChangesetComment.comment_id.asc())
417 inline_comments = q
431 inline_comments = q
418
432
419 c.inline_versions = comments_model.aggregate_comments(
433 c.inline_versions = comments_model.aggregate_comments(
420 inline_comments, versions, c.at_version_num, inline=True)
434 inline_comments, versions, c.at_version_num, inline=True)
421
435
422 # TODOs
436 # TODOs
423 c.unresolved_comments = CommentsModel() \
437 c.unresolved_comments = CommentsModel() \
424 .get_pull_request_unresolved_todos(pull_request)
438 .get_pull_request_unresolved_todos(pull_request)
425 c.resolved_comments = CommentsModel() \
439 c.resolved_comments = CommentsModel() \
426 .get_pull_request_resolved_todos(pull_request)
440 .get_pull_request_resolved_todos(pull_request)
427
441
428 # inject latest version
442 # inject latest version
429 latest_ver = PullRequest.get_pr_display_object(
443 latest_ver = PullRequest.get_pr_display_object(
430 pull_request_latest, pull_request_latest)
444 pull_request_latest, pull_request_latest)
431
445
432 c.versions = versions + [latest_ver]
446 c.versions = versions + [latest_ver]
433
447
434 # if we use version, then do not show later comments
448 # if we use version, then do not show later comments
435 # than current version
449 # than current version
436 display_inline_comments = collections.defaultdict(
450 display_inline_comments = collections.defaultdict(
437 lambda: collections.defaultdict(list))
451 lambda: collections.defaultdict(list))
438 for co in inline_comments:
452 for co in inline_comments:
439 if c.at_version_num:
453 if c.at_version_num:
440 # pick comments that are at least UPTO given version, so we
454 # pick comments that are at least UPTO given version, so we
441 # don't render comments for higher version
455 # don't render comments for higher version
442 should_render = co.pull_request_version_id and \
456 should_render = co.pull_request_version_id and \
443 co.pull_request_version_id <= c.at_version_num
457 co.pull_request_version_id <= c.at_version_num
444 else:
458 else:
445 # showing all, for 'latest'
459 # showing all, for 'latest'
446 should_render = True
460 should_render = True
447
461
448 if should_render:
462 if should_render:
449 display_inline_comments[co.f_path][co.line_no].append(co)
463 display_inline_comments[co.f_path][co.line_no].append(co)
450
464
451 # load diff data into template context, if we use compare mode then
465 # load diff data into template context, if we use compare mode then
452 # diff is calculated based on changes between versions of PR
466 # diff is calculated based on changes between versions of PR
453
467
454 source_repo = pull_request_at_ver.source_repo
468 source_repo = pull_request_at_ver.source_repo
455 source_ref_id = pull_request_at_ver.source_ref_parts.commit_id
469 source_ref_id = pull_request_at_ver.source_ref_parts.commit_id
456
470
457 target_repo = pull_request_at_ver.target_repo
471 target_repo = pull_request_at_ver.target_repo
458 target_ref_id = pull_request_at_ver.target_ref_parts.commit_id
472 target_ref_id = pull_request_at_ver.target_ref_parts.commit_id
459
473
460 if compare:
474 if compare:
461 # in compare switch the diff base to latest commit from prev version
475 # in compare switch the diff base to latest commit from prev version
462 target_ref_id = prev_pull_request_display_obj.revisions[0]
476 target_ref_id = prev_pull_request_display_obj.revisions[0]
463
477
464 # despite opening commits for bookmarks/branches/tags, we always
478 # despite opening commits for bookmarks/branches/tags, we always
465 # convert this to rev to prevent changes after bookmark or branch change
479 # convert this to rev to prevent changes after bookmark or branch change
466 c.source_ref_type = 'rev'
480 c.source_ref_type = 'rev'
467 c.source_ref = source_ref_id
481 c.source_ref = source_ref_id
468
482
469 c.target_ref_type = 'rev'
483 c.target_ref_type = 'rev'
470 c.target_ref = target_ref_id
484 c.target_ref = target_ref_id
471
485
472 c.source_repo = source_repo
486 c.source_repo = source_repo
473 c.target_repo = target_repo
487 c.target_repo = target_repo
474
488
475 c.commit_ranges = []
489 c.commit_ranges = []
476 source_commit = EmptyCommit()
490 source_commit = EmptyCommit()
477 target_commit = EmptyCommit()
491 target_commit = EmptyCommit()
478 c.missing_requirements = False
492 c.missing_requirements = False
479
493
480 source_scm = source_repo.scm_instance()
494 source_scm = source_repo.scm_instance()
481 target_scm = target_repo.scm_instance()
495 target_scm = target_repo.scm_instance()
482
496
483 shadow_scm = None
497 shadow_scm = None
484 try:
498 try:
485 shadow_scm = pull_request_latest.get_shadow_repo()
499 shadow_scm = pull_request_latest.get_shadow_repo()
486 except Exception:
500 except Exception:
487 log.debug('Failed to get shadow repo', exc_info=True)
501 log.debug('Failed to get shadow repo', exc_info=True)
488 # try first the existing source_repo, and then shadow
502 # try first the existing source_repo, and then shadow
489 # repo if we can obtain one
503 # repo if we can obtain one
490 commits_source_repo = source_scm
504 commits_source_repo = source_scm
491 if shadow_scm:
505 if shadow_scm:
492 commits_source_repo = shadow_scm
506 commits_source_repo = shadow_scm
493
507
494 c.commits_source_repo = commits_source_repo
508 c.commits_source_repo = commits_source_repo
495 c.ancestor = None # set it to None, to hide it from PR view
509 c.ancestor = None # set it to None, to hide it from PR view
496
510
497 # empty version means latest, so we keep this to prevent
511 # empty version means latest, so we keep this to prevent
498 # double caching
512 # double caching
499 version_normalized = version or 'latest'
513 version_normalized = version or 'latest'
500 from_version_normalized = from_version or 'latest'
514 from_version_normalized = from_version or 'latest'
501
515
502 cache_path = self.rhodecode_vcs_repo.get_create_shadow_cache_pr_path(target_repo)
516 cache_path = self.rhodecode_vcs_repo.get_create_shadow_cache_pr_path(target_repo)
503 cache_file_path = diff_cache_exist(
517 cache_file_path = diff_cache_exist(
504 cache_path, 'pull_request', pull_request_id, version_normalized,
518 cache_path, 'pull_request', pull_request_id, version_normalized,
505 from_version_normalized, source_ref_id, target_ref_id,
519 from_version_normalized, source_ref_id, target_ref_id,
506 hide_whitespace_changes, diff_context, c.fulldiff)
520 hide_whitespace_changes, diff_context, c.fulldiff)
507
521
508 caching_enabled = self._is_diff_cache_enabled(c.target_repo)
522 caching_enabled = self._is_diff_cache_enabled(c.target_repo)
509 force_recache = self.get_recache_flag()
523 force_recache = self.get_recache_flag()
510
524
511 cached_diff = None
525 cached_diff = None
512 if caching_enabled:
526 if caching_enabled:
513 cached_diff = load_cached_diff(cache_file_path)
527 cached_diff = load_cached_diff(cache_file_path)
514
528
515 has_proper_commit_cache = (
529 has_proper_commit_cache = (
516 cached_diff and cached_diff.get('commits')
530 cached_diff and cached_diff.get('commits')
517 and len(cached_diff.get('commits', [])) == 5
531 and len(cached_diff.get('commits', [])) == 5
518 and cached_diff.get('commits')[0]
532 and cached_diff.get('commits')[0]
519 and cached_diff.get('commits')[3])
533 and cached_diff.get('commits')[3])
520
534
521 if not force_recache and not c.range_diff_on and has_proper_commit_cache:
535 if not force_recache and not c.range_diff_on and has_proper_commit_cache:
522 diff_commit_cache = \
536 diff_commit_cache = \
523 (ancestor_commit, commit_cache, missing_requirements,
537 (ancestor_commit, commit_cache, missing_requirements,
524 source_commit, target_commit) = cached_diff['commits']
538 source_commit, target_commit) = cached_diff['commits']
525 else:
539 else:
526 diff_commit_cache = \
540 diff_commit_cache = \
527 (ancestor_commit, commit_cache, missing_requirements,
541 (ancestor_commit, commit_cache, missing_requirements,
528 source_commit, target_commit) = self.get_commits(
542 source_commit, target_commit) = self.get_commits(
529 commits_source_repo,
543 commits_source_repo,
530 pull_request_at_ver,
544 pull_request_at_ver,
531 source_commit,
545 source_commit,
532 source_ref_id,
546 source_ref_id,
533 source_scm,
547 source_scm,
534 target_commit,
548 target_commit,
535 target_ref_id,
549 target_ref_id,
536 target_scm)
550 target_scm)
537
551
538 # register our commit range
552 # register our commit range
539 for comm in commit_cache.values():
553 for comm in commit_cache.values():
540 c.commit_ranges.append(comm)
554 c.commit_ranges.append(comm)
541
555
542 c.missing_requirements = missing_requirements
556 c.missing_requirements = missing_requirements
543 c.ancestor_commit = ancestor_commit
557 c.ancestor_commit = ancestor_commit
544 c.statuses = source_repo.statuses(
558 c.statuses = source_repo.statuses(
545 [x.raw_id for x in c.commit_ranges])
559 [x.raw_id for x in c.commit_ranges])
546
560
547 # auto collapse if we have more than limit
561 # auto collapse if we have more than limit
548 collapse_limit = diffs.DiffProcessor._collapse_commits_over
562 collapse_limit = diffs.DiffProcessor._collapse_commits_over
549 c.collapse_all_commits = len(c.commit_ranges) > collapse_limit
563 c.collapse_all_commits = len(c.commit_ranges) > collapse_limit
550 c.compare_mode = compare
564 c.compare_mode = compare
551
565
552 # diff_limit is the old behavior, will cut off the whole diff
566 # diff_limit is the old behavior, will cut off the whole diff
553 # if the limit is applied otherwise will just hide the
567 # if the limit is applied otherwise will just hide the
554 # big files from the front-end
568 # big files from the front-end
555 diff_limit = c.visual.cut_off_limit_diff
569 diff_limit = c.visual.cut_off_limit_diff
556 file_limit = c.visual.cut_off_limit_file
570 file_limit = c.visual.cut_off_limit_file
557
571
558 c.missing_commits = False
572 c.missing_commits = False
559 if (c.missing_requirements
573 if (c.missing_requirements
560 or isinstance(source_commit, EmptyCommit)
574 or isinstance(source_commit, EmptyCommit)
561 or source_commit == target_commit):
575 or source_commit == target_commit):
562
576
563 c.missing_commits = True
577 c.missing_commits = True
564 else:
578 else:
565 c.inline_comments = display_inline_comments
579 c.inline_comments = display_inline_comments
566
580
567 has_proper_diff_cache = cached_diff and cached_diff.get('commits')
581 has_proper_diff_cache = cached_diff and cached_diff.get('commits')
568 if not force_recache and has_proper_diff_cache:
582 if not force_recache and has_proper_diff_cache:
569 c.diffset = cached_diff['diff']
583 c.diffset = cached_diff['diff']
570 (ancestor_commit, commit_cache, missing_requirements,
584 (ancestor_commit, commit_cache, missing_requirements,
571 source_commit, target_commit) = cached_diff['commits']
585 source_commit, target_commit) = cached_diff['commits']
572 else:
586 else:
573 c.diffset = self._get_diffset(
587 c.diffset = self._get_diffset(
574 c.source_repo.repo_name, commits_source_repo,
588 c.source_repo.repo_name, commits_source_repo,
575 source_ref_id, target_ref_id,
589 source_ref_id, target_ref_id,
576 target_commit, source_commit,
590 target_commit, source_commit,
577 diff_limit, file_limit, c.fulldiff,
591 diff_limit, file_limit, c.fulldiff,
578 hide_whitespace_changes, diff_context)
592 hide_whitespace_changes, diff_context)
579
593
580 # save cached diff
594 # save cached diff
581 if caching_enabled:
595 if caching_enabled:
582 cache_diff(cache_file_path, c.diffset, diff_commit_cache)
596 cache_diff(cache_file_path, c.diffset, diff_commit_cache)
583
597
584 c.limited_diff = c.diffset.limited_diff
598 c.limited_diff = c.diffset.limited_diff
585
599
586 # calculate removed files that are bound to comments
600 # calculate removed files that are bound to comments
587 comment_deleted_files = [
601 comment_deleted_files = [
588 fname for fname in display_inline_comments
602 fname for fname in display_inline_comments
589 if fname not in c.diffset.file_stats]
603 if fname not in c.diffset.file_stats]
590
604
591 c.deleted_files_comments = collections.defaultdict(dict)
605 c.deleted_files_comments = collections.defaultdict(dict)
592 for fname, per_line_comments in display_inline_comments.items():
606 for fname, per_line_comments in display_inline_comments.items():
593 if fname in comment_deleted_files:
607 if fname in comment_deleted_files:
594 c.deleted_files_comments[fname]['stats'] = 0
608 c.deleted_files_comments[fname]['stats'] = 0
595 c.deleted_files_comments[fname]['comments'] = list()
609 c.deleted_files_comments[fname]['comments'] = list()
596 for lno, comments in per_line_comments.items():
610 for lno, comments in per_line_comments.items():
597 c.deleted_files_comments[fname]['comments'].extend(comments)
611 c.deleted_files_comments[fname]['comments'].extend(comments)
598
612
599 # maybe calculate the range diff
613 # maybe calculate the range diff
600 if c.range_diff_on:
614 if c.range_diff_on:
601 # TODO(marcink): set whitespace/context
615 # TODO(marcink): set whitespace/context
602 context_lcl = 3
616 context_lcl = 3
603 ign_whitespace_lcl = False
617 ign_whitespace_lcl = False
604
618
605 for commit in c.commit_ranges:
619 for commit in c.commit_ranges:
606 commit2 = commit
620 commit2 = commit
607 commit1 = commit.first_parent
621 commit1 = commit.first_parent
608
622
609 range_diff_cache_file_path = diff_cache_exist(
623 range_diff_cache_file_path = diff_cache_exist(
610 cache_path, 'diff', commit.raw_id,
624 cache_path, 'diff', commit.raw_id,
611 ign_whitespace_lcl, context_lcl, c.fulldiff)
625 ign_whitespace_lcl, context_lcl, c.fulldiff)
612
626
613 cached_diff = None
627 cached_diff = None
614 if caching_enabled:
628 if caching_enabled:
615 cached_diff = load_cached_diff(range_diff_cache_file_path)
629 cached_diff = load_cached_diff(range_diff_cache_file_path)
616
630
617 has_proper_diff_cache = cached_diff and cached_diff.get('diff')
631 has_proper_diff_cache = cached_diff and cached_diff.get('diff')
618 if not force_recache and has_proper_diff_cache:
632 if not force_recache and has_proper_diff_cache:
619 diffset = cached_diff['diff']
633 diffset = cached_diff['diff']
620 else:
634 else:
621 diffset = self._get_range_diffset(
635 diffset = self._get_range_diffset(
622 commits_source_repo, source_repo,
636 commits_source_repo, source_repo,
623 commit1, commit2, diff_limit, file_limit,
637 commit1, commit2, diff_limit, file_limit,
624 c.fulldiff, ign_whitespace_lcl, context_lcl
638 c.fulldiff, ign_whitespace_lcl, context_lcl
625 )
639 )
626
640
627 # save cached diff
641 # save cached diff
628 if caching_enabled:
642 if caching_enabled:
629 cache_diff(range_diff_cache_file_path, diffset, None)
643 cache_diff(range_diff_cache_file_path, diffset, None)
630
644
631 c.changes[commit.raw_id] = diffset
645 c.changes[commit.raw_id] = diffset
632
646
633 # this is a hack to properly display links, when creating PR, the
647 # this is a hack to properly display links, when creating PR, the
634 # compare view and others uses different notation, and
648 # compare view and others uses different notation, and
635 # compare_commits.mako renders links based on the target_repo.
649 # compare_commits.mako renders links based on the target_repo.
636 # We need to swap that here to generate it properly on the html side
650 # We need to swap that here to generate it properly on the html side
637 c.target_repo = c.source_repo
651 c.target_repo = c.source_repo
638
652
639 c.commit_statuses = ChangesetStatus.STATUSES
653 c.commit_statuses = ChangesetStatus.STATUSES
640
654
641 c.show_version_changes = not pr_closed
655 c.show_version_changes = not pr_closed
642 if c.show_version_changes:
656 if c.show_version_changes:
643 cur_obj = pull_request_at_ver
657 cur_obj = pull_request_at_ver
644 prev_obj = prev_pull_request_at_ver
658 prev_obj = prev_pull_request_at_ver
645
659
646 old_commit_ids = prev_obj.revisions
660 old_commit_ids = prev_obj.revisions
647 new_commit_ids = cur_obj.revisions
661 new_commit_ids = cur_obj.revisions
648 commit_changes = PullRequestModel()._calculate_commit_id_changes(
662 commit_changes = PullRequestModel()._calculate_commit_id_changes(
649 old_commit_ids, new_commit_ids)
663 old_commit_ids, new_commit_ids)
650 c.commit_changes_summary = commit_changes
664 c.commit_changes_summary = commit_changes
651
665
652 # calculate the diff for commits between versions
666 # calculate the diff for commits between versions
653 c.commit_changes = []
667 c.commit_changes = []
654 mark = lambda cs, fw: list(
668 mark = lambda cs, fw: list(
655 h.itertools.izip_longest([], cs, fillvalue=fw))
669 h.itertools.izip_longest([], cs, fillvalue=fw))
656 for c_type, raw_id in mark(commit_changes.added, 'a') \
670 for c_type, raw_id in mark(commit_changes.added, 'a') \
657 + mark(commit_changes.removed, 'r') \
671 + mark(commit_changes.removed, 'r') \
658 + mark(commit_changes.common, 'c'):
672 + mark(commit_changes.common, 'c'):
659
673
660 if raw_id in commit_cache:
674 if raw_id in commit_cache:
661 commit = commit_cache[raw_id]
675 commit = commit_cache[raw_id]
662 else:
676 else:
663 try:
677 try:
664 commit = commits_source_repo.get_commit(raw_id)
678 commit = commits_source_repo.get_commit(raw_id)
665 except CommitDoesNotExistError:
679 except CommitDoesNotExistError:
666 # in case we fail extracting still use "dummy" commit
680 # in case we fail extracting still use "dummy" commit
667 # for display in commit diff
681 # for display in commit diff
668 commit = h.AttributeDict(
682 commit = h.AttributeDict(
669 {'raw_id': raw_id,
683 {'raw_id': raw_id,
670 'message': 'EMPTY or MISSING COMMIT'})
684 'message': 'EMPTY or MISSING COMMIT'})
671 c.commit_changes.append([c_type, commit])
685 c.commit_changes.append([c_type, commit])
672
686
673 # current user review statuses for each version
687 # current user review statuses for each version
674 c.review_versions = {}
688 c.review_versions = {}
675 if self._rhodecode_user.user_id in allowed_reviewers:
689 if self._rhodecode_user.user_id in allowed_reviewers:
676 for co in general_comments:
690 for co in general_comments:
677 if co.author.user_id == self._rhodecode_user.user_id:
691 if co.author.user_id == self._rhodecode_user.user_id:
678 status = co.status_change
692 status = co.status_change
679 if status:
693 if status:
680 _ver_pr = status[0].comment.pull_request_version_id
694 _ver_pr = status[0].comment.pull_request_version_id
681 c.review_versions[_ver_pr] = status[0]
695 c.review_versions[_ver_pr] = status[0]
682
696
683 return self._get_template_context(c)
697 return self._get_template_context(c)
684
698
685 def get_commits(
699 def get_commits(
686 self, commits_source_repo, pull_request_at_ver, source_commit,
700 self, commits_source_repo, pull_request_at_ver, source_commit,
687 source_ref_id, source_scm, target_commit, target_ref_id, target_scm):
701 source_ref_id, source_scm, target_commit, target_ref_id, target_scm):
688 commit_cache = collections.OrderedDict()
702 commit_cache = collections.OrderedDict()
689 missing_requirements = False
703 missing_requirements = False
690 try:
704 try:
691 pre_load = ["author", "date", "message", "branch", "parents"]
705 pre_load = ["author", "date", "message", "branch", "parents"]
692 show_revs = pull_request_at_ver.revisions
706 show_revs = pull_request_at_ver.revisions
693 for rev in show_revs:
707 for rev in show_revs:
694 comm = commits_source_repo.get_commit(
708 comm = commits_source_repo.get_commit(
695 commit_id=rev, pre_load=pre_load)
709 commit_id=rev, pre_load=pre_load)
696 commit_cache[comm.raw_id] = comm
710 commit_cache[comm.raw_id] = comm
697
711
698 # Order here matters, we first need to get target, and then
712 # Order here matters, we first need to get target, and then
699 # the source
713 # the source
700 target_commit = commits_source_repo.get_commit(
714 target_commit = commits_source_repo.get_commit(
701 commit_id=safe_str(target_ref_id))
715 commit_id=safe_str(target_ref_id))
702
716
703 source_commit = commits_source_repo.get_commit(
717 source_commit = commits_source_repo.get_commit(
704 commit_id=safe_str(source_ref_id))
718 commit_id=safe_str(source_ref_id))
705 except CommitDoesNotExistError:
719 except CommitDoesNotExistError:
706 log.warning(
720 log.warning(
707 'Failed to get commit from `{}` repo'.format(
721 'Failed to get commit from `{}` repo'.format(
708 commits_source_repo), exc_info=True)
722 commits_source_repo), exc_info=True)
709 except RepositoryRequirementError:
723 except RepositoryRequirementError:
710 log.warning(
724 log.warning(
711 'Failed to get all required data from repo', exc_info=True)
725 'Failed to get all required data from repo', exc_info=True)
712 missing_requirements = True
726 missing_requirements = True
713 ancestor_commit = None
727 ancestor_commit = None
714 try:
728 try:
715 ancestor_id = source_scm.get_common_ancestor(
729 ancestor_id = source_scm.get_common_ancestor(
716 source_commit.raw_id, target_commit.raw_id, target_scm)
730 source_commit.raw_id, target_commit.raw_id, target_scm)
717 ancestor_commit = source_scm.get_commit(ancestor_id)
731 ancestor_commit = source_scm.get_commit(ancestor_id)
718 except Exception:
732 except Exception:
719 ancestor_commit = None
733 ancestor_commit = None
720 return ancestor_commit, commit_cache, missing_requirements, source_commit, target_commit
734 return ancestor_commit, commit_cache, missing_requirements, source_commit, target_commit
721
735
722 def assure_not_empty_repo(self):
736 def assure_not_empty_repo(self):
723 _ = self.request.translate
737 _ = self.request.translate
724
738
725 try:
739 try:
726 self.db_repo.scm_instance().get_commit()
740 self.db_repo.scm_instance().get_commit()
727 except EmptyRepositoryError:
741 except EmptyRepositoryError:
728 h.flash(h.literal(_('There are no commits yet')),
742 h.flash(h.literal(_('There are no commits yet')),
729 category='warning')
743 category='warning')
730 raise HTTPFound(
744 raise HTTPFound(
731 h.route_path('repo_summary', repo_name=self.db_repo.repo_name))
745 h.route_path('repo_summary', repo_name=self.db_repo.repo_name))
732
746
733 @LoginRequired()
747 @LoginRequired()
734 @NotAnonymous()
748 @NotAnonymous()
735 @HasRepoPermissionAnyDecorator(
749 @HasRepoPermissionAnyDecorator(
736 'repository.read', 'repository.write', 'repository.admin')
750 'repository.read', 'repository.write', 'repository.admin')
737 @view_config(
751 @view_config(
738 route_name='pullrequest_new', request_method='GET',
752 route_name='pullrequest_new', request_method='GET',
739 renderer='rhodecode:templates/pullrequests/pullrequest.mako')
753 renderer='rhodecode:templates/pullrequests/pullrequest.mako')
740 def pull_request_new(self):
754 def pull_request_new(self):
741 _ = self.request.translate
755 _ = self.request.translate
742 c = self.load_default_context()
756 c = self.load_default_context()
743
757
744 self.assure_not_empty_repo()
758 self.assure_not_empty_repo()
745 source_repo = self.db_repo
759 source_repo = self.db_repo
746
760
747 commit_id = self.request.GET.get('commit')
761 commit_id = self.request.GET.get('commit')
748 branch_ref = self.request.GET.get('branch')
762 branch_ref = self.request.GET.get('branch')
749 bookmark_ref = self.request.GET.get('bookmark')
763 bookmark_ref = self.request.GET.get('bookmark')
750
764
751 try:
765 try:
752 source_repo_data = PullRequestModel().generate_repo_data(
766 source_repo_data = PullRequestModel().generate_repo_data(
753 source_repo, commit_id=commit_id,
767 source_repo, commit_id=commit_id,
754 branch=branch_ref, bookmark=bookmark_ref,
768 branch=branch_ref, bookmark=bookmark_ref,
755 translator=self.request.translate)
769 translator=self.request.translate)
756 except CommitDoesNotExistError as e:
770 except CommitDoesNotExistError as e:
757 log.exception(e)
771 log.exception(e)
758 h.flash(_('Commit does not exist'), 'error')
772 h.flash(_('Commit does not exist'), 'error')
759 raise HTTPFound(
773 raise HTTPFound(
760 h.route_path('pullrequest_new', repo_name=source_repo.repo_name))
774 h.route_path('pullrequest_new', repo_name=source_repo.repo_name))
761
775
762 default_target_repo = source_repo
776 default_target_repo = source_repo
763
777
764 if source_repo.parent and c.has_origin_repo_read_perm:
778 if source_repo.parent and c.has_origin_repo_read_perm:
765 parent_vcs_obj = source_repo.parent.scm_instance()
779 parent_vcs_obj = source_repo.parent.scm_instance()
766 if parent_vcs_obj and not parent_vcs_obj.is_empty():
780 if parent_vcs_obj and not parent_vcs_obj.is_empty():
767 # change default if we have a parent repo
781 # change default if we have a parent repo
768 default_target_repo = source_repo.parent
782 default_target_repo = source_repo.parent
769
783
770 target_repo_data = PullRequestModel().generate_repo_data(
784 target_repo_data = PullRequestModel().generate_repo_data(
771 default_target_repo, translator=self.request.translate)
785 default_target_repo, translator=self.request.translate)
772
786
773 selected_source_ref = source_repo_data['refs']['selected_ref']
787 selected_source_ref = source_repo_data['refs']['selected_ref']
774 title_source_ref = ''
788 title_source_ref = ''
775 if selected_source_ref:
789 if selected_source_ref:
776 title_source_ref = selected_source_ref.split(':', 2)[1]
790 title_source_ref = selected_source_ref.split(':', 2)[1]
777 c.default_title = PullRequestModel().generate_pullrequest_title(
791 c.default_title = PullRequestModel().generate_pullrequest_title(
778 source=source_repo.repo_name,
792 source=source_repo.repo_name,
779 source_ref=title_source_ref,
793 source_ref=title_source_ref,
780 target=default_target_repo.repo_name
794 target=default_target_repo.repo_name
781 )
795 )
782
796
783 c.default_repo_data = {
797 c.default_repo_data = {
784 'source_repo_name': source_repo.repo_name,
798 'source_repo_name': source_repo.repo_name,
785 'source_refs_json': json.dumps(source_repo_data),
799 'source_refs_json': json.dumps(source_repo_data),
786 'target_repo_name': default_target_repo.repo_name,
800 'target_repo_name': default_target_repo.repo_name,
787 'target_refs_json': json.dumps(target_repo_data),
801 'target_refs_json': json.dumps(target_repo_data),
788 }
802 }
789 c.default_source_ref = selected_source_ref
803 c.default_source_ref = selected_source_ref
790
804
791 return self._get_template_context(c)
805 return self._get_template_context(c)
792
806
793 @LoginRequired()
807 @LoginRequired()
794 @NotAnonymous()
808 @NotAnonymous()
795 @HasRepoPermissionAnyDecorator(
809 @HasRepoPermissionAnyDecorator(
796 'repository.read', 'repository.write', 'repository.admin')
810 'repository.read', 'repository.write', 'repository.admin')
797 @view_config(
811 @view_config(
798 route_name='pullrequest_repo_refs', request_method='GET',
812 route_name='pullrequest_repo_refs', request_method='GET',
799 renderer='json_ext', xhr=True)
813 renderer='json_ext', xhr=True)
800 def pull_request_repo_refs(self):
814 def pull_request_repo_refs(self):
801 self.load_default_context()
815 self.load_default_context()
802 target_repo_name = self.request.matchdict['target_repo_name']
816 target_repo_name = self.request.matchdict['target_repo_name']
803 repo = Repository.get_by_repo_name(target_repo_name)
817 repo = Repository.get_by_repo_name(target_repo_name)
804 if not repo:
818 if not repo:
805 raise HTTPNotFound()
819 raise HTTPNotFound()
806
820
807 target_perm = HasRepoPermissionAny(
821 target_perm = HasRepoPermissionAny(
808 'repository.read', 'repository.write', 'repository.admin')(
822 'repository.read', 'repository.write', 'repository.admin')(
809 target_repo_name)
823 target_repo_name)
810 if not target_perm:
824 if not target_perm:
811 raise HTTPNotFound()
825 raise HTTPNotFound()
812
826
813 return PullRequestModel().generate_repo_data(
827 return PullRequestModel().generate_repo_data(
814 repo, translator=self.request.translate)
828 repo, translator=self.request.translate)
815
829
816 @LoginRequired()
830 @LoginRequired()
817 @NotAnonymous()
831 @NotAnonymous()
818 @HasRepoPermissionAnyDecorator(
832 @HasRepoPermissionAnyDecorator(
819 'repository.read', 'repository.write', 'repository.admin')
833 'repository.read', 'repository.write', 'repository.admin')
820 @view_config(
834 @view_config(
821 route_name='pullrequest_repo_targets', request_method='GET',
835 route_name='pullrequest_repo_targets', request_method='GET',
822 renderer='json_ext', xhr=True)
836 renderer='json_ext', xhr=True)
823 def pullrequest_repo_targets(self):
837 def pullrequest_repo_targets(self):
824 _ = self.request.translate
838 _ = self.request.translate
825 filter_query = self.request.GET.get('query')
839 filter_query = self.request.GET.get('query')
826
840
827 # get the parents
841 # get the parents
828 parent_target_repos = []
842 parent_target_repos = []
829 if self.db_repo.parent:
843 if self.db_repo.parent:
830 parents_query = Repository.query() \
844 parents_query = Repository.query() \
831 .order_by(func.length(Repository.repo_name)) \
845 .order_by(func.length(Repository.repo_name)) \
832 .filter(Repository.fork_id == self.db_repo.parent.repo_id)
846 .filter(Repository.fork_id == self.db_repo.parent.repo_id)
833
847
834 if filter_query:
848 if filter_query:
835 ilike_expression = u'%{}%'.format(safe_unicode(filter_query))
849 ilike_expression = u'%{}%'.format(safe_unicode(filter_query))
836 parents_query = parents_query.filter(
850 parents_query = parents_query.filter(
837 Repository.repo_name.ilike(ilike_expression))
851 Repository.repo_name.ilike(ilike_expression))
838 parents = parents_query.limit(20).all()
852 parents = parents_query.limit(20).all()
839
853
840 for parent in parents:
854 for parent in parents:
841 parent_vcs_obj = parent.scm_instance()
855 parent_vcs_obj = parent.scm_instance()
842 if parent_vcs_obj and not parent_vcs_obj.is_empty():
856 if parent_vcs_obj and not parent_vcs_obj.is_empty():
843 parent_target_repos.append(parent)
857 parent_target_repos.append(parent)
844
858
845 # get other forks, and repo itself
859 # get other forks, and repo itself
846 query = Repository.query() \
860 query = Repository.query() \
847 .order_by(func.length(Repository.repo_name)) \
861 .order_by(func.length(Repository.repo_name)) \
848 .filter(
862 .filter(
849 or_(Repository.repo_id == self.db_repo.repo_id, # repo itself
863 or_(Repository.repo_id == self.db_repo.repo_id, # repo itself
850 Repository.fork_id == self.db_repo.repo_id) # forks of this repo
864 Repository.fork_id == self.db_repo.repo_id) # forks of this repo
851 ) \
865 ) \
852 .filter(~Repository.repo_id.in_([x.repo_id for x in parent_target_repos]))
866 .filter(~Repository.repo_id.in_([x.repo_id for x in parent_target_repos]))
853
867
854 if filter_query:
868 if filter_query:
855 ilike_expression = u'%{}%'.format(safe_unicode(filter_query))
869 ilike_expression = u'%{}%'.format(safe_unicode(filter_query))
856 query = query.filter(Repository.repo_name.ilike(ilike_expression))
870 query = query.filter(Repository.repo_name.ilike(ilike_expression))
857
871
858 limit = max(20 - len(parent_target_repos), 5) # not less then 5
872 limit = max(20 - len(parent_target_repos), 5) # not less then 5
859 target_repos = query.limit(limit).all()
873 target_repos = query.limit(limit).all()
860
874
861 all_target_repos = target_repos + parent_target_repos
875 all_target_repos = target_repos + parent_target_repos
862
876
863 repos = []
877 repos = []
864 # This checks permissions to the repositories
878 # This checks permissions to the repositories
865 for obj in ScmModel().get_repos(all_target_repos):
879 for obj in ScmModel().get_repos(all_target_repos):
866 repos.append({
880 repos.append({
867 'id': obj['name'],
881 'id': obj['name'],
868 'text': obj['name'],
882 'text': obj['name'],
869 'type': 'repo',
883 'type': 'repo',
870 'repo_id': obj['dbrepo']['repo_id'],
884 'repo_id': obj['dbrepo']['repo_id'],
871 'repo_type': obj['dbrepo']['repo_type'],
885 'repo_type': obj['dbrepo']['repo_type'],
872 'private': obj['dbrepo']['private'],
886 'private': obj['dbrepo']['private'],
873
887
874 })
888 })
875
889
876 data = {
890 data = {
877 'more': False,
891 'more': False,
878 'results': [{
892 'results': [{
879 'text': _('Repositories'),
893 'text': _('Repositories'),
880 'children': repos
894 'children': repos
881 }] if repos else []
895 }] if repos else []
882 }
896 }
883 return data
897 return data
884
898
885 @LoginRequired()
899 @LoginRequired()
886 @NotAnonymous()
900 @NotAnonymous()
887 @HasRepoPermissionAnyDecorator(
901 @HasRepoPermissionAnyDecorator(
888 'repository.read', 'repository.write', 'repository.admin')
902 'repository.read', 'repository.write', 'repository.admin')
889 @CSRFRequired()
903 @CSRFRequired()
890 @view_config(
904 @view_config(
891 route_name='pullrequest_create', request_method='POST',
905 route_name='pullrequest_create', request_method='POST',
892 renderer=None)
906 renderer=None)
893 def pull_request_create(self):
907 def pull_request_create(self):
894 _ = self.request.translate
908 _ = self.request.translate
895 self.assure_not_empty_repo()
909 self.assure_not_empty_repo()
896 self.load_default_context()
910 self.load_default_context()
897
911
898 controls = peppercorn.parse(self.request.POST.items())
912 controls = peppercorn.parse(self.request.POST.items())
899
913
900 try:
914 try:
901 form = PullRequestForm(
915 form = PullRequestForm(
902 self.request.translate, self.db_repo.repo_id)()
916 self.request.translate, self.db_repo.repo_id)()
903 _form = form.to_python(controls)
917 _form = form.to_python(controls)
904 except formencode.Invalid as errors:
918 except formencode.Invalid as errors:
905 if errors.error_dict.get('revisions'):
919 if errors.error_dict.get('revisions'):
906 msg = 'Revisions: %s' % errors.error_dict['revisions']
920 msg = 'Revisions: %s' % errors.error_dict['revisions']
907 elif errors.error_dict.get('pullrequest_title'):
921 elif errors.error_dict.get('pullrequest_title'):
908 msg = errors.error_dict.get('pullrequest_title')
922 msg = errors.error_dict.get('pullrequest_title')
909 else:
923 else:
910 msg = _('Error creating pull request: {}').format(errors)
924 msg = _('Error creating pull request: {}').format(errors)
911 log.exception(msg)
925 log.exception(msg)
912 h.flash(msg, 'error')
926 h.flash(msg, 'error')
913
927
914 # would rather just go back to form ...
928 # would rather just go back to form ...
915 raise HTTPFound(
929 raise HTTPFound(
916 h.route_path('pullrequest_new', repo_name=self.db_repo_name))
930 h.route_path('pullrequest_new', repo_name=self.db_repo_name))
917
931
918 source_repo = _form['source_repo']
932 source_repo = _form['source_repo']
919 source_ref = _form['source_ref']
933 source_ref = _form['source_ref']
920 target_repo = _form['target_repo']
934 target_repo = _form['target_repo']
921 target_ref = _form['target_ref']
935 target_ref = _form['target_ref']
922 commit_ids = _form['revisions'][::-1]
936 commit_ids = _form['revisions'][::-1]
923
937
924 # find the ancestor for this pr
938 # find the ancestor for this pr
925 source_db_repo = Repository.get_by_repo_name(_form['source_repo'])
939 source_db_repo = Repository.get_by_repo_name(_form['source_repo'])
926 target_db_repo = Repository.get_by_repo_name(_form['target_repo'])
940 target_db_repo = Repository.get_by_repo_name(_form['target_repo'])
927
941
928 if not (source_db_repo or target_db_repo):
942 if not (source_db_repo or target_db_repo):
929 h.flash(_('source_repo or target repo not found'), category='error')
943 h.flash(_('source_repo or target repo not found'), category='error')
930 raise HTTPFound(
944 raise HTTPFound(
931 h.route_path('pullrequest_new', repo_name=self.db_repo_name))
945 h.route_path('pullrequest_new', repo_name=self.db_repo_name))
932
946
933 # re-check permissions again here
947 # re-check permissions again here
934 # source_repo we must have read permissions
948 # source_repo we must have read permissions
935
949
936 source_perm = HasRepoPermissionAny(
950 source_perm = HasRepoPermissionAny(
937 'repository.read', 'repository.write', 'repository.admin')(
951 'repository.read', 'repository.write', 'repository.admin')(
938 source_db_repo.repo_name)
952 source_db_repo.repo_name)
939 if not source_perm:
953 if not source_perm:
940 msg = _('Not Enough permissions to source repo `{}`.'.format(
954 msg = _('Not Enough permissions to source repo `{}`.'.format(
941 source_db_repo.repo_name))
955 source_db_repo.repo_name))
942 h.flash(msg, category='error')
956 h.flash(msg, category='error')
943 # copy the args back to redirect
957 # copy the args back to redirect
944 org_query = self.request.GET.mixed()
958 org_query = self.request.GET.mixed()
945 raise HTTPFound(
959 raise HTTPFound(
946 h.route_path('pullrequest_new', repo_name=self.db_repo_name,
960 h.route_path('pullrequest_new', repo_name=self.db_repo_name,
947 _query=org_query))
961 _query=org_query))
948
962
949 # target repo we must have read permissions, and also later on
963 # target repo we must have read permissions, and also later on
950 # we want to check branch permissions here
964 # we want to check branch permissions here
951 target_perm = HasRepoPermissionAny(
965 target_perm = HasRepoPermissionAny(
952 'repository.read', 'repository.write', 'repository.admin')(
966 'repository.read', 'repository.write', 'repository.admin')(
953 target_db_repo.repo_name)
967 target_db_repo.repo_name)
954 if not target_perm:
968 if not target_perm:
955 msg = _('Not Enough permissions to target repo `{}`.'.format(
969 msg = _('Not Enough permissions to target repo `{}`.'.format(
956 target_db_repo.repo_name))
970 target_db_repo.repo_name))
957 h.flash(msg, category='error')
971 h.flash(msg, category='error')
958 # copy the args back to redirect
972 # copy the args back to redirect
959 org_query = self.request.GET.mixed()
973 org_query = self.request.GET.mixed()
960 raise HTTPFound(
974 raise HTTPFound(
961 h.route_path('pullrequest_new', repo_name=self.db_repo_name,
975 h.route_path('pullrequest_new', repo_name=self.db_repo_name,
962 _query=org_query))
976 _query=org_query))
963
977
964 source_scm = source_db_repo.scm_instance()
978 source_scm = source_db_repo.scm_instance()
965 target_scm = target_db_repo.scm_instance()
979 target_scm = target_db_repo.scm_instance()
966
980
967 source_commit = source_scm.get_commit(source_ref.split(':')[-1])
981 source_commit = source_scm.get_commit(source_ref.split(':')[-1])
968 target_commit = target_scm.get_commit(target_ref.split(':')[-1])
982 target_commit = target_scm.get_commit(target_ref.split(':')[-1])
969
983
970 ancestor = source_scm.get_common_ancestor(
984 ancestor = source_scm.get_common_ancestor(
971 source_commit.raw_id, target_commit.raw_id, target_scm)
985 source_commit.raw_id, target_commit.raw_id, target_scm)
972
986
973 # recalculate target ref based on ancestor
987 # recalculate target ref based on ancestor
974 target_ref_type, target_ref_name, __ = _form['target_ref'].split(':')
988 target_ref_type, target_ref_name, __ = _form['target_ref'].split(':')
975 target_ref = ':'.join((target_ref_type, target_ref_name, ancestor))
989 target_ref = ':'.join((target_ref_type, target_ref_name, ancestor))
976
990
977 get_default_reviewers_data, validate_default_reviewers = \
991 get_default_reviewers_data, validate_default_reviewers = \
978 PullRequestModel().get_reviewer_functions()
992 PullRequestModel().get_reviewer_functions()
979
993
980 # recalculate reviewers logic, to make sure we can validate this
994 # recalculate reviewers logic, to make sure we can validate this
981 reviewer_rules = get_default_reviewers_data(
995 reviewer_rules = get_default_reviewers_data(
982 self._rhodecode_db_user, source_db_repo,
996 self._rhodecode_db_user, source_db_repo,
983 source_commit, target_db_repo, target_commit)
997 source_commit, target_db_repo, target_commit)
984
998
985 given_reviewers = _form['review_members']
999 given_reviewers = _form['review_members']
986 reviewers = validate_default_reviewers(
1000 reviewers = validate_default_reviewers(
987 given_reviewers, reviewer_rules)
1001 given_reviewers, reviewer_rules)
988
1002
989 pullrequest_title = _form['pullrequest_title']
1003 pullrequest_title = _form['pullrequest_title']
990 title_source_ref = source_ref.split(':', 2)[1]
1004 title_source_ref = source_ref.split(':', 2)[1]
991 if not pullrequest_title:
1005 if not pullrequest_title:
992 pullrequest_title = PullRequestModel().generate_pullrequest_title(
1006 pullrequest_title = PullRequestModel().generate_pullrequest_title(
993 source=source_repo,
1007 source=source_repo,
994 source_ref=title_source_ref,
1008 source_ref=title_source_ref,
995 target=target_repo
1009 target=target_repo
996 )
1010 )
997
1011
998 description = _form['pullrequest_desc']
1012 description = _form['pullrequest_desc']
999 description_renderer = _form['description_renderer']
1013 description_renderer = _form['description_renderer']
1000
1014
1001 try:
1015 try:
1002 pull_request = PullRequestModel().create(
1016 pull_request = PullRequestModel().create(
1003 created_by=self._rhodecode_user.user_id,
1017 created_by=self._rhodecode_user.user_id,
1004 source_repo=source_repo,
1018 source_repo=source_repo,
1005 source_ref=source_ref,
1019 source_ref=source_ref,
1006 target_repo=target_repo,
1020 target_repo=target_repo,
1007 target_ref=target_ref,
1021 target_ref=target_ref,
1008 revisions=commit_ids,
1022 revisions=commit_ids,
1009 reviewers=reviewers,
1023 reviewers=reviewers,
1010 title=pullrequest_title,
1024 title=pullrequest_title,
1011 description=description,
1025 description=description,
1012 description_renderer=description_renderer,
1026 description_renderer=description_renderer,
1013 reviewer_data=reviewer_rules,
1027 reviewer_data=reviewer_rules,
1014 auth_user=self._rhodecode_user
1028 auth_user=self._rhodecode_user
1015 )
1029 )
1016 Session().commit()
1030 Session().commit()
1017
1031
1018 h.flash(_('Successfully opened new pull request'),
1032 h.flash(_('Successfully opened new pull request'),
1019 category='success')
1033 category='success')
1020 except Exception:
1034 except Exception:
1021 msg = _('Error occurred during creation of this pull request.')
1035 msg = _('Error occurred during creation of this pull request.')
1022 log.exception(msg)
1036 log.exception(msg)
1023 h.flash(msg, category='error')
1037 h.flash(msg, category='error')
1024
1038
1025 # copy the args back to redirect
1039 # copy the args back to redirect
1026 org_query = self.request.GET.mixed()
1040 org_query = self.request.GET.mixed()
1027 raise HTTPFound(
1041 raise HTTPFound(
1028 h.route_path('pullrequest_new', repo_name=self.db_repo_name,
1042 h.route_path('pullrequest_new', repo_name=self.db_repo_name,
1029 _query=org_query))
1043 _query=org_query))
1030
1044
1031 raise HTTPFound(
1045 raise HTTPFound(
1032 h.route_path('pullrequest_show', repo_name=target_repo,
1046 h.route_path('pullrequest_show', repo_name=target_repo,
1033 pull_request_id=pull_request.pull_request_id))
1047 pull_request_id=pull_request.pull_request_id))
1034
1048
1035 @LoginRequired()
1049 @LoginRequired()
1036 @NotAnonymous()
1050 @NotAnonymous()
1037 @HasRepoPermissionAnyDecorator(
1051 @HasRepoPermissionAnyDecorator(
1038 'repository.read', 'repository.write', 'repository.admin')
1052 'repository.read', 'repository.write', 'repository.admin')
1039 @CSRFRequired()
1053 @CSRFRequired()
1040 @view_config(
1054 @view_config(
1041 route_name='pullrequest_update', request_method='POST',
1055 route_name='pullrequest_update', request_method='POST',
1042 renderer='json_ext')
1056 renderer='json_ext')
1043 def pull_request_update(self):
1057 def pull_request_update(self):
1044 pull_request = PullRequest.get_or_404(
1058 pull_request = PullRequest.get_or_404(
1045 self.request.matchdict['pull_request_id'])
1059 self.request.matchdict['pull_request_id'])
1046 _ = self.request.translate
1060 _ = self.request.translate
1047
1061
1048 self.load_default_context()
1062 self.load_default_context()
1049 redirect_url = None
1063 redirect_url = None
1050
1064
1051 if pull_request.is_closed():
1065 if pull_request.is_closed():
1052 log.debug('update: forbidden because pull request is closed')
1066 log.debug('update: forbidden because pull request is closed')
1053 msg = _(u'Cannot update closed pull requests.')
1067 msg = _(u'Cannot update closed pull requests.')
1054 h.flash(msg, category='error')
1068 h.flash(msg, category='error')
1055 return {'response': True,
1069 return {'response': True,
1056 'redirect_url': redirect_url}
1070 'redirect_url': redirect_url}
1057
1071
1058 is_state_changing = pull_request.is_state_changing()
1072 is_state_changing = pull_request.is_state_changing()
1059
1073
1060 # only owner or admin can update it
1074 # only owner or admin can update it
1061 allowed_to_update = PullRequestModel().check_user_update(
1075 allowed_to_update = PullRequestModel().check_user_update(
1062 pull_request, self._rhodecode_user)
1076 pull_request, self._rhodecode_user)
1063 if allowed_to_update:
1077 if allowed_to_update:
1064 controls = peppercorn.parse(self.request.POST.items())
1078 controls = peppercorn.parse(self.request.POST.items())
1065 force_refresh = str2bool(self.request.POST.get('force_refresh'))
1079 force_refresh = str2bool(self.request.POST.get('force_refresh'))
1066
1080
1067 if 'review_members' in controls:
1081 if 'review_members' in controls:
1068 self._update_reviewers(
1082 self._update_reviewers(
1069 pull_request, controls['review_members'],
1083 pull_request, controls['review_members'],
1070 pull_request.reviewer_data)
1084 pull_request.reviewer_data)
1071 elif str2bool(self.request.POST.get('update_commits', 'false')):
1085 elif str2bool(self.request.POST.get('update_commits', 'false')):
1072 if is_state_changing:
1086 if is_state_changing:
1073 log.debug('commits update: forbidden because pull request is in state %s',
1087 log.debug('commits update: forbidden because pull request is in state %s',
1074 pull_request.pull_request_state)
1088 pull_request.pull_request_state)
1075 msg = _(u'Cannot update pull requests commits in state other than `{}`. '
1089 msg = _(u'Cannot update pull requests commits in state other than `{}`. '
1076 u'Current state is: `{}`').format(
1090 u'Current state is: `{}`').format(
1077 PullRequest.STATE_CREATED, pull_request.pull_request_state)
1091 PullRequest.STATE_CREATED, pull_request.pull_request_state)
1078 h.flash(msg, category='error')
1092 h.flash(msg, category='error')
1079 return {'response': True,
1093 return {'response': True,
1080 'redirect_url': redirect_url}
1094 'redirect_url': redirect_url}
1081
1095
1082 self._update_commits(pull_request)
1096 self._update_commits(pull_request)
1083 if force_refresh:
1097 if force_refresh:
1084 redirect_url = h.route_path(
1098 redirect_url = h.route_path(
1085 'pullrequest_show', repo_name=self.db_repo_name,
1099 'pullrequest_show', repo_name=self.db_repo_name,
1086 pull_request_id=pull_request.pull_request_id,
1100 pull_request_id=pull_request.pull_request_id,
1087 _query={"force_refresh": 1})
1101 _query={"force_refresh": 1})
1088 elif str2bool(self.request.POST.get('edit_pull_request', 'false')):
1102 elif str2bool(self.request.POST.get('edit_pull_request', 'false')):
1089 self._edit_pull_request(pull_request)
1103 self._edit_pull_request(pull_request)
1090 else:
1104 else:
1091 raise HTTPBadRequest()
1105 raise HTTPBadRequest()
1092
1106
1093 return {'response': True,
1107 return {'response': True,
1094 'redirect_url': redirect_url}
1108 'redirect_url': redirect_url}
1095 raise HTTPForbidden()
1109 raise HTTPForbidden()
1096
1110
1097 def _edit_pull_request(self, pull_request):
1111 def _edit_pull_request(self, pull_request):
1098 _ = self.request.translate
1112 _ = self.request.translate
1099
1113
1100 try:
1114 try:
1101 PullRequestModel().edit(
1115 PullRequestModel().edit(
1102 pull_request,
1116 pull_request,
1103 self.request.POST.get('title'),
1117 self.request.POST.get('title'),
1104 self.request.POST.get('description'),
1118 self.request.POST.get('description'),
1105 self.request.POST.get('description_renderer'),
1119 self.request.POST.get('description_renderer'),
1106 self._rhodecode_user)
1120 self._rhodecode_user)
1107 except ValueError:
1121 except ValueError:
1108 msg = _(u'Cannot update closed pull requests.')
1122 msg = _(u'Cannot update closed pull requests.')
1109 h.flash(msg, category='error')
1123 h.flash(msg, category='error')
1110 return
1124 return
1111 else:
1125 else:
1112 Session().commit()
1126 Session().commit()
1113
1127
1114 msg = _(u'Pull request title & description updated.')
1128 msg = _(u'Pull request title & description updated.')
1115 h.flash(msg, category='success')
1129 h.flash(msg, category='success')
1116 return
1130 return
1117
1131
1118 def _update_commits(self, pull_request):
1132 def _update_commits(self, pull_request):
1119 _ = self.request.translate
1133 _ = self.request.translate
1120
1134
1121 with pull_request.set_state(PullRequest.STATE_UPDATING):
1135 with pull_request.set_state(PullRequest.STATE_UPDATING):
1122 resp = PullRequestModel().update_commits(
1136 resp = PullRequestModel().update_commits(
1123 pull_request, self._rhodecode_db_user)
1137 pull_request, self._rhodecode_db_user)
1124
1138
1125 if resp.executed:
1139 if resp.executed:
1126
1140
1127 if resp.target_changed and resp.source_changed:
1141 if resp.target_changed and resp.source_changed:
1128 changed = 'target and source repositories'
1142 changed = 'target and source repositories'
1129 elif resp.target_changed and not resp.source_changed:
1143 elif resp.target_changed and not resp.source_changed:
1130 changed = 'target repository'
1144 changed = 'target repository'
1131 elif not resp.target_changed and resp.source_changed:
1145 elif not resp.target_changed and resp.source_changed:
1132 changed = 'source repository'
1146 changed = 'source repository'
1133 else:
1147 else:
1134 changed = 'nothing'
1148 changed = 'nothing'
1135
1149
1136 msg = _(u'Pull request updated to "{source_commit_id}" with '
1150 msg = _(u'Pull request updated to "{source_commit_id}" with '
1137 u'{count_added} added, {count_removed} removed commits. '
1151 u'{count_added} added, {count_removed} removed commits. '
1138 u'Source of changes: {change_source}')
1152 u'Source of changes: {change_source}')
1139 msg = msg.format(
1153 msg = msg.format(
1140 source_commit_id=pull_request.source_ref_parts.commit_id,
1154 source_commit_id=pull_request.source_ref_parts.commit_id,
1141 count_added=len(resp.changes.added),
1155 count_added=len(resp.changes.added),
1142 count_removed=len(resp.changes.removed),
1156 count_removed=len(resp.changes.removed),
1143 change_source=changed)
1157 change_source=changed)
1144 h.flash(msg, category='success')
1158 h.flash(msg, category='success')
1145
1159
1146 channel = '/repo${}$/pr/{}'.format(
1160 channel = '/repo${}$/pr/{}'.format(
1147 pull_request.target_repo.repo_name, pull_request.pull_request_id)
1161 pull_request.target_repo.repo_name, pull_request.pull_request_id)
1148 message = msg + (
1162 message = msg + (
1149 ' - <a onclick="window.location.reload()">'
1163 ' - <a onclick="window.location.reload()">'
1150 '<strong>{}</strong></a>'.format(_('Reload page')))
1164 '<strong>{}</strong></a>'.format(_('Reload page')))
1151 channelstream.post_message(
1165 channelstream.post_message(
1152 channel, message, self._rhodecode_user.username,
1166 channel, message, self._rhodecode_user.username,
1153 registry=self.request.registry)
1167 registry=self.request.registry)
1154 else:
1168 else:
1155 msg = PullRequestModel.UPDATE_STATUS_MESSAGES[resp.reason]
1169 msg = PullRequestModel.UPDATE_STATUS_MESSAGES[resp.reason]
1156 warning_reasons = [
1170 warning_reasons = [
1157 UpdateFailureReason.NO_CHANGE,
1171 UpdateFailureReason.NO_CHANGE,
1158 UpdateFailureReason.WRONG_REF_TYPE,
1172 UpdateFailureReason.WRONG_REF_TYPE,
1159 ]
1173 ]
1160 category = 'warning' if resp.reason in warning_reasons else 'error'
1174 category = 'warning' if resp.reason in warning_reasons else 'error'
1161 h.flash(msg, category=category)
1175 h.flash(msg, category=category)
1162
1176
1163 @LoginRequired()
1177 @LoginRequired()
1164 @NotAnonymous()
1178 @NotAnonymous()
1165 @HasRepoPermissionAnyDecorator(
1179 @HasRepoPermissionAnyDecorator(
1166 'repository.read', 'repository.write', 'repository.admin')
1180 'repository.read', 'repository.write', 'repository.admin')
1167 @CSRFRequired()
1181 @CSRFRequired()
1168 @view_config(
1182 @view_config(
1169 route_name='pullrequest_merge', request_method='POST',
1183 route_name='pullrequest_merge', request_method='POST',
1170 renderer='json_ext')
1184 renderer='json_ext')
1171 def pull_request_merge(self):
1185 def pull_request_merge(self):
1172 """
1186 """
1173 Merge will perform a server-side merge of the specified
1187 Merge will perform a server-side merge of the specified
1174 pull request, if the pull request is approved and mergeable.
1188 pull request, if the pull request is approved and mergeable.
1175 After successful merging, the pull request is automatically
1189 After successful merging, the pull request is automatically
1176 closed, with a relevant comment.
1190 closed, with a relevant comment.
1177 """
1191 """
1178 pull_request = PullRequest.get_or_404(
1192 pull_request = PullRequest.get_or_404(
1179 self.request.matchdict['pull_request_id'])
1193 self.request.matchdict['pull_request_id'])
1180 _ = self.request.translate
1194 _ = self.request.translate
1181
1195
1182 if pull_request.is_state_changing():
1196 if pull_request.is_state_changing():
1183 log.debug('show: forbidden because pull request is in state %s',
1197 log.debug('show: forbidden because pull request is in state %s',
1184 pull_request.pull_request_state)
1198 pull_request.pull_request_state)
1185 msg = _(u'Cannot merge pull requests in state other than `{}`. '
1199 msg = _(u'Cannot merge pull requests in state other than `{}`. '
1186 u'Current state is: `{}`').format(PullRequest.STATE_CREATED,
1200 u'Current state is: `{}`').format(PullRequest.STATE_CREATED,
1187 pull_request.pull_request_state)
1201 pull_request.pull_request_state)
1188 h.flash(msg, category='error')
1202 h.flash(msg, category='error')
1189 raise HTTPFound(
1203 raise HTTPFound(
1190 h.route_path('pullrequest_show',
1204 h.route_path('pullrequest_show',
1191 repo_name=pull_request.target_repo.repo_name,
1205 repo_name=pull_request.target_repo.repo_name,
1192 pull_request_id=pull_request.pull_request_id))
1206 pull_request_id=pull_request.pull_request_id))
1193
1207
1194 self.load_default_context()
1208 self.load_default_context()
1195
1209
1196 with pull_request.set_state(PullRequest.STATE_UPDATING):
1210 with pull_request.set_state(PullRequest.STATE_UPDATING):
1197 check = MergeCheck.validate(
1211 check = MergeCheck.validate(
1198 pull_request, auth_user=self._rhodecode_user,
1212 pull_request, auth_user=self._rhodecode_user,
1199 translator=self.request.translate)
1213 translator=self.request.translate)
1200 merge_possible = not check.failed
1214 merge_possible = not check.failed
1201
1215
1202 for err_type, error_msg in check.errors:
1216 for err_type, error_msg in check.errors:
1203 h.flash(error_msg, category=err_type)
1217 h.flash(error_msg, category=err_type)
1204
1218
1205 if merge_possible:
1219 if merge_possible:
1206 log.debug("Pre-conditions checked, trying to merge.")
1220 log.debug("Pre-conditions checked, trying to merge.")
1207 extras = vcs_operation_context(
1221 extras = vcs_operation_context(
1208 self.request.environ, repo_name=pull_request.target_repo.repo_name,
1222 self.request.environ, repo_name=pull_request.target_repo.repo_name,
1209 username=self._rhodecode_db_user.username, action='push',
1223 username=self._rhodecode_db_user.username, action='push',
1210 scm=pull_request.target_repo.repo_type)
1224 scm=pull_request.target_repo.repo_type)
1211 with pull_request.set_state(PullRequest.STATE_UPDATING):
1225 with pull_request.set_state(PullRequest.STATE_UPDATING):
1212 self._merge_pull_request(
1226 self._merge_pull_request(
1213 pull_request, self._rhodecode_db_user, extras)
1227 pull_request, self._rhodecode_db_user, extras)
1214 else:
1228 else:
1215 log.debug("Pre-conditions failed, NOT merging.")
1229 log.debug("Pre-conditions failed, NOT merging.")
1216
1230
1217 raise HTTPFound(
1231 raise HTTPFound(
1218 h.route_path('pullrequest_show',
1232 h.route_path('pullrequest_show',
1219 repo_name=pull_request.target_repo.repo_name,
1233 repo_name=pull_request.target_repo.repo_name,
1220 pull_request_id=pull_request.pull_request_id))
1234 pull_request_id=pull_request.pull_request_id))
1221
1235
1222 def _merge_pull_request(self, pull_request, user, extras):
1236 def _merge_pull_request(self, pull_request, user, extras):
1223 _ = self.request.translate
1237 _ = self.request.translate
1224 merge_resp = PullRequestModel().merge_repo(pull_request, user, extras=extras)
1238 merge_resp = PullRequestModel().merge_repo(pull_request, user, extras=extras)
1225
1239
1226 if merge_resp.executed:
1240 if merge_resp.executed:
1227 log.debug("The merge was successful, closing the pull request.")
1241 log.debug("The merge was successful, closing the pull request.")
1228 PullRequestModel().close_pull_request(
1242 PullRequestModel().close_pull_request(
1229 pull_request.pull_request_id, user)
1243 pull_request.pull_request_id, user)
1230 Session().commit()
1244 Session().commit()
1231 msg = _('Pull request was successfully merged and closed.')
1245 msg = _('Pull request was successfully merged and closed.')
1232 h.flash(msg, category='success')
1246 h.flash(msg, category='success')
1233 else:
1247 else:
1234 log.debug(
1248 log.debug(
1235 "The merge was not successful. Merge response: %s", merge_resp)
1249 "The merge was not successful. Merge response: %s", merge_resp)
1236 msg = merge_resp.merge_status_message
1250 msg = merge_resp.merge_status_message
1237 h.flash(msg, category='error')
1251 h.flash(msg, category='error')
1238
1252
1239 def _update_reviewers(self, pull_request, review_members, reviewer_rules):
1253 def _update_reviewers(self, pull_request, review_members, reviewer_rules):
1240 _ = self.request.translate
1254 _ = self.request.translate
1241
1255
1242 get_default_reviewers_data, validate_default_reviewers = \
1256 get_default_reviewers_data, validate_default_reviewers = \
1243 PullRequestModel().get_reviewer_functions()
1257 PullRequestModel().get_reviewer_functions()
1244
1258
1245 try:
1259 try:
1246 reviewers = validate_default_reviewers(review_members, reviewer_rules)
1260 reviewers = validate_default_reviewers(review_members, reviewer_rules)
1247 except ValueError as e:
1261 except ValueError as e:
1248 log.error('Reviewers Validation: {}'.format(e))
1262 log.error('Reviewers Validation: {}'.format(e))
1249 h.flash(e, category='error')
1263 h.flash(e, category='error')
1250 return
1264 return
1251
1265
1252 old_calculated_status = pull_request.calculated_review_status()
1266 old_calculated_status = pull_request.calculated_review_status()
1253 PullRequestModel().update_reviewers(
1267 PullRequestModel().update_reviewers(
1254 pull_request, reviewers, self._rhodecode_user)
1268 pull_request, reviewers, self._rhodecode_user)
1255 h.flash(_('Pull request reviewers updated.'), category='success')
1269 h.flash(_('Pull request reviewers updated.'), category='success')
1256 Session().commit()
1270 Session().commit()
1257
1271
1258 # trigger status changed if change in reviewers changes the status
1272 # trigger status changed if change in reviewers changes the status
1259 calculated_status = pull_request.calculated_review_status()
1273 calculated_status = pull_request.calculated_review_status()
1260 if old_calculated_status != calculated_status:
1274 if old_calculated_status != calculated_status:
1261 PullRequestModel().trigger_pull_request_hook(
1275 PullRequestModel().trigger_pull_request_hook(
1262 pull_request, self._rhodecode_user, 'review_status_change',
1276 pull_request, self._rhodecode_user, 'review_status_change',
1263 data={'status': calculated_status})
1277 data={'status': calculated_status})
1264
1278
1265 @LoginRequired()
1279 @LoginRequired()
1266 @NotAnonymous()
1280 @NotAnonymous()
1267 @HasRepoPermissionAnyDecorator(
1281 @HasRepoPermissionAnyDecorator(
1268 'repository.read', 'repository.write', 'repository.admin')
1282 'repository.read', 'repository.write', 'repository.admin')
1269 @CSRFRequired()
1283 @CSRFRequired()
1270 @view_config(
1284 @view_config(
1271 route_name='pullrequest_delete', request_method='POST',
1285 route_name='pullrequest_delete', request_method='POST',
1272 renderer='json_ext')
1286 renderer='json_ext')
1273 def pull_request_delete(self):
1287 def pull_request_delete(self):
1274 _ = self.request.translate
1288 _ = self.request.translate
1275
1289
1276 pull_request = PullRequest.get_or_404(
1290 pull_request = PullRequest.get_or_404(
1277 self.request.matchdict['pull_request_id'])
1291 self.request.matchdict['pull_request_id'])
1278 self.load_default_context()
1292 self.load_default_context()
1279
1293
1280 pr_closed = pull_request.is_closed()
1294 pr_closed = pull_request.is_closed()
1281 allowed_to_delete = PullRequestModel().check_user_delete(
1295 allowed_to_delete = PullRequestModel().check_user_delete(
1282 pull_request, self._rhodecode_user) and not pr_closed
1296 pull_request, self._rhodecode_user) and not pr_closed
1283
1297
1284 # only owner can delete it !
1298 # only owner can delete it !
1285 if allowed_to_delete:
1299 if allowed_to_delete:
1286 PullRequestModel().delete(pull_request, self._rhodecode_user)
1300 PullRequestModel().delete(pull_request, self._rhodecode_user)
1287 Session().commit()
1301 Session().commit()
1288 h.flash(_('Successfully deleted pull request'),
1302 h.flash(_('Successfully deleted pull request'),
1289 category='success')
1303 category='success')
1290 raise HTTPFound(h.route_path('pullrequest_show_all',
1304 raise HTTPFound(h.route_path('pullrequest_show_all',
1291 repo_name=self.db_repo_name))
1305 repo_name=self.db_repo_name))
1292
1306
1293 log.warning('user %s tried to delete pull request without access',
1307 log.warning('user %s tried to delete pull request without access',
1294 self._rhodecode_user)
1308 self._rhodecode_user)
1295 raise HTTPNotFound()
1309 raise HTTPNotFound()
1296
1310
1297 @LoginRequired()
1311 @LoginRequired()
1298 @NotAnonymous()
1312 @NotAnonymous()
1299 @HasRepoPermissionAnyDecorator(
1313 @HasRepoPermissionAnyDecorator(
1300 'repository.read', 'repository.write', 'repository.admin')
1314 'repository.read', 'repository.write', 'repository.admin')
1301 @CSRFRequired()
1315 @CSRFRequired()
1302 @view_config(
1316 @view_config(
1303 route_name='pullrequest_comment_create', request_method='POST',
1317 route_name='pullrequest_comment_create', request_method='POST',
1304 renderer='json_ext')
1318 renderer='json_ext')
1305 def pull_request_comment_create(self):
1319 def pull_request_comment_create(self):
1306 _ = self.request.translate
1320 _ = self.request.translate
1307
1321
1308 pull_request = PullRequest.get_or_404(
1322 pull_request = PullRequest.get_or_404(
1309 self.request.matchdict['pull_request_id'])
1323 self.request.matchdict['pull_request_id'])
1310 pull_request_id = pull_request.pull_request_id
1324 pull_request_id = pull_request.pull_request_id
1311
1325
1312 if pull_request.is_closed():
1326 if pull_request.is_closed():
1313 log.debug('comment: forbidden because pull request is closed')
1327 log.debug('comment: forbidden because pull request is closed')
1314 raise HTTPForbidden()
1328 raise HTTPForbidden()
1315
1329
1316 allowed_to_comment = PullRequestModel().check_user_comment(
1330 allowed_to_comment = PullRequestModel().check_user_comment(
1317 pull_request, self._rhodecode_user)
1331 pull_request, self._rhodecode_user)
1318 if not allowed_to_comment:
1332 if not allowed_to_comment:
1319 log.debug(
1333 log.debug(
1320 'comment: forbidden because pull request is from forbidden repo')
1334 'comment: forbidden because pull request is from forbidden repo')
1321 raise HTTPForbidden()
1335 raise HTTPForbidden()
1322
1336
1323 c = self.load_default_context()
1337 c = self.load_default_context()
1324
1338
1325 status = self.request.POST.get('changeset_status', None)
1339 status = self.request.POST.get('changeset_status', None)
1326 text = self.request.POST.get('text')
1340 text = self.request.POST.get('text')
1327 comment_type = self.request.POST.get('comment_type')
1341 comment_type = self.request.POST.get('comment_type')
1328 resolves_comment_id = self.request.POST.get('resolves_comment_id', None)
1342 resolves_comment_id = self.request.POST.get('resolves_comment_id', None)
1329 close_pull_request = self.request.POST.get('close_pull_request')
1343 close_pull_request = self.request.POST.get('close_pull_request')
1330
1344
1331 # the logic here should work like following, if we submit close
1345 # the logic here should work like following, if we submit close
1332 # pr comment, use `close_pull_request_with_comment` function
1346 # pr comment, use `close_pull_request_with_comment` function
1333 # else handle regular comment logic
1347 # else handle regular comment logic
1334
1348
1335 if close_pull_request:
1349 if close_pull_request:
1336 # only owner or admin or person with write permissions
1350 # only owner or admin or person with write permissions
1337 allowed_to_close = PullRequestModel().check_user_update(
1351 allowed_to_close = PullRequestModel().check_user_update(
1338 pull_request, self._rhodecode_user)
1352 pull_request, self._rhodecode_user)
1339 if not allowed_to_close:
1353 if not allowed_to_close:
1340 log.debug('comment: forbidden because not allowed to close '
1354 log.debug('comment: forbidden because not allowed to close '
1341 'pull request %s', pull_request_id)
1355 'pull request %s', pull_request_id)
1342 raise HTTPForbidden()
1356 raise HTTPForbidden()
1343
1357
1344 # This also triggers `review_status_change`
1358 # This also triggers `review_status_change`
1345 comment, status = PullRequestModel().close_pull_request_with_comment(
1359 comment, status = PullRequestModel().close_pull_request_with_comment(
1346 pull_request, self._rhodecode_user, self.db_repo, message=text,
1360 pull_request, self._rhodecode_user, self.db_repo, message=text,
1347 auth_user=self._rhodecode_user)
1361 auth_user=self._rhodecode_user)
1348 Session().flush()
1362 Session().flush()
1349
1363
1350 PullRequestModel().trigger_pull_request_hook(
1364 PullRequestModel().trigger_pull_request_hook(
1351 pull_request, self._rhodecode_user, 'comment',
1365 pull_request, self._rhodecode_user, 'comment',
1352 data={'comment': comment})
1366 data={'comment': comment})
1353
1367
1354 else:
1368 else:
1355 # regular comment case, could be inline, or one with status.
1369 # regular comment case, could be inline, or one with status.
1356 # for that one we check also permissions
1370 # for that one we check also permissions
1357
1371
1358 allowed_to_change_status = PullRequestModel().check_user_change_status(
1372 allowed_to_change_status = PullRequestModel().check_user_change_status(
1359 pull_request, self._rhodecode_user)
1373 pull_request, self._rhodecode_user)
1360
1374
1361 if status and allowed_to_change_status:
1375 if status and allowed_to_change_status:
1362 message = (_('Status change %(transition_icon)s %(status)s')
1376 message = (_('Status change %(transition_icon)s %(status)s')
1363 % {'transition_icon': '>',
1377 % {'transition_icon': '>',
1364 'status': ChangesetStatus.get_status_lbl(status)})
1378 'status': ChangesetStatus.get_status_lbl(status)})
1365 text = text or message
1379 text = text or message
1366
1380
1367 comment = CommentsModel().create(
1381 comment = CommentsModel().create(
1368 text=text,
1382 text=text,
1369 repo=self.db_repo.repo_id,
1383 repo=self.db_repo.repo_id,
1370 user=self._rhodecode_user.user_id,
1384 user=self._rhodecode_user.user_id,
1371 pull_request=pull_request,
1385 pull_request=pull_request,
1372 f_path=self.request.POST.get('f_path'),
1386 f_path=self.request.POST.get('f_path'),
1373 line_no=self.request.POST.get('line'),
1387 line_no=self.request.POST.get('line'),
1374 status_change=(ChangesetStatus.get_status_lbl(status)
1388 status_change=(ChangesetStatus.get_status_lbl(status)
1375 if status and allowed_to_change_status else None),
1389 if status and allowed_to_change_status else None),
1376 status_change_type=(status
1390 status_change_type=(status
1377 if status and allowed_to_change_status else None),
1391 if status and allowed_to_change_status else None),
1378 comment_type=comment_type,
1392 comment_type=comment_type,
1379 resolves_comment_id=resolves_comment_id,
1393 resolves_comment_id=resolves_comment_id,
1380 auth_user=self._rhodecode_user
1394 auth_user=self._rhodecode_user
1381 )
1395 )
1382
1396
1383 if allowed_to_change_status:
1397 if allowed_to_change_status:
1384 # calculate old status before we change it
1398 # calculate old status before we change it
1385 old_calculated_status = pull_request.calculated_review_status()
1399 old_calculated_status = pull_request.calculated_review_status()
1386
1400
1387 # get status if set !
1401 # get status if set !
1388 if status:
1402 if status:
1389 ChangesetStatusModel().set_status(
1403 ChangesetStatusModel().set_status(
1390 self.db_repo.repo_id,
1404 self.db_repo.repo_id,
1391 status,
1405 status,
1392 self._rhodecode_user.user_id,
1406 self._rhodecode_user.user_id,
1393 comment,
1407 comment,
1394 pull_request=pull_request
1408 pull_request=pull_request
1395 )
1409 )
1396
1410
1397 Session().flush()
1411 Session().flush()
1398 # this is somehow required to get access to some relationship
1412 # this is somehow required to get access to some relationship
1399 # loaded on comment
1413 # loaded on comment
1400 Session().refresh(comment)
1414 Session().refresh(comment)
1401
1415
1402 PullRequestModel().trigger_pull_request_hook(
1416 PullRequestModel().trigger_pull_request_hook(
1403 pull_request, self._rhodecode_user, 'comment',
1417 pull_request, self._rhodecode_user, 'comment',
1404 data={'comment': comment})
1418 data={'comment': comment})
1405
1419
1406 # we now calculate the status of pull request, and based on that
1420 # we now calculate the status of pull request, and based on that
1407 # calculation we set the commits status
1421 # calculation we set the commits status
1408 calculated_status = pull_request.calculated_review_status()
1422 calculated_status = pull_request.calculated_review_status()
1409 if old_calculated_status != calculated_status:
1423 if old_calculated_status != calculated_status:
1410 PullRequestModel().trigger_pull_request_hook(
1424 PullRequestModel().trigger_pull_request_hook(
1411 pull_request, self._rhodecode_user, 'review_status_change',
1425 pull_request, self._rhodecode_user, 'review_status_change',
1412 data={'status': calculated_status})
1426 data={'status': calculated_status})
1413
1427
1414 Session().commit()
1428 Session().commit()
1415
1429
1416 data = {
1430 data = {
1417 'target_id': h.safeid(h.safe_unicode(
1431 'target_id': h.safeid(h.safe_unicode(
1418 self.request.POST.get('f_path'))),
1432 self.request.POST.get('f_path'))),
1419 }
1433 }
1420 if comment:
1434 if comment:
1421 c.co = comment
1435 c.co = comment
1422 rendered_comment = render(
1436 rendered_comment = render(
1423 'rhodecode:templates/changeset/changeset_comment_block.mako',
1437 'rhodecode:templates/changeset/changeset_comment_block.mako',
1424 self._get_template_context(c), self.request)
1438 self._get_template_context(c), self.request)
1425
1439
1426 data.update(comment.get_dict())
1440 data.update(comment.get_dict())
1427 data.update({'rendered_text': rendered_comment})
1441 data.update({'rendered_text': rendered_comment})
1428
1442
1429 return data
1443 return data
1430
1444
1431 @LoginRequired()
1445 @LoginRequired()
1432 @NotAnonymous()
1446 @NotAnonymous()
1433 @HasRepoPermissionAnyDecorator(
1447 @HasRepoPermissionAnyDecorator(
1434 'repository.read', 'repository.write', 'repository.admin')
1448 'repository.read', 'repository.write', 'repository.admin')
1435 @CSRFRequired()
1449 @CSRFRequired()
1436 @view_config(
1450 @view_config(
1437 route_name='pullrequest_comment_delete', request_method='POST',
1451 route_name='pullrequest_comment_delete', request_method='POST',
1438 renderer='json_ext')
1452 renderer='json_ext')
1439 def pull_request_comment_delete(self):
1453 def pull_request_comment_delete(self):
1440 pull_request = PullRequest.get_or_404(
1454 pull_request = PullRequest.get_or_404(
1441 self.request.matchdict['pull_request_id'])
1455 self.request.matchdict['pull_request_id'])
1442
1456
1443 comment = ChangesetComment.get_or_404(
1457 comment = ChangesetComment.get_or_404(
1444 self.request.matchdict['comment_id'])
1458 self.request.matchdict['comment_id'])
1445 comment_id = comment.comment_id
1459 comment_id = comment.comment_id
1446
1460
1447 if pull_request.is_closed():
1461 if pull_request.is_closed():
1448 log.debug('comment: forbidden because pull request is closed')
1462 log.debug('comment: forbidden because pull request is closed')
1449 raise HTTPForbidden()
1463 raise HTTPForbidden()
1450
1464
1451 if not comment:
1465 if not comment:
1452 log.debug('Comment with id:%s not found, skipping', comment_id)
1466 log.debug('Comment with id:%s not found, skipping', comment_id)
1453 # comment already deleted in another call probably
1467 # comment already deleted in another call probably
1454 return True
1468 return True
1455
1469
1456 if comment.pull_request.is_closed():
1470 if comment.pull_request.is_closed():
1457 # don't allow deleting comments on closed pull request
1471 # don't allow deleting comments on closed pull request
1458 raise HTTPForbidden()
1472 raise HTTPForbidden()
1459
1473
1460 is_repo_admin = h.HasRepoPermissionAny('repository.admin')(self.db_repo_name)
1474 is_repo_admin = h.HasRepoPermissionAny('repository.admin')(self.db_repo_name)
1461 super_admin = h.HasPermissionAny('hg.admin')()
1475 super_admin = h.HasPermissionAny('hg.admin')()
1462 comment_owner = comment.author.user_id == self._rhodecode_user.user_id
1476 comment_owner = comment.author.user_id == self._rhodecode_user.user_id
1463 is_repo_comment = comment.repo.repo_name == self.db_repo_name
1477 is_repo_comment = comment.repo.repo_name == self.db_repo_name
1464 comment_repo_admin = is_repo_admin and is_repo_comment
1478 comment_repo_admin = is_repo_admin and is_repo_comment
1465
1479
1466 if super_admin or comment_owner or comment_repo_admin:
1480 if super_admin or comment_owner or comment_repo_admin:
1467 old_calculated_status = comment.pull_request.calculated_review_status()
1481 old_calculated_status = comment.pull_request.calculated_review_status()
1468 CommentsModel().delete(comment=comment, auth_user=self._rhodecode_user)
1482 CommentsModel().delete(comment=comment, auth_user=self._rhodecode_user)
1469 Session().commit()
1483 Session().commit()
1470 calculated_status = comment.pull_request.calculated_review_status()
1484 calculated_status = comment.pull_request.calculated_review_status()
1471 if old_calculated_status != calculated_status:
1485 if old_calculated_status != calculated_status:
1472 PullRequestModel().trigger_pull_request_hook(
1486 PullRequestModel().trigger_pull_request_hook(
1473 comment.pull_request, self._rhodecode_user, 'review_status_change',
1487 comment.pull_request, self._rhodecode_user, 'review_status_change',
1474 data={'status': calculated_status})
1488 data={'status': calculated_status})
1475 return True
1489 return True
1476 else:
1490 else:
1477 log.warning('No permissions for user %s to delete comment_id: %s',
1491 log.warning('No permissions for user %s to delete comment_id: %s',
1478 self._rhodecode_db_user, comment_id)
1492 self._rhodecode_db_user, comment_id)
1479 raise HTTPNotFound()
1493 raise HTTPNotFound()
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
@@ -1,907 +1,912 b''
1 <%inherit file="/base/base.mako"/>
1 <%inherit file="/base/base.mako"/>
2 <%namespace name="base" file="/base/base.mako"/>
2 <%namespace name="base" file="/base/base.mako"/>
3 <%namespace name="dt" file="/data_table/_dt_elements.mako"/>
3 <%namespace name="dt" file="/data_table/_dt_elements.mako"/>
4
4
5 <%def name="title()">
5 <%def name="title()">
6 ${_('{} Pull Request !{}').format(c.repo_name, c.pull_request.pull_request_id)}
6 ${_('{} Pull Request !{}').format(c.repo_name, c.pull_request.pull_request_id)}
7 %if c.rhodecode_name:
7 %if c.rhodecode_name:
8 &middot; ${h.branding(c.rhodecode_name)}
8 &middot; ${h.branding(c.rhodecode_name)}
9 %endif
9 %endif
10 </%def>
10 </%def>
11
11
12 <%def name="breadcrumbs_links()">
12 <%def name="breadcrumbs_links()">
13
13
14 <div id="pr-title">
14 <div id="pr-title">
15 % if c.pull_request.is_closed():
15 % if c.pull_request.is_closed():
16 <span class="pr-title-closed-tag tag">${_('Closed')}</span>
16 <span class="pr-title-closed-tag tag">${_('Closed')}</span>
17 % endif
17 % endif
18 <input class="pr-title-input large disabled" disabled="disabled" name="pullrequest_title" type="text" value="${c.pull_request.title}">
18 <input class="pr-title-input large disabled" disabled="disabled" name="pullrequest_title" type="text" value="${c.pull_request.title}">
19 </div>
19 </div>
20 <div id="pr-title-edit" class="input" style="display: none;">
20 <div id="pr-title-edit" class="input" style="display: none;">
21 <input class="pr-title-input large" id="pr-title-input" name="pullrequest_title" type="text" value="${c.pull_request.title}">
21 <input class="pr-title-input large" id="pr-title-input" name="pullrequest_title" type="text" value="${c.pull_request.title}">
22 </div>
22 </div>
23 </%def>
23 </%def>
24
24
25 <%def name="menu_bar_nav()">
25 <%def name="menu_bar_nav()">
26 ${self.menu_items(active='repositories')}
26 ${self.menu_items(active='repositories')}
27 </%def>
27 </%def>
28
28
29 <%def name="menu_bar_subnav()">
29 <%def name="menu_bar_subnav()">
30 ${self.repo_menu(active='showpullrequest')}
30 ${self.repo_menu(active='showpullrequest')}
31 </%def>
31 </%def>
32
32
33 <%def name="main()">
33 <%def name="main()">
34
34
35 <script type="text/javascript">
35 <script type="text/javascript">
36 // TODO: marcink switch this to pyroutes
36 // TODO: marcink switch this to pyroutes
37 AJAX_COMMENT_DELETE_URL = "${h.route_path('pullrequest_comment_delete',repo_name=c.repo_name,pull_request_id=c.pull_request.pull_request_id,comment_id='__COMMENT_ID__')}";
37 AJAX_COMMENT_DELETE_URL = "${h.route_path('pullrequest_comment_delete',repo_name=c.repo_name,pull_request_id=c.pull_request.pull_request_id,comment_id='__COMMENT_ID__')}";
38 templateContext.pull_request_data.pull_request_id = ${c.pull_request.pull_request_id};
38 templateContext.pull_request_data.pull_request_id = ${c.pull_request.pull_request_id};
39 </script>
39 </script>
40
40
41 <div class="box">
41 <div class="box">
42
42
43 ${self.breadcrumbs()}
43 ${self.breadcrumbs()}
44
44
45 <div class="box pr-summary">
45 <div class="box pr-summary">
46
46
47 <div class="summary-details block-left">
47 <div class="summary-details block-left">
48 <% summary = lambda n:{False:'summary-short'}.get(n) %>
48 <% summary = lambda n:{False:'summary-short'}.get(n) %>
49 <div class="pr-details-title">
49 <div class="pr-details-title">
50 <div class="pull-left">
50 <div class="pull-left">
51 <a href="${h.route_path('pull_requests_global', pull_request_id=c.pull_request.pull_request_id)}">${_('Pull request !{}').format(c.pull_request.pull_request_id)}</a>
51 <a href="${h.route_path('pull_requests_global', pull_request_id=c.pull_request.pull_request_id)}">${_('Pull request !{}').format(c.pull_request.pull_request_id)}</a>
52 ${_('Created on')}
52 ${_('Created on')}
53 <span class="tooltip" title="${_('Last updated on')} ${h.format_date(c.pull_request.updated_on)}">${h.format_date(c.pull_request.created_on)},</span>
53 <span class="tooltip" title="${_('Last updated on')} ${h.format_date(c.pull_request.updated_on)}">${h.format_date(c.pull_request.created_on)},</span>
54 <span class="pr-details-title-author-pref">${_('by')}</span>
54 <span class="pr-details-title-author-pref">${_('by')}</span>
55 </div>
55 </div>
56
56
57 <div class="pull-left">
57 <div class="pull-left">
58 ${self.gravatar_with_user(c.pull_request.author.email, 16, tooltip=True)}
58 ${self.gravatar_with_user(c.pull_request.author.email, 16, tooltip=True)}
59 </div>
59 </div>
60
60
61 %if c.allowed_to_update:
61 %if c.allowed_to_update:
62 <div class="pull-right">
62 <div class="pull-right">
63 <div id="edit_pull_request" class="action_button pr-save" style="display: none;">${_('Update title & description')}</div>
63 <div id="edit_pull_request" class="action_button pr-save" style="display: none;">${_('Update title & description')}</div>
64 <div id="delete_pullrequest" class="action_button pr-save ${('' if c.allowed_to_delete else 'disabled' )}" style="display: none;">
64 <div id="delete_pullrequest" class="action_button pr-save ${('' if c.allowed_to_delete else 'disabled' )}" style="display: none;">
65 % if c.allowed_to_delete:
65 % if c.allowed_to_delete:
66 ${h.secure_form(h.route_path('pullrequest_delete', repo_name=c.pull_request.target_repo.repo_name, pull_request_id=c.pull_request.pull_request_id), request=request)}
66 ${h.secure_form(h.route_path('pullrequest_delete', repo_name=c.pull_request.target_repo.repo_name, pull_request_id=c.pull_request.pull_request_id), request=request)}
67 ${h.submit('remove_%s' % c.pull_request.pull_request_id, _('Delete pull request'),
67 ${h.submit('remove_%s' % c.pull_request.pull_request_id, _('Delete pull request'),
68 class_="btn btn-link btn-danger no-margin",onclick="return confirm('"+_('Confirm to delete this pull request')+"');")}
68 class_="btn btn-link btn-danger no-margin",onclick="return confirm('"+_('Confirm to delete this pull request')+"');")}
69 ${h.end_form()}
69 ${h.end_form()}
70 % else:
70 % else:
71 <span class="tooltip" title="${_('Not allowed to delete this pull request')}">${_('Delete pull request')}</span>
71 <span class="tooltip" title="${_('Not allowed to delete this pull request')}">${_('Delete pull request')}</span>
72 % endif
72 % endif
73 </div>
73 </div>
74 <div id="open_edit_pullrequest" class="action_button">${_('Edit')}</div>
74 <div id="open_edit_pullrequest" class="action_button">${_('Edit')}</div>
75 <div id="close_edit_pullrequest" class="action_button" style="display: none;">${_('Cancel')}</div>
75 <div id="close_edit_pullrequest" class="action_button" style="display: none;">${_('Cancel')}</div>
76 </div>
76 </div>
77
77
78 %endif
78 %endif
79 </div>
79 </div>
80
80
81 <div id="pr-desc" class="input" title="${_('Rendered using {} renderer').format(c.renderer)}">
81 <div id="pr-desc" class="input" title="${_('Rendered using {} renderer').format(c.renderer)}">
82 ${h.render(c.pull_request.description, renderer=c.renderer, repo_name=c.repo_name)}
82 ${h.render(c.pull_request.description, renderer=c.renderer, repo_name=c.repo_name)}
83 </div>
83 </div>
84
84
85 <div id="pr-desc-edit" class="input textarea" style="display: none;">
85 <div id="pr-desc-edit" class="input textarea" style="display: none;">
86 <input id="pr-renderer-input" type="hidden" name="description_renderer" value="${c.visual.default_renderer}">
86 <input id="pr-renderer-input" type="hidden" name="description_renderer" value="${c.visual.default_renderer}">
87 ${dt.markup_form('pr-description-input', form_text=c.pull_request.description)}
87 ${dt.markup_form('pr-description-input', form_text=c.pull_request.description)}
88 </div>
88 </div>
89
89
90 <div id="summary" class="fields pr-details-content">
90 <div id="summary" class="fields pr-details-content">
91
91
92 ## review
92 ## review
93 <div class="field">
93 <div class="field">
94 <div class="label-pr-detail">
94 <div class="label-pr-detail">
95 <label>${_('Review status')}:</label>
95 <label>${_('Review status')}:</label>
96 </div>
96 </div>
97 <div class="input">
97 <div class="input">
98 %if c.pull_request_review_status:
98 %if c.pull_request_review_status:
99 <div class="tag status-tag-${c.pull_request_review_status}">
99 <div class="tag status-tag-${c.pull_request_review_status}">
100 <i class="icon-circle review-status-${c.pull_request_review_status}"></i>
100 <i class="icon-circle review-status-${c.pull_request_review_status}"></i>
101 <span class="changeset-status-lbl">
101 <span class="changeset-status-lbl">
102 %if c.pull_request.is_closed():
102 %if c.pull_request.is_closed():
103 ${_('Closed')},
103 ${_('Closed')},
104 %endif
104 %endif
105
105
106 ${h.commit_status_lbl(c.pull_request_review_status)}
106 ${h.commit_status_lbl(c.pull_request_review_status)}
107
107
108 </span>
108 </span>
109 </div>
109 </div>
110 - ${_ungettext('calculated based on {} reviewer vote', 'calculated based on {} reviewers votes', len(c.pull_request_reviewers)).format(len(c.pull_request_reviewers))}
110 - ${_ungettext('calculated based on {} reviewer vote', 'calculated based on {} reviewers votes', len(c.pull_request_reviewers)).format(len(c.pull_request_reviewers))}
111 %endif
111 %endif
112 </div>
112 </div>
113 </div>
113 </div>
114
114
115 ## source
115 ## source
116 <div class="field">
116 <div class="field">
117 <div class="label-pr-detail">
117 <div class="label-pr-detail">
118 <label>${_('Commit flow')}:</label>
118 <label>${_('Commit flow')}:</label>
119 </div>
119 </div>
120 <div class="input">
120 <div class="input">
121 <div class="pr-commit-flow">
121 <div class="pr-commit-flow">
122 ## Source
122 ## Source
123 %if c.pull_request.source_ref_parts.type == 'branch':
123 %if c.pull_request.source_ref_parts.type == 'branch':
124 <a href="${h.route_path('repo_commits', repo_name=c.pull_request.source_repo.repo_name, _query=dict(branch=c.pull_request.source_ref_parts.name))}"><code class="pr-source-info">${c.pull_request.source_ref_parts.type}:${c.pull_request.source_ref_parts.name}</code></a>
124 <a href="${h.route_path('repo_commits', repo_name=c.pull_request.source_repo.repo_name, _query=dict(branch=c.pull_request.source_ref_parts.name))}"><code class="pr-source-info">${c.pull_request.source_ref_parts.type}:${c.pull_request.source_ref_parts.name}</code></a>
125 %else:
125 %else:
126 <code class="pr-source-info">${'{}:{}'.format(c.pull_request.source_ref_parts.type, c.pull_request.source_ref_parts.name)}</code>
126 <code class="pr-source-info">${'{}:{}'.format(c.pull_request.source_ref_parts.type, c.pull_request.source_ref_parts.name)}</code>
127 %endif
127 %endif
128 ${_('of')} <a href="${h.route_path('repo_summary', repo_name=c.pull_request.source_repo.repo_name)}">${c.pull_request.source_repo.repo_name}</a>
128 ${_('of')} <a href="${h.route_path('repo_summary', repo_name=c.pull_request.source_repo.repo_name)}">${c.pull_request.source_repo.repo_name}</a>
129 &rarr;
129 &rarr;
130 ## Target
130 ## Target
131 %if c.pull_request.target_ref_parts.type == 'branch':
131 %if c.pull_request.target_ref_parts.type == 'branch':
132 <a href="${h.route_path('repo_commits', repo_name=c.pull_request.target_repo.repo_name, _query=dict(branch=c.pull_request.target_ref_parts.name))}"><code class="pr-target-info">${c.pull_request.target_ref_parts.type}:${c.pull_request.target_ref_parts.name}</code></a>
132 <a href="${h.route_path('repo_commits', repo_name=c.pull_request.target_repo.repo_name, _query=dict(branch=c.pull_request.target_ref_parts.name))}"><code class="pr-target-info">${c.pull_request.target_ref_parts.type}:${c.pull_request.target_ref_parts.name}</code></a>
133 %else:
133 %else:
134 <code class="pr-target-info">${'{}:{}'.format(c.pull_request.target_ref_parts.type, c.pull_request.target_ref_parts.name)}</code>
134 <code class="pr-target-info">${'{}:{}'.format(c.pull_request.target_ref_parts.type, c.pull_request.target_ref_parts.name)}</code>
135 %endif
135 %endif
136
136
137 ${_('of')} <a href="${h.route_path('repo_summary', repo_name=c.pull_request.target_repo.repo_name)}">${c.pull_request.target_repo.repo_name}</a>
137 ${_('of')} <a href="${h.route_path('repo_summary', repo_name=c.pull_request.target_repo.repo_name)}">${c.pull_request.target_repo.repo_name}</a>
138
138
139 <a class="source-details-action" href="#expand-source-details" onclick="return versionController.toggleElement(this, '.source-details')" data-toggle-on='<i class="icon-angle-down">more details</i>' data-toggle-off='<i class="icon-angle-up">less details</i>'>
139 <a class="source-details-action" href="#expand-source-details" onclick="return versionController.toggleElement(this, '.source-details')" data-toggle-on='<i class="icon-angle-down">more details</i>' data-toggle-off='<i class="icon-angle-up">less details</i>'>
140 <i class="icon-angle-down">more details</i>
140 <i class="icon-angle-down">more details</i>
141 </a>
141 </a>
142
142
143 </div>
143 </div>
144
144
145 <div class="source-details" style="display: none">
145 <div class="source-details" style="display: none">
146
146
147 <ul>
147 <ul>
148
148
149 ## common ancestor
149 ## common ancestor
150 <li>
150 <li>
151 ${_('Common ancestor')}:
151 ${_('Common ancestor')}:
152 % if c.ancestor_commit:
152 % if c.ancestor_commit:
153 <a href="${h.route_path('repo_commit', repo_name=c.target_repo.repo_name, commit_id=c.ancestor_commit.raw_id)}">${h.show_id(c.ancestor_commit)}</a>
153 <a href="${h.route_path('repo_commit', repo_name=c.target_repo.repo_name, commit_id=c.ancestor_commit.raw_id)}">${h.show_id(c.ancestor_commit)}</a>
154 % else:
154 % else:
155 ${_('not available')}
155 ${_('not available')}
156 % endif
156 % endif
157 </li>
157 </li>
158
158
159 ## pull url
159 ## pull url
160 <li>
160 <li>
161 %if h.is_hg(c.pull_request.source_repo):
161 %if h.is_hg(c.pull_request.source_repo):
162 <% clone_url = 'hg pull -r {} {}'.format(h.short_id(c.source_ref), c.pull_request.source_repo.clone_url()) %>
162 <% clone_url = 'hg pull -r {} {}'.format(h.short_id(c.source_ref), c.pull_request.source_repo.clone_url()) %>
163 %elif h.is_git(c.pull_request.source_repo):
163 %elif h.is_git(c.pull_request.source_repo):
164 <% clone_url = 'git pull {} {}'.format(c.pull_request.source_repo.clone_url(), c.pull_request.source_ref_parts.name) %>
164 <% clone_url = 'git pull {} {}'.format(c.pull_request.source_repo.clone_url(), c.pull_request.source_ref_parts.name) %>
165 %endif
165 %endif
166
166
167 <span>${_('Pull changes from source')}</span>: <input type="text" class="input-monospace pr-pullinfo" value="${clone_url}" readonly="readonly">
167 <span>${_('Pull changes from source')}</span>: <input type="text" class="input-monospace pr-pullinfo" value="${clone_url}" readonly="readonly">
168 <i class="tooltip icon-clipboard clipboard-action pull-right pr-pullinfo-copy" data-clipboard-text="${clone_url}" title="${_('Copy the pull url')}"></i>
168 <i class="tooltip icon-clipboard clipboard-action pull-right pr-pullinfo-copy" data-clipboard-text="${clone_url}" title="${_('Copy the pull url')}"></i>
169 </li>
169 </li>
170
170
171 ## Shadow repo
171 ## Shadow repo
172 <li>
172 <li>
173 % if not c.pull_request.is_closed() and c.pull_request.shadow_merge_ref:
173 % if not c.pull_request.is_closed() and c.pull_request.shadow_merge_ref:
174 %if h.is_hg(c.pull_request.target_repo):
174 %if h.is_hg(c.pull_request.target_repo):
175 <% clone_url = 'hg clone --update {} {} pull-request-{}'.format(c.pull_request.shadow_merge_ref.name, c.shadow_clone_url, c.pull_request.pull_request_id) %>
175 <% clone_url = 'hg clone --update {} {} pull-request-{}'.format(c.pull_request.shadow_merge_ref.name, c.shadow_clone_url, c.pull_request.pull_request_id) %>
176 %elif h.is_git(c.pull_request.target_repo):
176 %elif h.is_git(c.pull_request.target_repo):
177 <% clone_url = 'git clone --branch {} {} pull-request-{}'.format(c.pull_request.shadow_merge_ref.name, c.shadow_clone_url, c.pull_request.pull_request_id) %>
177 <% clone_url = 'git clone --branch {} {} pull-request-{}'.format(c.pull_request.shadow_merge_ref.name, c.shadow_clone_url, c.pull_request.pull_request_id) %>
178 %endif
178 %endif
179
179
180 <span class="tooltip" title="${_('Clone repository in its merged state using shadow repository')}">${_('Clone from shadow repository')}</span>: <input type="text" class="input-monospace pr-mergeinfo" value="${clone_url}" readonly="readonly">
180 <span class="tooltip" title="${_('Clone repository in its merged state using shadow repository')}">${_('Clone from shadow repository')}</span>: <input type="text" class="input-monospace pr-mergeinfo" value="${clone_url}" readonly="readonly">
181 <i class="tooltip icon-clipboard clipboard-action pull-right pr-mergeinfo-copy" data-clipboard-text="${clone_url}" title="${_('Copy the clone url')}"></i>
181 <i class="tooltip icon-clipboard clipboard-action pull-right pr-mergeinfo-copy" data-clipboard-text="${clone_url}" title="${_('Copy the clone url')}"></i>
182
182
183 % else:
183 % else:
184 <div class="">
184 <div class="">
185 ${_('Shadow repository data not available')}.
185 ${_('Shadow repository data not available')}.
186 </div>
186 </div>
187 % endif
187 % endif
188 </li>
188 </li>
189
189
190 </ul>
190 </ul>
191
191
192 </div>
192 </div>
193
193
194 </div>
194 </div>
195
195
196 </div>
196 </div>
197
197
198 ## versions
198 ## versions
199 <div class="field">
199 <div class="field">
200 <div class="label-pr-detail">
200 <div class="label-pr-detail">
201 <label>${_('Versions')}:</label>
201 <label>${_('Versions')}:</label>
202 </div>
202 </div>
203
203
204 <% outdated_comm_count_ver = len(c.inline_versions[None]['outdated']) %>
204 <% outdated_comm_count_ver = len(c.inline_versions[None]['outdated']) %>
205 <% general_outdated_comm_count_ver = len(c.comment_versions[None]['outdated']) %>
205 <% general_outdated_comm_count_ver = len(c.comment_versions[None]['outdated']) %>
206
206
207 <div class="pr-versions">
207 <div class="pr-versions">
208 % if c.show_version_changes:
208 % if c.show_version_changes:
209 <% outdated_comm_count_ver = len(c.inline_versions[c.at_version_num]['outdated']) %>
209 <% outdated_comm_count_ver = len(c.inline_versions[c.at_version_num]['outdated']) %>
210 <% general_outdated_comm_count_ver = len(c.comment_versions[c.at_version_num]['outdated']) %>
210 <% general_outdated_comm_count_ver = len(c.comment_versions[c.at_version_num]['outdated']) %>
211 ${_ungettext('{} version available for this pull request, ', '{} versions available for this pull request, ', len(c.versions)).format(len(c.versions))}
211 ${_ungettext('{} version available for this pull request, ', '{} versions available for this pull request, ', len(c.versions)).format(len(c.versions))}
212 <a id="show-pr-versions" onclick="return versionController.toggleVersionView(this)" href="#show-pr-versions"
212 <a id="show-pr-versions" onclick="return versionController.toggleVersionView(this)" href="#show-pr-versions"
213 data-toggle-on="${_('show versions')}."
213 data-toggle-on="${_('show versions')}."
214 data-toggle-off="${_('hide versions')}.">
214 data-toggle-off="${_('hide versions')}.">
215 ${_('show versions')}.
215 ${_('show versions')}.
216 </a>
216 </a>
217 <table>
217 <table>
218 ## SHOW ALL VERSIONS OF PR
218 ## SHOW ALL VERSIONS OF PR
219 <% ver_pr = None %>
219 <% ver_pr = None %>
220
220
221 % for data in reversed(list(enumerate(c.versions, 1))):
221 % for data in reversed(list(enumerate(c.versions, 1))):
222 <% ver_pos = data[0] %>
222 <% ver_pos = data[0] %>
223 <% ver = data[1] %>
223 <% ver = data[1] %>
224 <% ver_pr = ver.pull_request_version_id %>
224 <% ver_pr = ver.pull_request_version_id %>
225 <% display_row = '' if c.at_version and (c.at_version_num == ver_pr or c.from_version_num == ver_pr) else 'none' %>
225 <% display_row = '' if c.at_version and (c.at_version_num == ver_pr or c.from_version_num == ver_pr) else 'none' %>
226
226
227 <tr class="version-pr" style="display: ${display_row}">
227 <tr class="version-pr" style="display: ${display_row}">
228 <td>
228 <td>
229 <code>
229 <code>
230 <a href="${request.current_route_path(_query=dict(version=ver_pr or 'latest'))}">v${ver_pos}</a>
230 <a href="${request.current_route_path(_query=dict(version=ver_pr or 'latest'))}">v${ver_pos}</a>
231 </code>
231 </code>
232 </td>
232 </td>
233 <td>
233 <td>
234 <input ${('checked="checked"' if c.from_version_num == ver_pr else '')} class="compare-radio-button" type="radio" name="ver_source" value="${ver_pr or 'latest'}" data-ver-pos="${ver_pos}"/>
234 <input ${('checked="checked"' if c.from_version_num == ver_pr else '')} class="compare-radio-button" type="radio" name="ver_source" value="${ver_pr or 'latest'}" data-ver-pos="${ver_pos}"/>
235 <input ${('checked="checked"' if c.at_version_num == ver_pr else '')} class="compare-radio-button" type="radio" name="ver_target" value="${ver_pr or 'latest'}" data-ver-pos="${ver_pos}"/>
235 <input ${('checked="checked"' if c.at_version_num == ver_pr else '')} class="compare-radio-button" type="radio" name="ver_target" value="${ver_pr or 'latest'}" data-ver-pos="${ver_pos}"/>
236 </td>
236 </td>
237 <td>
237 <td>
238 <% review_status = c.review_versions[ver_pr].status if ver_pr in c.review_versions else 'not_reviewed' %>
238 <% review_status = c.review_versions[ver_pr].status if ver_pr in c.review_versions else 'not_reviewed' %>
239 <i class="tooltip icon-circle review-status-${review_status}" title="${_('Your review status at this version')}"></i>
239 <i class="tooltip icon-circle review-status-${review_status}" title="${_('Your review status at this version')}"></i>
240
240
241 </td>
241 </td>
242 <td>
242 <td>
243 % if c.at_version_num != ver_pr:
243 % if c.at_version_num != ver_pr:
244 <i class="tooltip icon-comment" title="${_('Comments from pull request version v{0}').format(ver_pos)}"></i>
244 <i class="tooltip icon-comment" title="${_('Comments from pull request version v{0}').format(ver_pos)}"></i>
245 <code>
245 <code>
246 General:${len(c.comment_versions[ver_pr]['at'])} / Inline:${len(c.inline_versions[ver_pr]['at'])}
246 General:${len(c.comment_versions[ver_pr]['at'])} / Inline:${len(c.inline_versions[ver_pr]['at'])}
247 </code>
247 </code>
248 % endif
248 % endif
249 </td>
249 </td>
250 <td>
250 <td>
251 ##<code>${ver.source_ref_parts.commit_id[:6]}</code>
251 ##<code>${ver.source_ref_parts.commit_id[:6]}</code>
252 </td>
252 </td>
253 <td>
253 <td>
254 <code>${h.age_component(ver.updated_on, time_is_local=True, tooltip=False)}</code>
254 <code>${h.age_component(ver.updated_on, time_is_local=True, tooltip=False)}</code>
255 </td>
255 </td>
256 </tr>
256 </tr>
257 % endfor
257 % endfor
258
258
259 <tr>
259 <tr>
260 <td colspan="6">
260 <td colspan="6">
261 <button id="show-version-diff" onclick="return versionController.showVersionDiff()" class="btn btn-sm" style="display: none"
261 <button id="show-version-diff" onclick="return versionController.showVersionDiff()" class="btn btn-sm" style="display: none"
262 data-label-text-locked="${_('select versions to show changes')}"
262 data-label-text-locked="${_('select versions to show changes')}"
263 data-label-text-diff="${_('show changes between versions')}"
263 data-label-text-diff="${_('show changes between versions')}"
264 data-label-text-show="${_('show pull request for this version')}"
264 data-label-text-show="${_('show pull request for this version')}"
265 >
265 >
266 ${_('select versions to show changes')}
266 ${_('select versions to show changes')}
267 </button>
267 </button>
268 </td>
268 </td>
269 </tr>
269 </tr>
270 </table>
270 </table>
271 % else:
271 % else:
272 <div>
272 <div>
273 ${_('Pull request versions not available')}.
273 ${_('Pull request versions not available')}.
274 </div>
274 </div>
275 % endif
275 % endif
276 </div>
276 </div>
277 </div>
277 </div>
278
278
279 </div>
279 </div>
280
280
281 </div>
281 </div>
282
282
283 ## REVIEW RULES
283 ## REVIEW RULES
284 <div id="review_rules" style="display: none" class="reviewers-title block-right">
284 <div id="review_rules" style="display: none" class="reviewers-title block-right">
285 <div class="pr-details-title">
285 <div class="pr-details-title">
286 ${_('Reviewer rules')}
286 ${_('Reviewer rules')}
287 %if c.allowed_to_update:
287 %if c.allowed_to_update:
288 <span id="close_edit_reviewers" class="block-right action_button last-item" style="display: none;">${_('Close')}</span>
288 <span id="close_edit_reviewers" class="block-right action_button last-item" style="display: none;">${_('Close')}</span>
289 %endif
289 %endif
290 </div>
290 </div>
291 <div class="pr-reviewer-rules">
291 <div class="pr-reviewer-rules">
292 ## review rules will be appended here, by default reviewers logic
292 ## review rules will be appended here, by default reviewers logic
293 </div>
293 </div>
294 <input id="review_data" type="hidden" name="review_data" value="">
294 <input id="review_data" type="hidden" name="review_data" value="">
295 </div>
295 </div>
296
296
297 ## REVIEWERS
297 ## REVIEWERS
298 <div class="reviewers-title block-right">
298 <div class="reviewers-title block-right">
299 <div class="pr-details-title">
299 <div class="pr-details-title">
300 ${_('Pull request reviewers')}
300 ${_('Pull request reviewers')}
301 %if c.allowed_to_update:
301 %if c.allowed_to_update:
302 <span id="open_edit_reviewers" class="block-right action_button last-item">${_('Edit')}</span>
302 <span id="open_edit_reviewers" class="block-right action_button last-item">${_('Edit')}</span>
303 %endif
303 %endif
304 </div>
304 </div>
305 </div>
305 </div>
306 <div id="reviewers" class="block-right pr-details-content reviewers">
306 <div id="reviewers" class="block-right pr-details-content reviewers">
307
307
308 ## members redering block
308 ## members redering block
309 <input type="hidden" name="__start__" value="review_members:sequence">
309 <input type="hidden" name="__start__" value="review_members:sequence">
310 <ul id="review_members" class="group_members">
310 <ul id="review_members" class="group_members">
311
311
312 % for review_obj, member, reasons, mandatory, status in c.pull_request_reviewers:
312 % for review_obj, member, reasons, mandatory, status in c.pull_request_reviewers:
313 <script>
313 <script>
314 var member = ${h.json.dumps(h.reviewer_as_json(member, reasons=reasons, mandatory=mandatory, user_group=review_obj.rule_user_group_data()))|n};
314 var member = ${h.json.dumps(h.reviewer_as_json(member, reasons=reasons, mandatory=mandatory, user_group=review_obj.rule_user_group_data()))|n};
315 var status = "${(status[0][1].status if status else 'not_reviewed')}";
315 var status = "${(status[0][1].status if status else 'not_reviewed')}";
316 var status_lbl = "${h.commit_status_lbl(status[0][1].status if status else 'not_reviewed')}";
316 var status_lbl = "${h.commit_status_lbl(status[0][1].status if status else 'not_reviewed')}";
317 var allowed_to_update = ${h.json.dumps(c.allowed_to_update)};
317 var allowed_to_update = ${h.json.dumps(c.allowed_to_update)};
318
318
319 var entry = renderTemplate('reviewMemberEntry', {
319 var entry = renderTemplate('reviewMemberEntry', {
320 'member': member,
320 'member': member,
321 'mandatory': member.mandatory,
321 'mandatory': member.mandatory,
322 'reasons': member.reasons,
322 'reasons': member.reasons,
323 'allowed_to_update': allowed_to_update,
323 'allowed_to_update': allowed_to_update,
324 'review_status': status,
324 'review_status': status,
325 'review_status_label': status_lbl,
325 'review_status_label': status_lbl,
326 'user_group': member.user_group,
326 'user_group': member.user_group,
327 'create': false
327 'create': false
328 });
328 });
329 $('#review_members').append(entry)
329 $('#review_members').append(entry)
330 </script>
330 </script>
331
331
332 % endfor
332 % endfor
333
333
334 </ul>
334 </ul>
335
335
336 <input type="hidden" name="__end__" value="review_members:sequence">
336 <input type="hidden" name="__end__" value="review_members:sequence">
337 ## end members redering block
337 ## end members redering block
338
338
339 %if not c.pull_request.is_closed():
339 %if not c.pull_request.is_closed():
340 <div id="add_reviewer" class="ac" style="display: none;">
340 <div id="add_reviewer" class="ac" style="display: none;">
341 %if c.allowed_to_update:
341 %if c.allowed_to_update:
342 % if not c.forbid_adding_reviewers:
342 % if not c.forbid_adding_reviewers:
343 <div id="add_reviewer_input" class="reviewer_ac">
343 <div id="add_reviewer_input" class="reviewer_ac">
344 ${h.text('user', class_='ac-input', placeholder=_('Add reviewer or reviewer group'))}
344 ${h.text('user', class_='ac-input', placeholder=_('Add reviewer or reviewer group'))}
345 <div id="reviewers_container"></div>
345 <div id="reviewers_container"></div>
346 </div>
346 </div>
347 % endif
347 % endif
348 <div class="pull-right">
348 <div class="pull-right">
349 <button id="update_pull_request" class="btn btn-small no-margin">${_('Save Changes')}</button>
349 <button id="update_pull_request" class="btn btn-small no-margin">${_('Save Changes')}</button>
350 </div>
350 </div>
351 %endif
351 %endif
352 </div>
352 </div>
353 %endif
353 %endif
354 </div>
354 </div>
355
355
356 ## TODOs will be listed here
356 ## TODOs will be listed here
357 <div class="reviewers-title block-right">
357 <div class="reviewers-title block-right">
358 <div class="pr-details-title">
358 <div class="pr-details-title">
359 ## Only show unresolved, that is only what matters
359 ## Only show unresolved, that is only what matters
360 TODO Comments - ${len(c.unresolved_comments)} / ${(len(c.unresolved_comments) + len(c.resolved_comments))}
360 TODO Comments - ${len(c.unresolved_comments)} / ${(len(c.unresolved_comments) + len(c.resolved_comments))}
361
361
362 % if not c.at_version:
362 % if not c.at_version:
363 % if c.resolved_comments:
363 % if c.resolved_comments:
364 <span class="block-right action_button last-item noselect" onclick="$('.unresolved-todo-text').toggle(); return versionController.toggleElement(this, '.unresolved-todo');" data-toggle-on="Show resolved" data-toggle-off="Hide resolved">Show resolved</span>
364 <span class="block-right action_button last-item noselect" onclick="$('.unresolved-todo-text').toggle(); return versionController.toggleElement(this, '.unresolved-todo');" data-toggle-on="Show resolved" data-toggle-off="Hide resolved">Show resolved</span>
365 % else:
365 % else:
366 <span class="block-right last-item noselect">Show resolved</span>
366 <span class="block-right last-item noselect">Show resolved</span>
367 % endif
367 % endif
368 % endif
368 % endif
369 </div>
369 </div>
370 </div>
370 </div>
371 <div class="block-right pr-details-content reviewers">
371 <div class="block-right pr-details-content reviewers">
372
372
373 <table class="todo-table">
373 <table class="todo-table">
374 <%
374 <%
375 def sorter(entry):
375 def sorter(entry):
376 user_id = entry.author.user_id
376 user_id = entry.author.user_id
377 resolved = '1' if entry.resolved else '0'
377 resolved = '1' if entry.resolved else '0'
378 if user_id == c.rhodecode_user.user_id:
378 if user_id == c.rhodecode_user.user_id:
379 # own comments first
379 # own comments first
380 user_id = 0
380 user_id = 0
381 return '{}_{}_{}'.format(resolved, user_id, str(entry.comment_id).zfill(100))
381 return '{}_{}_{}'.format(resolved, user_id, str(entry.comment_id).zfill(100))
382 %>
382 %>
383
383
384 % if c.at_version:
384 % if c.at_version:
385 <tr>
385 <tr>
386 <td class="unresolved-todo-text">${_('unresolved TODOs unavailable in this view')}.</td>
386 <td class="unresolved-todo-text">${_('unresolved TODOs unavailable in this view')}.</td>
387 </tr>
387 </tr>
388 % else:
388 % else:
389 % for todo_comment in sorted(c.unresolved_comments + c.resolved_comments, key=sorter):
389 % for todo_comment in sorted(c.unresolved_comments + c.resolved_comments, key=sorter):
390 <% resolved = todo_comment.resolved %>
390 <% resolved = todo_comment.resolved %>
391 % if inline:
391 % if inline:
392 <% outdated_at_ver = todo_comment.outdated_at_version(getattr(c, 'at_version_num', None)) %>
392 <% outdated_at_ver = todo_comment.outdated_at_version(getattr(c, 'at_version_num', None)) %>
393 % else:
393 % else:
394 <% outdated_at_ver = todo_comment.older_than_version(getattr(c, 'at_version_num', None)) %>
394 <% outdated_at_ver = todo_comment.older_than_version(getattr(c, 'at_version_num', None)) %>
395 % endif
395 % endif
396
396
397 <tr ${('class="unresolved-todo" style="display: none"' if resolved else '') |n}>
397 <tr ${('class="unresolved-todo" style="display: none"' if resolved else '') |n}>
398
398
399 <td class="td-todo-number">
399 <td class="td-todo-number">
400 % if resolved:
400 % if resolved:
401 <a class="permalink todo-resolved tooltip" title="${_('Resolved by comment #{}').format(todo_comment.resolved.comment_id)}" href="#comment-${todo_comment.comment_id}" onclick="return Rhodecode.comments.scrollToComment($('#comment-${todo_comment.comment_id}'), 0, ${h.json.dumps(outdated_at_ver)})">
401 <a class="permalink todo-resolved tooltip" title="${_('Resolved by comment #{}').format(todo_comment.resolved.comment_id)}" href="#comment-${todo_comment.comment_id}" onclick="return Rhodecode.comments.scrollToComment($('#comment-${todo_comment.comment_id}'), 0, ${h.json.dumps(outdated_at_ver)})">
402 <i class="icon-flag-filled"></i> ${todo_comment.comment_id}</a>
402 <i class="icon-flag-filled"></i> ${todo_comment.comment_id}</a>
403 % else:
403 % else:
404 <a class="permalink" href="#comment-${todo_comment.comment_id}" onclick="return Rhodecode.comments.scrollToComment($('#comment-${todo_comment.comment_id}'), 0, ${h.json.dumps(outdated_at_ver)})">
404 <a class="permalink" href="#comment-${todo_comment.comment_id}" onclick="return Rhodecode.comments.scrollToComment($('#comment-${todo_comment.comment_id}'), 0, ${h.json.dumps(outdated_at_ver)})">
405 <i class="icon-flag-filled"></i> ${todo_comment.comment_id}</a>
405 <i class="icon-flag-filled"></i> ${todo_comment.comment_id}</a>
406 % endif
406 % endif
407 </td>
407 </td>
408 <td class="td-todo-gravatar">
408 <td class="td-todo-gravatar">
409 ${base.gravatar(todo_comment.author.email, 16, user=todo_comment.author, tooltip=True, extra_class=['no-margin'])}
409 ${base.gravatar(todo_comment.author.email, 16, user=todo_comment.author, tooltip=True, extra_class=['no-margin'])}
410 </td>
410 </td>
411 <td class="todo-comment-text-wrapper">
411 <td class="todo-comment-text-wrapper">
412 <div class="todo-comment-text">
412 <div class="todo-comment-text">
413 <code>${h.chop_at_smart(todo_comment.text, '\n', suffix_if_chopped='...')}</code>
413 <code>${h.chop_at_smart(todo_comment.text, '\n', suffix_if_chopped='...')}</code>
414 </div>
414 </div>
415 </td>
415 </td>
416
416
417 </tr>
417 </tr>
418 % endfor
418 % endfor
419
419
420 % if len(c.unresolved_comments) == 0:
420 % if len(c.unresolved_comments) == 0:
421 <tr>
421 <tr>
422 <td class="unresolved-todo-text">${_('No unresolved TODOs')}.</td>
422 <td class="unresolved-todo-text">${_('No unresolved TODOs')}.</td>
423 </tr>
423 </tr>
424 % endif
424 % endif
425
425
426 % endif
426 % endif
427
427
428 </table>
428 </table>
429
429
430 </div>
430 </div>
431 </div>
431 </div>
432
432
433 </div>
433 </div>
434
434
435 <div class="box">
435 <div class="box">
436
436
437 % if c.state_progressing:
437 % if c.state_progressing:
438
438
439 <h2 style="text-align: center">
439 <h2 style="text-align: center">
440 ${_('Cannot show diff when pull request state is changing. Current progress state')}: <span class="tag tag-merge-state-${c.pull_request.state}">${c.pull_request.state}</span>
440 ${_('Cannot show diff when pull request state is changing. Current progress state')}: <span class="tag tag-merge-state-${c.pull_request.state}">${c.pull_request.state}</span>
441
442 % if c.is_super_admin:
443 <br/>
444 If you think this is an error try <a href="${h.current_route_path(request, force_state='created')}">forced state reset</a> to <span class="tag tag-merge-state-created">created</span> state.
445 % endif
441 </h2>
446 </h2>
442
447
443 % else:
448 % else:
444
449
445 ## Diffs rendered here
450 ## Diffs rendered here
446 <div class="table" >
451 <div class="table" >
447 <div id="changeset_compare_view_content">
452 <div id="changeset_compare_view_content">
448 ##CS
453 ##CS
449 % if c.missing_requirements:
454 % if c.missing_requirements:
450 <div class="box">
455 <div class="box">
451 <div class="alert alert-warning">
456 <div class="alert alert-warning">
452 <div>
457 <div>
453 <strong>${_('Missing requirements:')}</strong>
458 <strong>${_('Missing requirements:')}</strong>
454 ${_('These commits cannot be displayed, because this repository uses the Mercurial largefiles extension, which was not enabled.')}
459 ${_('These commits cannot be displayed, because this repository uses the Mercurial largefiles extension, which was not enabled.')}
455 </div>
460 </div>
456 </div>
461 </div>
457 </div>
462 </div>
458 % elif c.missing_commits:
463 % elif c.missing_commits:
459 <div class="box">
464 <div class="box">
460 <div class="alert alert-warning">
465 <div class="alert alert-warning">
461 <div>
466 <div>
462 <strong>${_('Missing commits')}:</strong>
467 <strong>${_('Missing commits')}:</strong>
463 ${_('This pull request cannot be displayed, because one or more commits no longer exist in the source repository.')}
468 ${_('This pull request cannot be displayed, because one or more commits no longer exist in the source repository.')}
464 ${_('Please update this pull request, push the commits back into the source repository, or consider closing this pull request.')}
469 ${_('Please update this pull request, push the commits back into the source repository, or consider closing this pull request.')}
465 ${_('Consider doing a {force_refresh_url} in case you think this is an error.').format(force_refresh_url=h.link_to('force refresh', h.current_route_path(request, force_refresh='1')))|n}
470 ${_('Consider doing a {force_refresh_url} in case you think this is an error.').format(force_refresh_url=h.link_to('force refresh', h.current_route_path(request, force_refresh='1')))|n}
466 </div>
471 </div>
467 </div>
472 </div>
468 </div>
473 </div>
469 % endif
474 % endif
470
475
471 <div class="compare_view_commits_title">
476 <div class="compare_view_commits_title">
472 % if not c.compare_mode:
477 % if not c.compare_mode:
473
478
474 % if c.at_version_pos:
479 % if c.at_version_pos:
475 <h4>
480 <h4>
476 ${_('Showing changes at v%d, commenting is disabled.') % c.at_version_pos}
481 ${_('Showing changes at v%d, commenting is disabled.') % c.at_version_pos}
477 </h4>
482 </h4>
478 % endif
483 % endif
479
484
480 <div class="pull-left">
485 <div class="pull-left">
481 <div class="btn-group">
486 <div class="btn-group">
482 <a class="${('collapsed' if c.collapse_all_commits else '')}" href="#expand-commits" onclick="toggleCommitExpand(this); return false" data-toggle-commits-cnt=${len(c.commit_ranges)} >
487 <a class="${('collapsed' if c.collapse_all_commits else '')}" href="#expand-commits" onclick="toggleCommitExpand(this); return false" data-toggle-commits-cnt=${len(c.commit_ranges)} >
483 % if c.collapse_all_commits:
488 % if c.collapse_all_commits:
484 <i class="icon-plus-squared-alt icon-no-margin"></i>
489 <i class="icon-plus-squared-alt icon-no-margin"></i>
485 ${_ungettext('Expand {} commit', 'Expand {} commits', len(c.commit_ranges)).format(len(c.commit_ranges))}
490 ${_ungettext('Expand {} commit', 'Expand {} commits', len(c.commit_ranges)).format(len(c.commit_ranges))}
486 % else:
491 % else:
487 <i class="icon-minus-squared-alt icon-no-margin"></i>
492 <i class="icon-minus-squared-alt icon-no-margin"></i>
488 ${_ungettext('Collapse {} commit', 'Collapse {} commits', len(c.commit_ranges)).format(len(c.commit_ranges))}
493 ${_ungettext('Collapse {} commit', 'Collapse {} commits', len(c.commit_ranges)).format(len(c.commit_ranges))}
489 % endif
494 % endif
490 </a>
495 </a>
491 </div>
496 </div>
492 </div>
497 </div>
493
498
494 <div class="pull-right">
499 <div class="pull-right">
495 % if c.allowed_to_update and not c.pull_request.is_closed():
500 % if c.allowed_to_update and not c.pull_request.is_closed():
496
501
497 <div class="btn-group btn-group-actions">
502 <div class="btn-group btn-group-actions">
498 <a id="update_commits" class="btn btn-primary no-margin" onclick="updateController.updateCommits(this); return false">
503 <a id="update_commits" class="btn btn-primary no-margin" onclick="updateController.updateCommits(this); return false">
499 ${_('Update commits')}
504 ${_('Update commits')}
500 </a>
505 </a>
501
506
502 <a id="update_commits_switcher" class="tooltip btn btn-primary" style="margin-left: -1px" data-toggle="dropdown" aria-pressed="false" role="button" title="${_('more update options')}">
507 <a id="update_commits_switcher" class="tooltip btn btn-primary" style="margin-left: -1px" data-toggle="dropdown" aria-pressed="false" role="button" title="${_('more update options')}">
503 <i class="icon-down"></i>
508 <i class="icon-down"></i>
504 </a>
509 </a>
505
510
506 <div class="btn-action-switcher-container" id="update-commits-switcher">
511 <div class="btn-action-switcher-container" id="update-commits-switcher">
507 <ul class="btn-action-switcher" role="menu">
512 <ul class="btn-action-switcher" role="menu">
508 <li>
513 <li>
509 <a href="#forceUpdate" onclick="updateController.forceUpdateCommits(this); return false">
514 <a href="#forceUpdate" onclick="updateController.forceUpdateCommits(this); return false">
510 ${_('Force update commits')}
515 ${_('Force update commits')}
511 </a>
516 </a>
512 <div class="action-help-block">
517 <div class="action-help-block">
513 ${_('Update commits and force refresh this pull request.')}
518 ${_('Update commits and force refresh this pull request.')}
514 </div>
519 </div>
515 </li>
520 </li>
516 </ul>
521 </ul>
517 </div>
522 </div>
518 </div>
523 </div>
519
524
520 % else:
525 % else:
521 <a class="tooltip btn disabled pull-right" disabled="disabled" title="${_('Update is disabled for current view')}">${_('Update commits')}</a>
526 <a class="tooltip btn disabled pull-right" disabled="disabled" title="${_('Update is disabled for current view')}">${_('Update commits')}</a>
522 % endif
527 % endif
523
528
524 </div>
529 </div>
525 % endif
530 % endif
526 </div>
531 </div>
527
532
528 % if not c.missing_commits:
533 % if not c.missing_commits:
529 % if c.compare_mode:
534 % if c.compare_mode:
530 % if c.at_version:
535 % if c.at_version:
531 <h4>
536 <h4>
532 ${_('Commits and changes between v{ver_from} and {ver_to} of this pull request, commenting is disabled').format(ver_from=c.from_version_pos, ver_to=c.at_version_pos if c.at_version_pos else 'latest')}:
537 ${_('Commits and changes between v{ver_from} and {ver_to} of this pull request, commenting is disabled').format(ver_from=c.from_version_pos, ver_to=c.at_version_pos if c.at_version_pos else 'latest')}:
533 </h4>
538 </h4>
534
539
535 <div class="subtitle-compare">
540 <div class="subtitle-compare">
536 ${_('commits added: {}, removed: {}').format(len(c.commit_changes_summary.added), len(c.commit_changes_summary.removed))}
541 ${_('commits added: {}, removed: {}').format(len(c.commit_changes_summary.added), len(c.commit_changes_summary.removed))}
537 </div>
542 </div>
538
543
539 <div class="container">
544 <div class="container">
540 <table class="rctable compare_view_commits">
545 <table class="rctable compare_view_commits">
541 <tr>
546 <tr>
542 <th></th>
547 <th></th>
543 <th>${_('Time')}</th>
548 <th>${_('Time')}</th>
544 <th>${_('Author')}</th>
549 <th>${_('Author')}</th>
545 <th>${_('Commit')}</th>
550 <th>${_('Commit')}</th>
546 <th></th>
551 <th></th>
547 <th>${_('Description')}</th>
552 <th>${_('Description')}</th>
548 </tr>
553 </tr>
549
554
550 % for c_type, commit in c.commit_changes:
555 % for c_type, commit in c.commit_changes:
551 % if c_type in ['a', 'r']:
556 % if c_type in ['a', 'r']:
552 <%
557 <%
553 if c_type == 'a':
558 if c_type == 'a':
554 cc_title = _('Commit added in displayed changes')
559 cc_title = _('Commit added in displayed changes')
555 elif c_type == 'r':
560 elif c_type == 'r':
556 cc_title = _('Commit removed in displayed changes')
561 cc_title = _('Commit removed in displayed changes')
557 else:
562 else:
558 cc_title = ''
563 cc_title = ''
559 %>
564 %>
560 <tr id="row-${commit.raw_id}" commit_id="${commit.raw_id}" class="compare_select">
565 <tr id="row-${commit.raw_id}" commit_id="${commit.raw_id}" class="compare_select">
561 <td>
566 <td>
562 <div class="commit-change-indicator color-${c_type}-border">
567 <div class="commit-change-indicator color-${c_type}-border">
563 <div class="commit-change-content color-${c_type} tooltip" title="${h.tooltip(cc_title)}">
568 <div class="commit-change-content color-${c_type} tooltip" title="${h.tooltip(cc_title)}">
564 ${c_type.upper()}
569 ${c_type.upper()}
565 </div>
570 </div>
566 </div>
571 </div>
567 </td>
572 </td>
568 <td class="td-time">
573 <td class="td-time">
569 ${h.age_component(commit.date)}
574 ${h.age_component(commit.date)}
570 </td>
575 </td>
571 <td class="td-user">
576 <td class="td-user">
572 ${base.gravatar_with_user(commit.author, 16, tooltip=True)}
577 ${base.gravatar_with_user(commit.author, 16, tooltip=True)}
573 </td>
578 </td>
574 <td class="td-hash">
579 <td class="td-hash">
575 <code>
580 <code>
576 <a href="${h.route_path('repo_commit', repo_name=c.target_repo.repo_name, commit_id=commit.raw_id)}">
581 <a href="${h.route_path('repo_commit', repo_name=c.target_repo.repo_name, commit_id=commit.raw_id)}">
577 r${commit.idx}:${h.short_id(commit.raw_id)}
582 r${commit.idx}:${h.short_id(commit.raw_id)}
578 </a>
583 </a>
579 ${h.hidden('revisions', commit.raw_id)}
584 ${h.hidden('revisions', commit.raw_id)}
580 </code>
585 </code>
581 </td>
586 </td>
582 <td class="td-message expand_commit" data-commit-id="${commit.raw_id}" title="${_( 'Expand commit message')}" onclick="commitsController.expandCommit(this); return false">
587 <td class="td-message expand_commit" data-commit-id="${commit.raw_id}" title="${_( 'Expand commit message')}" onclick="commitsController.expandCommit(this); return false">
583 <i class="icon-expand-linked"></i>
588 <i class="icon-expand-linked"></i>
584 </td>
589 </td>
585 <td class="mid td-description">
590 <td class="mid td-description">
586 <div class="log-container truncate-wrap">
591 <div class="log-container truncate-wrap">
587 <div class="message truncate" id="c-${commit.raw_id}" data-message-raw="${commit.message}">${h.urlify_commit_message(commit.message, c.repo_name)}</div>
592 <div class="message truncate" id="c-${commit.raw_id}" data-message-raw="${commit.message}">${h.urlify_commit_message(commit.message, c.repo_name)}</div>
588 </div>
593 </div>
589 </td>
594 </td>
590 </tr>
595 </tr>
591 % endif
596 % endif
592 % endfor
597 % endfor
593 </table>
598 </table>
594 </div>
599 </div>
595
600
596 % endif
601 % endif
597
602
598 % else:
603 % else:
599 <%include file="/compare/compare_commits.mako" />
604 <%include file="/compare/compare_commits.mako" />
600 % endif
605 % endif
601
606
602 <div class="cs_files">
607 <div class="cs_files">
603 <%namespace name="cbdiffs" file="/codeblocks/diffs.mako"/>
608 <%namespace name="cbdiffs" file="/codeblocks/diffs.mako"/>
604 % if c.at_version:
609 % if c.at_version:
605 <% c.inline_cnt = len(c.inline_versions[c.at_version_num]['display']) %>
610 <% c.inline_cnt = len(c.inline_versions[c.at_version_num]['display']) %>
606 <% c.comments = c.comment_versions[c.at_version_num]['display'] %>
611 <% c.comments = c.comment_versions[c.at_version_num]['display'] %>
607 % else:
612 % else:
608 <% c.inline_cnt = len(c.inline_versions[c.at_version_num]['until']) %>
613 <% c.inline_cnt = len(c.inline_versions[c.at_version_num]['until']) %>
609 <% c.comments = c.comment_versions[c.at_version_num]['until'] %>
614 <% c.comments = c.comment_versions[c.at_version_num]['until'] %>
610 % endif
615 % endif
611
616
612 <%
617 <%
613 pr_menu_data = {
618 pr_menu_data = {
614 'outdated_comm_count_ver': outdated_comm_count_ver
619 'outdated_comm_count_ver': outdated_comm_count_ver
615 }
620 }
616 %>
621 %>
617
622
618 ${cbdiffs.render_diffset_menu(c.diffset, range_diff_on=c.range_diff_on)}
623 ${cbdiffs.render_diffset_menu(c.diffset, range_diff_on=c.range_diff_on)}
619
624
620 % if c.range_diff_on:
625 % if c.range_diff_on:
621 % for commit in c.commit_ranges:
626 % for commit in c.commit_ranges:
622 ${cbdiffs.render_diffset(
627 ${cbdiffs.render_diffset(
623 c.changes[commit.raw_id],
628 c.changes[commit.raw_id],
624 commit=commit, use_comments=True,
629 commit=commit, use_comments=True,
625 collapse_when_files_over=5,
630 collapse_when_files_over=5,
626 disable_new_comments=True,
631 disable_new_comments=True,
627 deleted_files_comments=c.deleted_files_comments,
632 deleted_files_comments=c.deleted_files_comments,
628 inline_comments=c.inline_comments,
633 inline_comments=c.inline_comments,
629 pull_request_menu=pr_menu_data, show_todos=False)}
634 pull_request_menu=pr_menu_data, show_todos=False)}
630 % endfor
635 % endfor
631 % else:
636 % else:
632 ${cbdiffs.render_diffset(
637 ${cbdiffs.render_diffset(
633 c.diffset, use_comments=True,
638 c.diffset, use_comments=True,
634 collapse_when_files_over=30,
639 collapse_when_files_over=30,
635 disable_new_comments=not c.allowed_to_comment,
640 disable_new_comments=not c.allowed_to_comment,
636 deleted_files_comments=c.deleted_files_comments,
641 deleted_files_comments=c.deleted_files_comments,
637 inline_comments=c.inline_comments,
642 inline_comments=c.inline_comments,
638 pull_request_menu=pr_menu_data, show_todos=False)}
643 pull_request_menu=pr_menu_data, show_todos=False)}
639 % endif
644 % endif
640
645
641 </div>
646 </div>
642 % else:
647 % else:
643 ## skipping commits we need to clear the view for missing commits
648 ## skipping commits we need to clear the view for missing commits
644 <div style="clear:both;"></div>
649 <div style="clear:both;"></div>
645 % endif
650 % endif
646
651
647 </div>
652 </div>
648 </div>
653 </div>
649
654
650 ## template for inline comment form
655 ## template for inline comment form
651 <%namespace name="comment" file="/changeset/changeset_file_comment.mako"/>
656 <%namespace name="comment" file="/changeset/changeset_file_comment.mako"/>
652
657
653 ## comments heading with count
658 ## comments heading with count
654 <div class="comments-heading">
659 <div class="comments-heading">
655 <i class="icon-comment"></i>
660 <i class="icon-comment"></i>
656 ${_('Comments')} ${len(c.comments)}
661 ${_('Comments')} ${len(c.comments)}
657 </div>
662 </div>
658
663
659 ## render general comments
664 ## render general comments
660 <div id="comment-tr-show">
665 <div id="comment-tr-show">
661 % if general_outdated_comm_count_ver:
666 % if general_outdated_comm_count_ver:
662 <div class="info-box">
667 <div class="info-box">
663 % if general_outdated_comm_count_ver == 1:
668 % if general_outdated_comm_count_ver == 1:
664 ${_('there is {num} general comment from older versions').format(num=general_outdated_comm_count_ver)},
669 ${_('there is {num} general comment from older versions').format(num=general_outdated_comm_count_ver)},
665 <a href="#show-hidden-comments" onclick="$('.comment-general.comment-outdated').show(); $(this).parent().hide(); return false;">${_('show it')}</a>
670 <a href="#show-hidden-comments" onclick="$('.comment-general.comment-outdated').show(); $(this).parent().hide(); return false;">${_('show it')}</a>
666 % else:
671 % else:
667 ${_('there are {num} general comments from older versions').format(num=general_outdated_comm_count_ver)},
672 ${_('there are {num} general comments from older versions').format(num=general_outdated_comm_count_ver)},
668 <a href="#show-hidden-comments" onclick="$('.comment-general.comment-outdated').show(); $(this).parent().hide(); return false;">${_('show them')}</a>
673 <a href="#show-hidden-comments" onclick="$('.comment-general.comment-outdated').show(); $(this).parent().hide(); return false;">${_('show them')}</a>
669 % endif
674 % endif
670 </div>
675 </div>
671 % endif
676 % endif
672 </div>
677 </div>
673
678
674 ${comment.generate_comments(c.comments, include_pull_request=True, is_pull_request=True)}
679 ${comment.generate_comments(c.comments, include_pull_request=True, is_pull_request=True)}
675
680
676 % if not c.pull_request.is_closed():
681 % if not c.pull_request.is_closed():
677 ## main comment form and it status
682 ## main comment form and it status
678 ${comment.comments(h.route_path('pullrequest_comment_create', repo_name=c.repo_name,
683 ${comment.comments(h.route_path('pullrequest_comment_create', repo_name=c.repo_name,
679 pull_request_id=c.pull_request.pull_request_id),
684 pull_request_id=c.pull_request.pull_request_id),
680 c.pull_request_review_status,
685 c.pull_request_review_status,
681 is_pull_request=True, change_status=c.allowed_to_change_status)}
686 is_pull_request=True, change_status=c.allowed_to_change_status)}
682
687
683 ## merge status, and merge action
688 ## merge status, and merge action
684 <div class="pull-request-merge">
689 <div class="pull-request-merge">
685 <%include file="/pullrequests/pullrequest_merge_checks.mako"/>
690 <%include file="/pullrequests/pullrequest_merge_checks.mako"/>
686 </div>
691 </div>
687
692
688 %endif
693 %endif
689
694
690 % endif
695 % endif
691 </div>
696 </div>
692
697
693 <script type="text/javascript">
698 <script type="text/javascript">
694
699
695 versionController = new VersionController();
700 versionController = new VersionController();
696 versionController.init();
701 versionController.init();
697
702
698 reviewersController = new ReviewersController();
703 reviewersController = new ReviewersController();
699 commitsController = new CommitsController();
704 commitsController = new CommitsController();
700
705
701 updateController = new UpdatePrController();
706 updateController = new UpdatePrController();
702
707
703 $(function () {
708 $(function () {
704
709
705 // custom code mirror
710 // custom code mirror
706 var codeMirrorInstance = $('#pr-description-input').get(0).MarkupForm.cm;
711 var codeMirrorInstance = $('#pr-description-input').get(0).MarkupForm.cm;
707
712
708 var PRDetails = {
713 var PRDetails = {
709 editButton: $('#open_edit_pullrequest'),
714 editButton: $('#open_edit_pullrequest'),
710 closeButton: $('#close_edit_pullrequest'),
715 closeButton: $('#close_edit_pullrequest'),
711 deleteButton: $('#delete_pullrequest'),
716 deleteButton: $('#delete_pullrequest'),
712 viewFields: $('#pr-desc, #pr-title'),
717 viewFields: $('#pr-desc, #pr-title'),
713 editFields: $('#pr-desc-edit, #pr-title-edit, .pr-save'),
718 editFields: $('#pr-desc-edit, #pr-title-edit, .pr-save'),
714
719
715 init: function () {
720 init: function () {
716 var that = this;
721 var that = this;
717 this.editButton.on('click', function (e) {
722 this.editButton.on('click', function (e) {
718 that.edit();
723 that.edit();
719 });
724 });
720 this.closeButton.on('click', function (e) {
725 this.closeButton.on('click', function (e) {
721 that.view();
726 that.view();
722 });
727 });
723 },
728 },
724
729
725 edit: function (event) {
730 edit: function (event) {
726 this.viewFields.hide();
731 this.viewFields.hide();
727 this.editButton.hide();
732 this.editButton.hide();
728 this.deleteButton.hide();
733 this.deleteButton.hide();
729 this.closeButton.show();
734 this.closeButton.show();
730 this.editFields.show();
735 this.editFields.show();
731 codeMirrorInstance.refresh();
736 codeMirrorInstance.refresh();
732 },
737 },
733
738
734 view: function (event) {
739 view: function (event) {
735 this.editButton.show();
740 this.editButton.show();
736 this.deleteButton.show();
741 this.deleteButton.show();
737 this.editFields.hide();
742 this.editFields.hide();
738 this.closeButton.hide();
743 this.closeButton.hide();
739 this.viewFields.show();
744 this.viewFields.show();
740 }
745 }
741 };
746 };
742
747
743 var ReviewersPanel = {
748 var ReviewersPanel = {
744 editButton: $('#open_edit_reviewers'),
749 editButton: $('#open_edit_reviewers'),
745 closeButton: $('#close_edit_reviewers'),
750 closeButton: $('#close_edit_reviewers'),
746 addButton: $('#add_reviewer'),
751 addButton: $('#add_reviewer'),
747 removeButtons: $('.reviewer_member_remove,.reviewer_member_mandatory_remove'),
752 removeButtons: $('.reviewer_member_remove,.reviewer_member_mandatory_remove'),
748
753
749 init: function () {
754 init: function () {
750 var self = this;
755 var self = this;
751 this.editButton.on('click', function (e) {
756 this.editButton.on('click', function (e) {
752 self.edit();
757 self.edit();
753 });
758 });
754 this.closeButton.on('click', function (e) {
759 this.closeButton.on('click', function (e) {
755 self.close();
760 self.close();
756 });
761 });
757 },
762 },
758
763
759 edit: function (event) {
764 edit: function (event) {
760 this.editButton.hide();
765 this.editButton.hide();
761 this.closeButton.show();
766 this.closeButton.show();
762 this.addButton.show();
767 this.addButton.show();
763 this.removeButtons.css('visibility', 'visible');
768 this.removeButtons.css('visibility', 'visible');
764 // review rules
769 // review rules
765 reviewersController.loadReviewRules(
770 reviewersController.loadReviewRules(
766 ${c.pull_request.reviewer_data_json | n});
771 ${c.pull_request.reviewer_data_json | n});
767 },
772 },
768
773
769 close: function (event) {
774 close: function (event) {
770 this.editButton.show();
775 this.editButton.show();
771 this.closeButton.hide();
776 this.closeButton.hide();
772 this.addButton.hide();
777 this.addButton.hide();
773 this.removeButtons.css('visibility', 'hidden');
778 this.removeButtons.css('visibility', 'hidden');
774 // hide review rules
779 // hide review rules
775 reviewersController.hideReviewRules()
780 reviewersController.hideReviewRules()
776 }
781 }
777 };
782 };
778
783
779 PRDetails.init();
784 PRDetails.init();
780 ReviewersPanel.init();
785 ReviewersPanel.init();
781
786
782 showOutdated = function (self) {
787 showOutdated = function (self) {
783 $('.comment-inline.comment-outdated').show();
788 $('.comment-inline.comment-outdated').show();
784 $('.filediff-outdated').show();
789 $('.filediff-outdated').show();
785 $('.showOutdatedComments').hide();
790 $('.showOutdatedComments').hide();
786 $('.hideOutdatedComments').show();
791 $('.hideOutdatedComments').show();
787 };
792 };
788
793
789 hideOutdated = function (self) {
794 hideOutdated = function (self) {
790 $('.comment-inline.comment-outdated').hide();
795 $('.comment-inline.comment-outdated').hide();
791 $('.filediff-outdated').hide();
796 $('.filediff-outdated').hide();
792 $('.hideOutdatedComments').hide();
797 $('.hideOutdatedComments').hide();
793 $('.showOutdatedComments').show();
798 $('.showOutdatedComments').show();
794 };
799 };
795
800
796 refreshMergeChecks = function () {
801 refreshMergeChecks = function () {
797 var loadUrl = "${request.current_route_path(_query=dict(merge_checks=1))}";
802 var loadUrl = "${request.current_route_path(_query=dict(merge_checks=1))}";
798 $('.pull-request-merge').css('opacity', 0.3);
803 $('.pull-request-merge').css('opacity', 0.3);
799 $('.action-buttons-extra').css('opacity', 0.3);
804 $('.action-buttons-extra').css('opacity', 0.3);
800
805
801 $('.pull-request-merge').load(
806 $('.pull-request-merge').load(
802 loadUrl, function () {
807 loadUrl, function () {
803 $('.pull-request-merge').css('opacity', 1);
808 $('.pull-request-merge').css('opacity', 1);
804
809
805 $('.action-buttons-extra').css('opacity', 1);
810 $('.action-buttons-extra').css('opacity', 1);
806 }
811 }
807 );
812 );
808 };
813 };
809
814
810 closePullRequest = function (status) {
815 closePullRequest = function (status) {
811 if (!confirm(_gettext('Are you sure to close this pull request without merging?'))) {
816 if (!confirm(_gettext('Are you sure to close this pull request without merging?'))) {
812 return false;
817 return false;
813 }
818 }
814 // inject closing flag
819 // inject closing flag
815 $('.action-buttons-extra').append('<input type="hidden" class="close-pr-input" id="close_pull_request" value="1">');
820 $('.action-buttons-extra').append('<input type="hidden" class="close-pr-input" id="close_pull_request" value="1">');
816 $(generalCommentForm.statusChange).select2("val", status).trigger('change');
821 $(generalCommentForm.statusChange).select2("val", status).trigger('change');
817 $(generalCommentForm.submitForm).submit();
822 $(generalCommentForm.submitForm).submit();
818 };
823 };
819
824
820 $('#show-outdated-comments').on('click', function (e) {
825 $('#show-outdated-comments').on('click', function (e) {
821 var button = $(this);
826 var button = $(this);
822 var outdated = $('.comment-outdated');
827 var outdated = $('.comment-outdated');
823
828
824 if (button.html() === "(Show)") {
829 if (button.html() === "(Show)") {
825 button.html("(Hide)");
830 button.html("(Hide)");
826 outdated.show();
831 outdated.show();
827 } else {
832 } else {
828 button.html("(Show)");
833 button.html("(Show)");
829 outdated.hide();
834 outdated.hide();
830 }
835 }
831 });
836 });
832
837
833 $('.show-inline-comments').on('change', function (e) {
838 $('.show-inline-comments').on('change', function (e) {
834 var show = 'none';
839 var show = 'none';
835 var target = e.currentTarget;
840 var target = e.currentTarget;
836 if (target.checked) {
841 if (target.checked) {
837 show = ''
842 show = ''
838 }
843 }
839 var boxid = $(target).attr('id_for');
844 var boxid = $(target).attr('id_for');
840 var comments = $('#{0} .inline-comments'.format(boxid));
845 var comments = $('#{0} .inline-comments'.format(boxid));
841 var fn_display = function (idx) {
846 var fn_display = function (idx) {
842 $(this).css('display', show);
847 $(this).css('display', show);
843 };
848 };
844 $(comments).each(fn_display);
849 $(comments).each(fn_display);
845 var btns = $('#{0} .inline-comments-button'.format(boxid));
850 var btns = $('#{0} .inline-comments-button'.format(boxid));
846 $(btns).each(fn_display);
851 $(btns).each(fn_display);
847 });
852 });
848
853
849 $('#merge_pull_request_form').submit(function () {
854 $('#merge_pull_request_form').submit(function () {
850 if (!$('#merge_pull_request').attr('disabled')) {
855 if (!$('#merge_pull_request').attr('disabled')) {
851 $('#merge_pull_request').attr('disabled', 'disabled');
856 $('#merge_pull_request').attr('disabled', 'disabled');
852 }
857 }
853 return true;
858 return true;
854 });
859 });
855
860
856 $('#edit_pull_request').on('click', function (e) {
861 $('#edit_pull_request').on('click', function (e) {
857 var title = $('#pr-title-input').val();
862 var title = $('#pr-title-input').val();
858 var description = codeMirrorInstance.getValue();
863 var description = codeMirrorInstance.getValue();
859 var renderer = $('#pr-renderer-input').val();
864 var renderer = $('#pr-renderer-input').val();
860 editPullRequest(
865 editPullRequest(
861 "${c.repo_name}", "${c.pull_request.pull_request_id}",
866 "${c.repo_name}", "${c.pull_request.pull_request_id}",
862 title, description, renderer);
867 title, description, renderer);
863 });
868 });
864
869
865 $('#update_pull_request').on('click', function (e) {
870 $('#update_pull_request').on('click', function (e) {
866 $(this).attr('disabled', 'disabled');
871 $(this).attr('disabled', 'disabled');
867 $(this).addClass('disabled');
872 $(this).addClass('disabled');
868 $(this).html(_gettext('Saving...'));
873 $(this).html(_gettext('Saving...'));
869 reviewersController.updateReviewers(
874 reviewersController.updateReviewers(
870 "${c.repo_name}", "${c.pull_request.pull_request_id}");
875 "${c.repo_name}", "${c.pull_request.pull_request_id}");
871 });
876 });
872
877
873
878
874 // fixing issue with caches on firefox
879 // fixing issue with caches on firefox
875 $('#update_commits').removeAttr("disabled");
880 $('#update_commits').removeAttr("disabled");
876
881
877 $('.show-inline-comments').on('click', function (e) {
882 $('.show-inline-comments').on('click', function (e) {
878 var boxid = $(this).attr('data-comment-id');
883 var boxid = $(this).attr('data-comment-id');
879 var button = $(this);
884 var button = $(this);
880
885
881 if (button.hasClass("comments-visible")) {
886 if (button.hasClass("comments-visible")) {
882 $('#{0} .inline-comments'.format(boxid)).each(function (index) {
887 $('#{0} .inline-comments'.format(boxid)).each(function (index) {
883 $(this).hide();
888 $(this).hide();
884 });
889 });
885 button.removeClass("comments-visible");
890 button.removeClass("comments-visible");
886 } else {
891 } else {
887 $('#{0} .inline-comments'.format(boxid)).each(function (index) {
892 $('#{0} .inline-comments'.format(boxid)).each(function (index) {
888 $(this).show();
893 $(this).show();
889 });
894 });
890 button.addClass("comments-visible");
895 button.addClass("comments-visible");
891 }
896 }
892 });
897 });
893
898
894 // register submit callback on commentForm form to track TODOs
899 // register submit callback on commentForm form to track TODOs
895 window.commentFormGlobalSubmitSuccessCallback = function () {
900 window.commentFormGlobalSubmitSuccessCallback = function () {
896 refreshMergeChecks();
901 refreshMergeChecks();
897 };
902 };
898
903
899 ReviewerAutoComplete('#user');
904 ReviewerAutoComplete('#user');
900
905
901 })
906 })
902
907
903 </script>
908 </script>
904
909
905 </div>
910 </div>
906
911
907 </%def>
912 </%def>
General Comments 0
You need to be logged in to leave comments. Login now