##// END OF EJS Templates
pull-requests: allow forced state change to repo admins too.
super-admin -
r4710:97a223d9 stable
parent child Browse files
Show More
@@ -1,1872 +1,1873 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2020 RhodeCode GmbH
3 # Copyright (C) 2011-2020 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import logging
21 import logging
22 import collections
22 import collections
23
23
24 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, HTTPConflict)
28 HTTPFound, HTTPNotFound, HTTPForbidden, HTTPBadRequest, HTTPConflict)
29
29
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.exceptions import CommentVersionMismatch
37 from rhodecode.lib.exceptions import CommentVersionMismatch
38 from rhodecode.lib.ext_json import json
38 from rhodecode.lib.ext_json import json
39 from rhodecode.lib.auth import (
39 from rhodecode.lib.auth import (
40 LoginRequired, HasRepoPermissionAny, HasRepoPermissionAnyDecorator,
40 LoginRequired, HasRepoPermissionAny, HasRepoPermissionAnyDecorator,
41 NotAnonymous, CSRFRequired)
41 NotAnonymous, CSRFRequired)
42 from rhodecode.lib.utils2 import str2bool, safe_str, safe_unicode, safe_int, aslist, retry
42 from rhodecode.lib.utils2 import str2bool, safe_str, safe_unicode, safe_int, aslist, retry
43 from rhodecode.lib.vcs.backends.base import (
43 from rhodecode.lib.vcs.backends.base import (
44 EmptyCommit, UpdateFailureReason, unicode_to_reference)
44 EmptyCommit, UpdateFailureReason, unicode_to_reference)
45 from rhodecode.lib.vcs.exceptions import (
45 from rhodecode.lib.vcs.exceptions import (
46 CommitDoesNotExistError, RepositoryRequirementError, EmptyRepositoryError)
46 CommitDoesNotExistError, RepositoryRequirementError, EmptyRepositoryError)
47 from rhodecode.model.changeset_status import ChangesetStatusModel
47 from rhodecode.model.changeset_status import ChangesetStatusModel
48 from rhodecode.model.comment import CommentsModel
48 from rhodecode.model.comment import CommentsModel
49 from rhodecode.model.db import (
49 from rhodecode.model.db import (
50 func, false, or_, PullRequest, ChangesetComment, ChangesetStatus, Repository,
50 func, false, or_, PullRequest, ChangesetComment, ChangesetStatus, Repository,
51 PullRequestReviewers)
51 PullRequestReviewers)
52 from rhodecode.model.forms import PullRequestForm
52 from rhodecode.model.forms import PullRequestForm
53 from rhodecode.model.meta import Session
53 from rhodecode.model.meta import Session
54 from rhodecode.model.pull_request import PullRequestModel, MergeCheck
54 from rhodecode.model.pull_request import PullRequestModel, MergeCheck
55 from rhodecode.model.scm import ScmModel
55 from rhodecode.model.scm import ScmModel
56
56
57 log = logging.getLogger(__name__)
57 log = logging.getLogger(__name__)
58
58
59
59
60 class RepoPullRequestsView(RepoAppView, DataGridAppView):
60 class RepoPullRequestsView(RepoAppView, DataGridAppView):
61
61
62 def load_default_context(self):
62 def load_default_context(self):
63 c = self._get_local_tmpl_context(include_app_defaults=True)
63 c = self._get_local_tmpl_context(include_app_defaults=True)
64 c.REVIEW_STATUS_APPROVED = ChangesetStatus.STATUS_APPROVED
64 c.REVIEW_STATUS_APPROVED = ChangesetStatus.STATUS_APPROVED
65 c.REVIEW_STATUS_REJECTED = ChangesetStatus.STATUS_REJECTED
65 c.REVIEW_STATUS_REJECTED = ChangesetStatus.STATUS_REJECTED
66 # backward compat., we use for OLD PRs a plain renderer
66 # backward compat., we use for OLD PRs a plain renderer
67 c.renderer = 'plain'
67 c.renderer = 'plain'
68 return c
68 return c
69
69
70 def _get_pull_requests_list(
70 def _get_pull_requests_list(
71 self, repo_name, source, filter_type, opened_by, statuses):
71 self, repo_name, source, filter_type, opened_by, statuses):
72
72
73 draw, start, limit = self._extract_chunk(self.request)
73 draw, start, limit = self._extract_chunk(self.request)
74 search_q, order_by, order_dir = self._extract_ordering(self.request)
74 search_q, order_by, order_dir = self._extract_ordering(self.request)
75 _render = self.request.get_partial_renderer(
75 _render = self.request.get_partial_renderer(
76 'rhodecode:templates/data_table/_dt_elements.mako')
76 'rhodecode:templates/data_table/_dt_elements.mako')
77
77
78 # pagination
78 # pagination
79
79
80 if filter_type == 'awaiting_review':
80 if filter_type == 'awaiting_review':
81 pull_requests = PullRequestModel().get_awaiting_review(
81 pull_requests = PullRequestModel().get_awaiting_review(
82 repo_name,
82 repo_name,
83 search_q=search_q, statuses=statuses,
83 search_q=search_q, statuses=statuses,
84 offset=start, length=limit, order_by=order_by, order_dir=order_dir)
84 offset=start, length=limit, order_by=order_by, order_dir=order_dir)
85 pull_requests_total_count = PullRequestModel().count_awaiting_review(
85 pull_requests_total_count = PullRequestModel().count_awaiting_review(
86 repo_name,
86 repo_name,
87 search_q=search_q, statuses=statuses)
87 search_q=search_q, statuses=statuses)
88 elif filter_type == 'awaiting_my_review':
88 elif filter_type == 'awaiting_my_review':
89 pull_requests = PullRequestModel().get_awaiting_my_review(
89 pull_requests = PullRequestModel().get_awaiting_my_review(
90 repo_name, self._rhodecode_user.user_id,
90 repo_name, self._rhodecode_user.user_id,
91 search_q=search_q, statuses=statuses,
91 search_q=search_q, statuses=statuses,
92 offset=start, length=limit, order_by=order_by, order_dir=order_dir)
92 offset=start, length=limit, order_by=order_by, order_dir=order_dir)
93 pull_requests_total_count = PullRequestModel().count_awaiting_my_review(
93 pull_requests_total_count = PullRequestModel().count_awaiting_my_review(
94 repo_name, self._rhodecode_user.user_id,
94 repo_name, self._rhodecode_user.user_id,
95 search_q=search_q, statuses=statuses)
95 search_q=search_q, statuses=statuses)
96 else:
96 else:
97 pull_requests = PullRequestModel().get_all(
97 pull_requests = PullRequestModel().get_all(
98 repo_name, search_q=search_q, source=source, opened_by=opened_by,
98 repo_name, search_q=search_q, source=source, opened_by=opened_by,
99 statuses=statuses, offset=start, length=limit,
99 statuses=statuses, offset=start, length=limit,
100 order_by=order_by, order_dir=order_dir)
100 order_by=order_by, order_dir=order_dir)
101 pull_requests_total_count = PullRequestModel().count_all(
101 pull_requests_total_count = PullRequestModel().count_all(
102 repo_name, search_q=search_q, source=source, statuses=statuses,
102 repo_name, search_q=search_q, source=source, statuses=statuses,
103 opened_by=opened_by)
103 opened_by=opened_by)
104
104
105 data = []
105 data = []
106 comments_model = CommentsModel()
106 comments_model = CommentsModel()
107 for pr in pull_requests:
107 for pr in pull_requests:
108 comments_count = comments_model.get_all_comments(
108 comments_count = comments_model.get_all_comments(
109 self.db_repo.repo_id, pull_request=pr,
109 self.db_repo.repo_id, pull_request=pr,
110 include_drafts=False, count_only=True)
110 include_drafts=False, count_only=True)
111
111
112 review_statuses = pr.reviewers_statuses(user=self._rhodecode_db_user)
112 review_statuses = pr.reviewers_statuses(user=self._rhodecode_db_user)
113 my_review_status = ChangesetStatus.STATUS_NOT_REVIEWED
113 my_review_status = ChangesetStatus.STATUS_NOT_REVIEWED
114 if review_statuses and review_statuses[4]:
114 if review_statuses and review_statuses[4]:
115 _review_obj, _user, _reasons, _mandatory, statuses = review_statuses
115 _review_obj, _user, _reasons, _mandatory, statuses = review_statuses
116 my_review_status = statuses[0][1].status
116 my_review_status = statuses[0][1].status
117
117
118 data.append({
118 data.append({
119 'name': _render('pullrequest_name',
119 'name': _render('pullrequest_name',
120 pr.pull_request_id, pr.pull_request_state,
120 pr.pull_request_id, pr.pull_request_state,
121 pr.work_in_progress, pr.target_repo.repo_name,
121 pr.work_in_progress, pr.target_repo.repo_name,
122 short=True),
122 short=True),
123 'name_raw': pr.pull_request_id,
123 'name_raw': pr.pull_request_id,
124 'status': _render('pullrequest_status',
124 'status': _render('pullrequest_status',
125 pr.calculated_review_status()),
125 pr.calculated_review_status()),
126 'my_status': _render('pullrequest_status',
126 'my_status': _render('pullrequest_status',
127 my_review_status),
127 my_review_status),
128 'title': _render('pullrequest_title', pr.title, pr.description),
128 'title': _render('pullrequest_title', pr.title, pr.description),
129 'description': h.escape(pr.description),
129 'description': h.escape(pr.description),
130 'updated_on': _render('pullrequest_updated_on',
130 'updated_on': _render('pullrequest_updated_on',
131 h.datetime_to_time(pr.updated_on),
131 h.datetime_to_time(pr.updated_on),
132 pr.versions_count),
132 pr.versions_count),
133 'updated_on_raw': h.datetime_to_time(pr.updated_on),
133 'updated_on_raw': h.datetime_to_time(pr.updated_on),
134 'created_on': _render('pullrequest_updated_on',
134 'created_on': _render('pullrequest_updated_on',
135 h.datetime_to_time(pr.created_on)),
135 h.datetime_to_time(pr.created_on)),
136 'created_on_raw': h.datetime_to_time(pr.created_on),
136 'created_on_raw': h.datetime_to_time(pr.created_on),
137 'state': pr.pull_request_state,
137 'state': pr.pull_request_state,
138 'author': _render('pullrequest_author',
138 'author': _render('pullrequest_author',
139 pr.author.full_contact, ),
139 pr.author.full_contact, ),
140 'author_raw': pr.author.full_name,
140 'author_raw': pr.author.full_name,
141 'comments': _render('pullrequest_comments', comments_count),
141 'comments': _render('pullrequest_comments', comments_count),
142 'comments_raw': comments_count,
142 'comments_raw': comments_count,
143 'closed': pr.is_closed(),
143 'closed': pr.is_closed(),
144 })
144 })
145
145
146 data = ({
146 data = ({
147 'draw': draw,
147 'draw': draw,
148 'data': data,
148 'data': data,
149 'recordsTotal': pull_requests_total_count,
149 'recordsTotal': pull_requests_total_count,
150 'recordsFiltered': pull_requests_total_count,
150 'recordsFiltered': pull_requests_total_count,
151 })
151 })
152 return data
152 return data
153
153
154 @LoginRequired()
154 @LoginRequired()
155 @HasRepoPermissionAnyDecorator(
155 @HasRepoPermissionAnyDecorator(
156 'repository.read', 'repository.write', 'repository.admin')
156 'repository.read', 'repository.write', 'repository.admin')
157 def pull_request_list(self):
157 def pull_request_list(self):
158 c = self.load_default_context()
158 c = self.load_default_context()
159
159
160 req_get = self.request.GET
160 req_get = self.request.GET
161 c.source = str2bool(req_get.get('source'))
161 c.source = str2bool(req_get.get('source'))
162 c.closed = str2bool(req_get.get('closed'))
162 c.closed = str2bool(req_get.get('closed'))
163 c.my = str2bool(req_get.get('my'))
163 c.my = str2bool(req_get.get('my'))
164 c.awaiting_review = str2bool(req_get.get('awaiting_review'))
164 c.awaiting_review = str2bool(req_get.get('awaiting_review'))
165 c.awaiting_my_review = str2bool(req_get.get('awaiting_my_review'))
165 c.awaiting_my_review = str2bool(req_get.get('awaiting_my_review'))
166
166
167 c.active = 'open'
167 c.active = 'open'
168 if c.my:
168 if c.my:
169 c.active = 'my'
169 c.active = 'my'
170 if c.closed:
170 if c.closed:
171 c.active = 'closed'
171 c.active = 'closed'
172 if c.awaiting_review and not c.source:
172 if c.awaiting_review and not c.source:
173 c.active = 'awaiting'
173 c.active = 'awaiting'
174 if c.source and not c.awaiting_review:
174 if c.source and not c.awaiting_review:
175 c.active = 'source'
175 c.active = 'source'
176 if c.awaiting_my_review:
176 if c.awaiting_my_review:
177 c.active = 'awaiting_my'
177 c.active = 'awaiting_my'
178
178
179 return self._get_template_context(c)
179 return self._get_template_context(c)
180
180
181 @LoginRequired()
181 @LoginRequired()
182 @HasRepoPermissionAnyDecorator(
182 @HasRepoPermissionAnyDecorator(
183 'repository.read', 'repository.write', 'repository.admin')
183 'repository.read', 'repository.write', 'repository.admin')
184 def pull_request_list_data(self):
184 def pull_request_list_data(self):
185 self.load_default_context()
185 self.load_default_context()
186
186
187 # additional filters
187 # additional filters
188 req_get = self.request.GET
188 req_get = self.request.GET
189 source = str2bool(req_get.get('source'))
189 source = str2bool(req_get.get('source'))
190 closed = str2bool(req_get.get('closed'))
190 closed = str2bool(req_get.get('closed'))
191 my = str2bool(req_get.get('my'))
191 my = str2bool(req_get.get('my'))
192 awaiting_review = str2bool(req_get.get('awaiting_review'))
192 awaiting_review = str2bool(req_get.get('awaiting_review'))
193 awaiting_my_review = str2bool(req_get.get('awaiting_my_review'))
193 awaiting_my_review = str2bool(req_get.get('awaiting_my_review'))
194
194
195 filter_type = 'awaiting_review' if awaiting_review \
195 filter_type = 'awaiting_review' if awaiting_review \
196 else 'awaiting_my_review' if awaiting_my_review \
196 else 'awaiting_my_review' if awaiting_my_review \
197 else None
197 else None
198
198
199 opened_by = None
199 opened_by = None
200 if my:
200 if my:
201 opened_by = [self._rhodecode_user.user_id]
201 opened_by = [self._rhodecode_user.user_id]
202
202
203 statuses = [PullRequest.STATUS_NEW, PullRequest.STATUS_OPEN]
203 statuses = [PullRequest.STATUS_NEW, PullRequest.STATUS_OPEN]
204 if closed:
204 if closed:
205 statuses = [PullRequest.STATUS_CLOSED]
205 statuses = [PullRequest.STATUS_CLOSED]
206
206
207 data = self._get_pull_requests_list(
207 data = self._get_pull_requests_list(
208 repo_name=self.db_repo_name, source=source,
208 repo_name=self.db_repo_name, source=source,
209 filter_type=filter_type, opened_by=opened_by, statuses=statuses)
209 filter_type=filter_type, opened_by=opened_by, statuses=statuses)
210
210
211 return data
211 return data
212
212
213 def _is_diff_cache_enabled(self, target_repo):
213 def _is_diff_cache_enabled(self, target_repo):
214 caching_enabled = self._get_general_setting(
214 caching_enabled = self._get_general_setting(
215 target_repo, 'rhodecode_diff_cache')
215 target_repo, 'rhodecode_diff_cache')
216 log.debug('Diff caching enabled: %s', caching_enabled)
216 log.debug('Diff caching enabled: %s', caching_enabled)
217 return caching_enabled
217 return caching_enabled
218
218
219 def _get_diffset(self, source_repo_name, source_repo,
219 def _get_diffset(self, source_repo_name, source_repo,
220 ancestor_commit,
220 ancestor_commit,
221 source_ref_id, target_ref_id,
221 source_ref_id, target_ref_id,
222 target_commit, source_commit, diff_limit, file_limit,
222 target_commit, source_commit, diff_limit, file_limit,
223 fulldiff, hide_whitespace_changes, diff_context, use_ancestor=True):
223 fulldiff, hide_whitespace_changes, diff_context, use_ancestor=True):
224
224
225 target_commit_final = target_commit
225 target_commit_final = target_commit
226 source_commit_final = source_commit
226 source_commit_final = source_commit
227
227
228 if use_ancestor:
228 if use_ancestor:
229 # we might want to not use it for versions
229 # we might want to not use it for versions
230 target_ref_id = ancestor_commit.raw_id
230 target_ref_id = ancestor_commit.raw_id
231 target_commit_final = ancestor_commit
231 target_commit_final = ancestor_commit
232
232
233 vcs_diff = PullRequestModel().get_diff(
233 vcs_diff = PullRequestModel().get_diff(
234 source_repo, source_ref_id, target_ref_id,
234 source_repo, source_ref_id, target_ref_id,
235 hide_whitespace_changes, diff_context)
235 hide_whitespace_changes, diff_context)
236
236
237 diff_processor = diffs.DiffProcessor(
237 diff_processor = diffs.DiffProcessor(
238 vcs_diff, format='newdiff', diff_limit=diff_limit,
238 vcs_diff, format='newdiff', diff_limit=diff_limit,
239 file_limit=file_limit, show_full_diff=fulldiff)
239 file_limit=file_limit, show_full_diff=fulldiff)
240
240
241 _parsed = diff_processor.prepare()
241 _parsed = diff_processor.prepare()
242
242
243 diffset = codeblocks.DiffSet(
243 diffset = codeblocks.DiffSet(
244 repo_name=self.db_repo_name,
244 repo_name=self.db_repo_name,
245 source_repo_name=source_repo_name,
245 source_repo_name=source_repo_name,
246 source_node_getter=codeblocks.diffset_node_getter(target_commit_final),
246 source_node_getter=codeblocks.diffset_node_getter(target_commit_final),
247 target_node_getter=codeblocks.diffset_node_getter(source_commit_final),
247 target_node_getter=codeblocks.diffset_node_getter(source_commit_final),
248 )
248 )
249 diffset = self.path_filter.render_patchset_filtered(
249 diffset = self.path_filter.render_patchset_filtered(
250 diffset, _parsed, target_ref_id, source_ref_id)
250 diffset, _parsed, target_ref_id, source_ref_id)
251
251
252 return diffset
252 return diffset
253
253
254 def _get_range_diffset(self, source_scm, source_repo,
254 def _get_range_diffset(self, source_scm, source_repo,
255 commit1, commit2, diff_limit, file_limit,
255 commit1, commit2, diff_limit, file_limit,
256 fulldiff, hide_whitespace_changes, diff_context):
256 fulldiff, hide_whitespace_changes, diff_context):
257 vcs_diff = source_scm.get_diff(
257 vcs_diff = source_scm.get_diff(
258 commit1, commit2,
258 commit1, commit2,
259 ignore_whitespace=hide_whitespace_changes,
259 ignore_whitespace=hide_whitespace_changes,
260 context=diff_context)
260 context=diff_context)
261
261
262 diff_processor = diffs.DiffProcessor(
262 diff_processor = diffs.DiffProcessor(
263 vcs_diff, format='newdiff', diff_limit=diff_limit,
263 vcs_diff, format='newdiff', diff_limit=diff_limit,
264 file_limit=file_limit, show_full_diff=fulldiff)
264 file_limit=file_limit, show_full_diff=fulldiff)
265
265
266 _parsed = diff_processor.prepare()
266 _parsed = diff_processor.prepare()
267
267
268 diffset = codeblocks.DiffSet(
268 diffset = codeblocks.DiffSet(
269 repo_name=source_repo.repo_name,
269 repo_name=source_repo.repo_name,
270 source_node_getter=codeblocks.diffset_node_getter(commit1),
270 source_node_getter=codeblocks.diffset_node_getter(commit1),
271 target_node_getter=codeblocks.diffset_node_getter(commit2))
271 target_node_getter=codeblocks.diffset_node_getter(commit2))
272
272
273 diffset = self.path_filter.render_patchset_filtered(
273 diffset = self.path_filter.render_patchset_filtered(
274 diffset, _parsed, commit1.raw_id, commit2.raw_id)
274 diffset, _parsed, commit1.raw_id, commit2.raw_id)
275
275
276 return diffset
276 return diffset
277
277
278 def register_comments_vars(self, c, pull_request, versions, include_drafts=True):
278 def register_comments_vars(self, c, pull_request, versions, include_drafts=True):
279 comments_model = CommentsModel()
279 comments_model = CommentsModel()
280
280
281 # GENERAL COMMENTS with versions #
281 # GENERAL COMMENTS with versions #
282 q = comments_model._all_general_comments_of_pull_request(pull_request)
282 q = comments_model._all_general_comments_of_pull_request(pull_request)
283 q = q.order_by(ChangesetComment.comment_id.asc())
283 q = q.order_by(ChangesetComment.comment_id.asc())
284 if not include_drafts:
284 if not include_drafts:
285 q = q.filter(ChangesetComment.draft == false())
285 q = q.filter(ChangesetComment.draft == false())
286 general_comments = q
286 general_comments = q
287
287
288 # pick comments we want to render at current version
288 # pick comments we want to render at current version
289 c.comment_versions = comments_model.aggregate_comments(
289 c.comment_versions = comments_model.aggregate_comments(
290 general_comments, versions, c.at_version_num)
290 general_comments, versions, c.at_version_num)
291
291
292 # INLINE COMMENTS with versions #
292 # INLINE COMMENTS with versions #
293 q = comments_model._all_inline_comments_of_pull_request(pull_request)
293 q = comments_model._all_inline_comments_of_pull_request(pull_request)
294 q = q.order_by(ChangesetComment.comment_id.asc())
294 q = q.order_by(ChangesetComment.comment_id.asc())
295 if not include_drafts:
295 if not include_drafts:
296 q = q.filter(ChangesetComment.draft == false())
296 q = q.filter(ChangesetComment.draft == false())
297 inline_comments = q
297 inline_comments = q
298
298
299 c.inline_versions = comments_model.aggregate_comments(
299 c.inline_versions = comments_model.aggregate_comments(
300 inline_comments, versions, c.at_version_num, inline=True)
300 inline_comments, versions, c.at_version_num, inline=True)
301
301
302 # Comments inline+general
302 # Comments inline+general
303 if c.at_version:
303 if c.at_version:
304 c.inline_comments_flat = c.inline_versions[c.at_version_num]['display']
304 c.inline_comments_flat = c.inline_versions[c.at_version_num]['display']
305 c.comments = c.comment_versions[c.at_version_num]['display']
305 c.comments = c.comment_versions[c.at_version_num]['display']
306 else:
306 else:
307 c.inline_comments_flat = c.inline_versions[c.at_version_num]['until']
307 c.inline_comments_flat = c.inline_versions[c.at_version_num]['until']
308 c.comments = c.comment_versions[c.at_version_num]['until']
308 c.comments = c.comment_versions[c.at_version_num]['until']
309
309
310 return general_comments, inline_comments
310 return general_comments, inline_comments
311
311
312 @LoginRequired()
312 @LoginRequired()
313 @HasRepoPermissionAnyDecorator(
313 @HasRepoPermissionAnyDecorator(
314 'repository.read', 'repository.write', 'repository.admin')
314 'repository.read', 'repository.write', 'repository.admin')
315 def pull_request_show(self):
315 def pull_request_show(self):
316 _ = self.request.translate
316 _ = self.request.translate
317 c = self.load_default_context()
317 c = self.load_default_context()
318
318
319 pull_request = PullRequest.get_or_404(
319 pull_request = PullRequest.get_or_404(
320 self.request.matchdict['pull_request_id'])
320 self.request.matchdict['pull_request_id'])
321 pull_request_id = pull_request.pull_request_id
321 pull_request_id = pull_request.pull_request_id
322
322
323 c.state_progressing = pull_request.is_state_changing()
323 c.state_progressing = pull_request.is_state_changing()
324 c.pr_broadcast_channel = channelstream.pr_channel(pull_request)
324 c.pr_broadcast_channel = channelstream.pr_channel(pull_request)
325
325
326 _new_state = {
326 _new_state = {
327 'created': PullRequest.STATE_CREATED,
327 'created': PullRequest.STATE_CREATED,
328 }.get(self.request.GET.get('force_state'))
328 }.get(self.request.GET.get('force_state'))
329 can_force_state = c.is_super_admin or HasRepoPermissionAny('repository.admin')(c.repo_name)
329
330
330 if c.is_super_admin and _new_state:
331 if can_force_state and _new_state:
331 with pull_request.set_state(PullRequest.STATE_UPDATING, final_state=_new_state):
332 with pull_request.set_state(PullRequest.STATE_UPDATING, final_state=_new_state):
332 h.flash(
333 h.flash(
333 _('Pull Request state was force changed to `{}`').format(_new_state),
334 _('Pull Request state was force changed to `{}`').format(_new_state),
334 category='success')
335 category='success')
335 Session().commit()
336 Session().commit()
336
337
337 raise HTTPFound(h.route_path(
338 raise HTTPFound(h.route_path(
338 'pullrequest_show', repo_name=self.db_repo_name,
339 'pullrequest_show', repo_name=self.db_repo_name,
339 pull_request_id=pull_request_id))
340 pull_request_id=pull_request_id))
340
341
341 version = self.request.GET.get('version')
342 version = self.request.GET.get('version')
342 from_version = self.request.GET.get('from_version') or version
343 from_version = self.request.GET.get('from_version') or version
343 merge_checks = self.request.GET.get('merge_checks')
344 merge_checks = self.request.GET.get('merge_checks')
344 c.fulldiff = str2bool(self.request.GET.get('fulldiff'))
345 c.fulldiff = str2bool(self.request.GET.get('fulldiff'))
345 force_refresh = str2bool(self.request.GET.get('force_refresh'))
346 force_refresh = str2bool(self.request.GET.get('force_refresh'))
346 c.range_diff_on = self.request.GET.get('range-diff') == "1"
347 c.range_diff_on = self.request.GET.get('range-diff') == "1"
347
348
348 # fetch global flags of ignore ws or context lines
349 # fetch global flags of ignore ws or context lines
349 diff_context = diffs.get_diff_context(self.request)
350 diff_context = diffs.get_diff_context(self.request)
350 hide_whitespace_changes = diffs.get_diff_whitespace_flag(self.request)
351 hide_whitespace_changes = diffs.get_diff_whitespace_flag(self.request)
351
352
352 (pull_request_latest,
353 (pull_request_latest,
353 pull_request_at_ver,
354 pull_request_at_ver,
354 pull_request_display_obj,
355 pull_request_display_obj,
355 at_version) = PullRequestModel().get_pr_version(
356 at_version) = PullRequestModel().get_pr_version(
356 pull_request_id, version=version)
357 pull_request_id, version=version)
357
358
358 pr_closed = pull_request_latest.is_closed()
359 pr_closed = pull_request_latest.is_closed()
359
360
360 if pr_closed and (version or from_version):
361 if pr_closed and (version or from_version):
361 # not allow to browse versions for closed PR
362 # not allow to browse versions for closed PR
362 raise HTTPFound(h.route_path(
363 raise HTTPFound(h.route_path(
363 'pullrequest_show', repo_name=self.db_repo_name,
364 'pullrequest_show', repo_name=self.db_repo_name,
364 pull_request_id=pull_request_id))
365 pull_request_id=pull_request_id))
365
366
366 versions = pull_request_display_obj.versions()
367 versions = pull_request_display_obj.versions()
367
368
368 c.commit_versions = PullRequestModel().pr_commits_versions(versions)
369 c.commit_versions = PullRequestModel().pr_commits_versions(versions)
369
370
370 # used to store per-commit range diffs
371 # used to store per-commit range diffs
371 c.changes = collections.OrderedDict()
372 c.changes = collections.OrderedDict()
372
373
373 c.at_version = at_version
374 c.at_version = at_version
374 c.at_version_num = (at_version
375 c.at_version_num = (at_version
375 if at_version and at_version != PullRequest.LATEST_VER
376 if at_version and at_version != PullRequest.LATEST_VER
376 else None)
377 else None)
377
378
378 c.at_version_index = ChangesetComment.get_index_from_version(
379 c.at_version_index = ChangesetComment.get_index_from_version(
379 c.at_version_num, versions)
380 c.at_version_num, versions)
380
381
381 (prev_pull_request_latest,
382 (prev_pull_request_latest,
382 prev_pull_request_at_ver,
383 prev_pull_request_at_ver,
383 prev_pull_request_display_obj,
384 prev_pull_request_display_obj,
384 prev_at_version) = PullRequestModel().get_pr_version(
385 prev_at_version) = PullRequestModel().get_pr_version(
385 pull_request_id, version=from_version)
386 pull_request_id, version=from_version)
386
387
387 c.from_version = prev_at_version
388 c.from_version = prev_at_version
388 c.from_version_num = (prev_at_version
389 c.from_version_num = (prev_at_version
389 if prev_at_version and prev_at_version != PullRequest.LATEST_VER
390 if prev_at_version and prev_at_version != PullRequest.LATEST_VER
390 else None)
391 else None)
391 c.from_version_index = ChangesetComment.get_index_from_version(
392 c.from_version_index = ChangesetComment.get_index_from_version(
392 c.from_version_num, versions)
393 c.from_version_num, versions)
393
394
394 # define if we're in COMPARE mode or VIEW at version mode
395 # define if we're in COMPARE mode or VIEW at version mode
395 compare = at_version != prev_at_version
396 compare = at_version != prev_at_version
396
397
397 # pull_requests repo_name we opened it against
398 # pull_requests repo_name we opened it against
398 # ie. target_repo must match
399 # ie. target_repo must match
399 if self.db_repo_name != pull_request_at_ver.target_repo.repo_name:
400 if self.db_repo_name != pull_request_at_ver.target_repo.repo_name:
400 log.warning('Mismatch between the current repo: %s, and target %s',
401 log.warning('Mismatch between the current repo: %s, and target %s',
401 self.db_repo_name, pull_request_at_ver.target_repo.repo_name)
402 self.db_repo_name, pull_request_at_ver.target_repo.repo_name)
402 raise HTTPNotFound()
403 raise HTTPNotFound()
403
404
404 c.shadow_clone_url = PullRequestModel().get_shadow_clone_url(pull_request_at_ver)
405 c.shadow_clone_url = PullRequestModel().get_shadow_clone_url(pull_request_at_ver)
405
406
406 c.pull_request = pull_request_display_obj
407 c.pull_request = pull_request_display_obj
407 c.renderer = pull_request_at_ver.description_renderer or c.renderer
408 c.renderer = pull_request_at_ver.description_renderer or c.renderer
408 c.pull_request_latest = pull_request_latest
409 c.pull_request_latest = pull_request_latest
409
410
410 # inject latest version
411 # inject latest version
411 latest_ver = PullRequest.get_pr_display_object(pull_request_latest, pull_request_latest)
412 latest_ver = PullRequest.get_pr_display_object(pull_request_latest, pull_request_latest)
412 c.versions = versions + [latest_ver]
413 c.versions = versions + [latest_ver]
413
414
414 if compare or (at_version and not at_version == PullRequest.LATEST_VER):
415 if compare or (at_version and not at_version == PullRequest.LATEST_VER):
415 c.allowed_to_change_status = False
416 c.allowed_to_change_status = False
416 c.allowed_to_update = False
417 c.allowed_to_update = False
417 c.allowed_to_merge = False
418 c.allowed_to_merge = False
418 c.allowed_to_delete = False
419 c.allowed_to_delete = False
419 c.allowed_to_comment = False
420 c.allowed_to_comment = False
420 c.allowed_to_close = False
421 c.allowed_to_close = False
421 else:
422 else:
422 can_change_status = PullRequestModel().check_user_change_status(
423 can_change_status = PullRequestModel().check_user_change_status(
423 pull_request_at_ver, self._rhodecode_user)
424 pull_request_at_ver, self._rhodecode_user)
424 c.allowed_to_change_status = can_change_status and not pr_closed
425 c.allowed_to_change_status = can_change_status and not pr_closed
425
426
426 c.allowed_to_update = PullRequestModel().check_user_update(
427 c.allowed_to_update = PullRequestModel().check_user_update(
427 pull_request_latest, self._rhodecode_user) and not pr_closed
428 pull_request_latest, self._rhodecode_user) and not pr_closed
428 c.allowed_to_merge = PullRequestModel().check_user_merge(
429 c.allowed_to_merge = PullRequestModel().check_user_merge(
429 pull_request_latest, self._rhodecode_user) and not pr_closed
430 pull_request_latest, self._rhodecode_user) and not pr_closed
430 c.allowed_to_delete = PullRequestModel().check_user_delete(
431 c.allowed_to_delete = PullRequestModel().check_user_delete(
431 pull_request_latest, self._rhodecode_user) and not pr_closed
432 pull_request_latest, self._rhodecode_user) and not pr_closed
432 c.allowed_to_comment = not pr_closed
433 c.allowed_to_comment = not pr_closed
433 c.allowed_to_close = c.allowed_to_merge and not pr_closed
434 c.allowed_to_close = c.allowed_to_merge and not pr_closed
434
435
435 c.forbid_adding_reviewers = False
436 c.forbid_adding_reviewers = False
436
437
437 if pull_request_latest.reviewer_data and \
438 if pull_request_latest.reviewer_data and \
438 'rules' in pull_request_latest.reviewer_data:
439 'rules' in pull_request_latest.reviewer_data:
439 rules = pull_request_latest.reviewer_data['rules'] or {}
440 rules = pull_request_latest.reviewer_data['rules'] or {}
440 try:
441 try:
441 c.forbid_adding_reviewers = rules.get('forbid_adding_reviewers')
442 c.forbid_adding_reviewers = rules.get('forbid_adding_reviewers')
442 except Exception:
443 except Exception:
443 pass
444 pass
444
445
445 # check merge capabilities
446 # check merge capabilities
446 _merge_check = MergeCheck.validate(
447 _merge_check = MergeCheck.validate(
447 pull_request_latest, auth_user=self._rhodecode_user,
448 pull_request_latest, auth_user=self._rhodecode_user,
448 translator=self.request.translate,
449 translator=self.request.translate,
449 force_shadow_repo_refresh=force_refresh)
450 force_shadow_repo_refresh=force_refresh)
450
451
451 c.pr_merge_errors = _merge_check.error_details
452 c.pr_merge_errors = _merge_check.error_details
452 c.pr_merge_possible = not _merge_check.failed
453 c.pr_merge_possible = not _merge_check.failed
453 c.pr_merge_message = _merge_check.merge_msg
454 c.pr_merge_message = _merge_check.merge_msg
454 c.pr_merge_source_commit = _merge_check.source_commit
455 c.pr_merge_source_commit = _merge_check.source_commit
455 c.pr_merge_target_commit = _merge_check.target_commit
456 c.pr_merge_target_commit = _merge_check.target_commit
456
457
457 c.pr_merge_info = MergeCheck.get_merge_conditions(
458 c.pr_merge_info = MergeCheck.get_merge_conditions(
458 pull_request_latest, translator=self.request.translate)
459 pull_request_latest, translator=self.request.translate)
459
460
460 c.pull_request_review_status = _merge_check.review_status
461 c.pull_request_review_status = _merge_check.review_status
461 if merge_checks:
462 if merge_checks:
462 self.request.override_renderer = \
463 self.request.override_renderer = \
463 'rhodecode:templates/pullrequests/pullrequest_merge_checks.mako'
464 'rhodecode:templates/pullrequests/pullrequest_merge_checks.mako'
464 return self._get_template_context(c)
465 return self._get_template_context(c)
465
466
466 c.reviewers_count = pull_request.reviewers_count
467 c.reviewers_count = pull_request.reviewers_count
467 c.observers_count = pull_request.observers_count
468 c.observers_count = pull_request.observers_count
468
469
469 # reviewers and statuses
470 # reviewers and statuses
470 c.pull_request_default_reviewers_data_json = json.dumps(pull_request.reviewer_data)
471 c.pull_request_default_reviewers_data_json = json.dumps(pull_request.reviewer_data)
471 c.pull_request_set_reviewers_data_json = collections.OrderedDict({'reviewers': []})
472 c.pull_request_set_reviewers_data_json = collections.OrderedDict({'reviewers': []})
472 c.pull_request_set_observers_data_json = collections.OrderedDict({'observers': []})
473 c.pull_request_set_observers_data_json = collections.OrderedDict({'observers': []})
473
474
474 for review_obj, member, reasons, mandatory, status in pull_request_at_ver.reviewers_statuses():
475 for review_obj, member, reasons, mandatory, status in pull_request_at_ver.reviewers_statuses():
475 member_reviewer = h.reviewer_as_json(
476 member_reviewer = h.reviewer_as_json(
476 member, reasons=reasons, mandatory=mandatory,
477 member, reasons=reasons, mandatory=mandatory,
477 role=review_obj.role,
478 role=review_obj.role,
478 user_group=review_obj.rule_user_group_data()
479 user_group=review_obj.rule_user_group_data()
479 )
480 )
480
481
481 current_review_status = status[0][1].status if status else ChangesetStatus.STATUS_NOT_REVIEWED
482 current_review_status = status[0][1].status if status else ChangesetStatus.STATUS_NOT_REVIEWED
482 member_reviewer['review_status'] = current_review_status
483 member_reviewer['review_status'] = current_review_status
483 member_reviewer['review_status_label'] = h.commit_status_lbl(current_review_status)
484 member_reviewer['review_status_label'] = h.commit_status_lbl(current_review_status)
484 member_reviewer['allowed_to_update'] = c.allowed_to_update
485 member_reviewer['allowed_to_update'] = c.allowed_to_update
485 c.pull_request_set_reviewers_data_json['reviewers'].append(member_reviewer)
486 c.pull_request_set_reviewers_data_json['reviewers'].append(member_reviewer)
486
487
487 c.pull_request_set_reviewers_data_json = json.dumps(c.pull_request_set_reviewers_data_json)
488 c.pull_request_set_reviewers_data_json = json.dumps(c.pull_request_set_reviewers_data_json)
488
489
489 for observer_obj, member in pull_request_at_ver.observers():
490 for observer_obj, member in pull_request_at_ver.observers():
490 member_observer = h.reviewer_as_json(
491 member_observer = h.reviewer_as_json(
491 member, reasons=[], mandatory=False,
492 member, reasons=[], mandatory=False,
492 role=observer_obj.role,
493 role=observer_obj.role,
493 user_group=observer_obj.rule_user_group_data()
494 user_group=observer_obj.rule_user_group_data()
494 )
495 )
495 member_observer['allowed_to_update'] = c.allowed_to_update
496 member_observer['allowed_to_update'] = c.allowed_to_update
496 c.pull_request_set_observers_data_json['observers'].append(member_observer)
497 c.pull_request_set_observers_data_json['observers'].append(member_observer)
497
498
498 c.pull_request_set_observers_data_json = json.dumps(c.pull_request_set_observers_data_json)
499 c.pull_request_set_observers_data_json = json.dumps(c.pull_request_set_observers_data_json)
499
500
500 general_comments, inline_comments = \
501 general_comments, inline_comments = \
501 self.register_comments_vars(c, pull_request_latest, versions)
502 self.register_comments_vars(c, pull_request_latest, versions)
502
503
503 # TODOs
504 # TODOs
504 c.unresolved_comments = CommentsModel() \
505 c.unresolved_comments = CommentsModel() \
505 .get_pull_request_unresolved_todos(pull_request_latest)
506 .get_pull_request_unresolved_todos(pull_request_latest)
506 c.resolved_comments = CommentsModel() \
507 c.resolved_comments = CommentsModel() \
507 .get_pull_request_resolved_todos(pull_request_latest)
508 .get_pull_request_resolved_todos(pull_request_latest)
508
509
509 # Drafts
510 # Drafts
510 c.draft_comments = CommentsModel().get_pull_request_drafts(
511 c.draft_comments = CommentsModel().get_pull_request_drafts(
511 self._rhodecode_db_user.user_id,
512 self._rhodecode_db_user.user_id,
512 pull_request_latest)
513 pull_request_latest)
513
514
514 # if we use version, then do not show later comments
515 # if we use version, then do not show later comments
515 # than current version
516 # than current version
516 display_inline_comments = collections.defaultdict(
517 display_inline_comments = collections.defaultdict(
517 lambda: collections.defaultdict(list))
518 lambda: collections.defaultdict(list))
518 for co in inline_comments:
519 for co in inline_comments:
519 if c.at_version_num:
520 if c.at_version_num:
520 # pick comments that are at least UPTO given version, so we
521 # pick comments that are at least UPTO given version, so we
521 # don't render comments for higher version
522 # don't render comments for higher version
522 should_render = co.pull_request_version_id and \
523 should_render = co.pull_request_version_id and \
523 co.pull_request_version_id <= c.at_version_num
524 co.pull_request_version_id <= c.at_version_num
524 else:
525 else:
525 # showing all, for 'latest'
526 # showing all, for 'latest'
526 should_render = True
527 should_render = True
527
528
528 if should_render:
529 if should_render:
529 display_inline_comments[co.f_path][co.line_no].append(co)
530 display_inline_comments[co.f_path][co.line_no].append(co)
530
531
531 # load diff data into template context, if we use compare mode then
532 # load diff data into template context, if we use compare mode then
532 # diff is calculated based on changes between versions of PR
533 # diff is calculated based on changes between versions of PR
533
534
534 source_repo = pull_request_at_ver.source_repo
535 source_repo = pull_request_at_ver.source_repo
535 source_ref_id = pull_request_at_ver.source_ref_parts.commit_id
536 source_ref_id = pull_request_at_ver.source_ref_parts.commit_id
536
537
537 target_repo = pull_request_at_ver.target_repo
538 target_repo = pull_request_at_ver.target_repo
538 target_ref_id = pull_request_at_ver.target_ref_parts.commit_id
539 target_ref_id = pull_request_at_ver.target_ref_parts.commit_id
539
540
540 if compare:
541 if compare:
541 # in compare switch the diff base to latest commit from prev version
542 # in compare switch the diff base to latest commit from prev version
542 target_ref_id = prev_pull_request_display_obj.revisions[0]
543 target_ref_id = prev_pull_request_display_obj.revisions[0]
543
544
544 # despite opening commits for bookmarks/branches/tags, we always
545 # despite opening commits for bookmarks/branches/tags, we always
545 # convert this to rev to prevent changes after bookmark or branch change
546 # convert this to rev to prevent changes after bookmark or branch change
546 c.source_ref_type = 'rev'
547 c.source_ref_type = 'rev'
547 c.source_ref = source_ref_id
548 c.source_ref = source_ref_id
548
549
549 c.target_ref_type = 'rev'
550 c.target_ref_type = 'rev'
550 c.target_ref = target_ref_id
551 c.target_ref = target_ref_id
551
552
552 c.source_repo = source_repo
553 c.source_repo = source_repo
553 c.target_repo = target_repo
554 c.target_repo = target_repo
554
555
555 c.commit_ranges = []
556 c.commit_ranges = []
556 source_commit = EmptyCommit()
557 source_commit = EmptyCommit()
557 target_commit = EmptyCommit()
558 target_commit = EmptyCommit()
558 c.missing_requirements = False
559 c.missing_requirements = False
559
560
560 source_scm = source_repo.scm_instance()
561 source_scm = source_repo.scm_instance()
561 target_scm = target_repo.scm_instance()
562 target_scm = target_repo.scm_instance()
562
563
563 shadow_scm = None
564 shadow_scm = None
564 try:
565 try:
565 shadow_scm = pull_request_latest.get_shadow_repo()
566 shadow_scm = pull_request_latest.get_shadow_repo()
566 except Exception:
567 except Exception:
567 log.debug('Failed to get shadow repo', exc_info=True)
568 log.debug('Failed to get shadow repo', exc_info=True)
568 # try first the existing source_repo, and then shadow
569 # try first the existing source_repo, and then shadow
569 # repo if we can obtain one
570 # repo if we can obtain one
570 commits_source_repo = source_scm
571 commits_source_repo = source_scm
571 if shadow_scm:
572 if shadow_scm:
572 commits_source_repo = shadow_scm
573 commits_source_repo = shadow_scm
573
574
574 c.commits_source_repo = commits_source_repo
575 c.commits_source_repo = commits_source_repo
575 c.ancestor = None # set it to None, to hide it from PR view
576 c.ancestor = None # set it to None, to hide it from PR view
576
577
577 # empty version means latest, so we keep this to prevent
578 # empty version means latest, so we keep this to prevent
578 # double caching
579 # double caching
579 version_normalized = version or PullRequest.LATEST_VER
580 version_normalized = version or PullRequest.LATEST_VER
580 from_version_normalized = from_version or PullRequest.LATEST_VER
581 from_version_normalized = from_version or PullRequest.LATEST_VER
581
582
582 cache_path = self.rhodecode_vcs_repo.get_create_shadow_cache_pr_path(target_repo)
583 cache_path = self.rhodecode_vcs_repo.get_create_shadow_cache_pr_path(target_repo)
583 cache_file_path = diff_cache_exist(
584 cache_file_path = diff_cache_exist(
584 cache_path, 'pull_request', pull_request_id, version_normalized,
585 cache_path, 'pull_request', pull_request_id, version_normalized,
585 from_version_normalized, source_ref_id, target_ref_id,
586 from_version_normalized, source_ref_id, target_ref_id,
586 hide_whitespace_changes, diff_context, c.fulldiff)
587 hide_whitespace_changes, diff_context, c.fulldiff)
587
588
588 caching_enabled = self._is_diff_cache_enabled(c.target_repo)
589 caching_enabled = self._is_diff_cache_enabled(c.target_repo)
589 force_recache = self.get_recache_flag()
590 force_recache = self.get_recache_flag()
590
591
591 cached_diff = None
592 cached_diff = None
592 if caching_enabled:
593 if caching_enabled:
593 cached_diff = load_cached_diff(cache_file_path)
594 cached_diff = load_cached_diff(cache_file_path)
594
595
595 has_proper_commit_cache = (
596 has_proper_commit_cache = (
596 cached_diff and cached_diff.get('commits')
597 cached_diff and cached_diff.get('commits')
597 and len(cached_diff.get('commits', [])) == 5
598 and len(cached_diff.get('commits', [])) == 5
598 and cached_diff.get('commits')[0]
599 and cached_diff.get('commits')[0]
599 and cached_diff.get('commits')[3])
600 and cached_diff.get('commits')[3])
600
601
601 if not force_recache and not c.range_diff_on and has_proper_commit_cache:
602 if not force_recache and not c.range_diff_on and has_proper_commit_cache:
602 diff_commit_cache = \
603 diff_commit_cache = \
603 (ancestor_commit, commit_cache, missing_requirements,
604 (ancestor_commit, commit_cache, missing_requirements,
604 source_commit, target_commit) = cached_diff['commits']
605 source_commit, target_commit) = cached_diff['commits']
605 else:
606 else:
606 # NOTE(marcink): we reach potentially unreachable errors when a PR has
607 # NOTE(marcink): we reach potentially unreachable errors when a PR has
607 # merge errors resulting in potentially hidden commits in the shadow repo.
608 # merge errors resulting in potentially hidden commits in the shadow repo.
608 maybe_unreachable = _merge_check.MERGE_CHECK in _merge_check.error_details \
609 maybe_unreachable = _merge_check.MERGE_CHECK in _merge_check.error_details \
609 and _merge_check.merge_response
610 and _merge_check.merge_response
610 maybe_unreachable = maybe_unreachable \
611 maybe_unreachable = maybe_unreachable \
611 and _merge_check.merge_response.metadata.get('unresolved_files')
612 and _merge_check.merge_response.metadata.get('unresolved_files')
612 log.debug("Using unreachable commits due to MERGE_CHECK in merge simulation")
613 log.debug("Using unreachable commits due to MERGE_CHECK in merge simulation")
613 diff_commit_cache = \
614 diff_commit_cache = \
614 (ancestor_commit, commit_cache, missing_requirements,
615 (ancestor_commit, commit_cache, missing_requirements,
615 source_commit, target_commit) = self.get_commits(
616 source_commit, target_commit) = self.get_commits(
616 commits_source_repo,
617 commits_source_repo,
617 pull_request_at_ver,
618 pull_request_at_ver,
618 source_commit,
619 source_commit,
619 source_ref_id,
620 source_ref_id,
620 source_scm,
621 source_scm,
621 target_commit,
622 target_commit,
622 target_ref_id,
623 target_ref_id,
623 target_scm,
624 target_scm,
624 maybe_unreachable=maybe_unreachable)
625 maybe_unreachable=maybe_unreachable)
625
626
626 # register our commit range
627 # register our commit range
627 for comm in commit_cache.values():
628 for comm in commit_cache.values():
628 c.commit_ranges.append(comm)
629 c.commit_ranges.append(comm)
629
630
630 c.missing_requirements = missing_requirements
631 c.missing_requirements = missing_requirements
631 c.ancestor_commit = ancestor_commit
632 c.ancestor_commit = ancestor_commit
632 c.statuses = source_repo.statuses(
633 c.statuses = source_repo.statuses(
633 [x.raw_id for x in c.commit_ranges])
634 [x.raw_id for x in c.commit_ranges])
634
635
635 # auto collapse if we have more than limit
636 # auto collapse if we have more than limit
636 collapse_limit = diffs.DiffProcessor._collapse_commits_over
637 collapse_limit = diffs.DiffProcessor._collapse_commits_over
637 c.collapse_all_commits = len(c.commit_ranges) > collapse_limit
638 c.collapse_all_commits = len(c.commit_ranges) > collapse_limit
638 c.compare_mode = compare
639 c.compare_mode = compare
639
640
640 # diff_limit is the old behavior, will cut off the whole diff
641 # diff_limit is the old behavior, will cut off the whole diff
641 # if the limit is applied otherwise will just hide the
642 # if the limit is applied otherwise will just hide the
642 # big files from the front-end
643 # big files from the front-end
643 diff_limit = c.visual.cut_off_limit_diff
644 diff_limit = c.visual.cut_off_limit_diff
644 file_limit = c.visual.cut_off_limit_file
645 file_limit = c.visual.cut_off_limit_file
645
646
646 c.missing_commits = False
647 c.missing_commits = False
647 if (c.missing_requirements
648 if (c.missing_requirements
648 or isinstance(source_commit, EmptyCommit)
649 or isinstance(source_commit, EmptyCommit)
649 or source_commit == target_commit):
650 or source_commit == target_commit):
650
651
651 c.missing_commits = True
652 c.missing_commits = True
652 else:
653 else:
653 c.inline_comments = display_inline_comments
654 c.inline_comments = display_inline_comments
654
655
655 use_ancestor = True
656 use_ancestor = True
656 if from_version_normalized != version_normalized:
657 if from_version_normalized != version_normalized:
657 use_ancestor = False
658 use_ancestor = False
658
659
659 has_proper_diff_cache = cached_diff and cached_diff.get('commits')
660 has_proper_diff_cache = cached_diff and cached_diff.get('commits')
660 if not force_recache and has_proper_diff_cache:
661 if not force_recache and has_proper_diff_cache:
661 c.diffset = cached_diff['diff']
662 c.diffset = cached_diff['diff']
662 else:
663 else:
663 try:
664 try:
664 c.diffset = self._get_diffset(
665 c.diffset = self._get_diffset(
665 c.source_repo.repo_name, commits_source_repo,
666 c.source_repo.repo_name, commits_source_repo,
666 c.ancestor_commit,
667 c.ancestor_commit,
667 source_ref_id, target_ref_id,
668 source_ref_id, target_ref_id,
668 target_commit, source_commit,
669 target_commit, source_commit,
669 diff_limit, file_limit, c.fulldiff,
670 diff_limit, file_limit, c.fulldiff,
670 hide_whitespace_changes, diff_context,
671 hide_whitespace_changes, diff_context,
671 use_ancestor=use_ancestor
672 use_ancestor=use_ancestor
672 )
673 )
673
674
674 # save cached diff
675 # save cached diff
675 if caching_enabled:
676 if caching_enabled:
676 cache_diff(cache_file_path, c.diffset, diff_commit_cache)
677 cache_diff(cache_file_path, c.diffset, diff_commit_cache)
677 except CommitDoesNotExistError:
678 except CommitDoesNotExistError:
678 log.exception('Failed to generate diffset')
679 log.exception('Failed to generate diffset')
679 c.missing_commits = True
680 c.missing_commits = True
680
681
681 if not c.missing_commits:
682 if not c.missing_commits:
682
683
683 c.limited_diff = c.diffset.limited_diff
684 c.limited_diff = c.diffset.limited_diff
684
685
685 # calculate removed files that are bound to comments
686 # calculate removed files that are bound to comments
686 comment_deleted_files = [
687 comment_deleted_files = [
687 fname for fname in display_inline_comments
688 fname for fname in display_inline_comments
688 if fname not in c.diffset.file_stats]
689 if fname not in c.diffset.file_stats]
689
690
690 c.deleted_files_comments = collections.defaultdict(dict)
691 c.deleted_files_comments = collections.defaultdict(dict)
691 for fname, per_line_comments in display_inline_comments.items():
692 for fname, per_line_comments in display_inline_comments.items():
692 if fname in comment_deleted_files:
693 if fname in comment_deleted_files:
693 c.deleted_files_comments[fname]['stats'] = 0
694 c.deleted_files_comments[fname]['stats'] = 0
694 c.deleted_files_comments[fname]['comments'] = list()
695 c.deleted_files_comments[fname]['comments'] = list()
695 for lno, comments in per_line_comments.items():
696 for lno, comments in per_line_comments.items():
696 c.deleted_files_comments[fname]['comments'].extend(comments)
697 c.deleted_files_comments[fname]['comments'].extend(comments)
697
698
698 # maybe calculate the range diff
699 # maybe calculate the range diff
699 if c.range_diff_on:
700 if c.range_diff_on:
700 # TODO(marcink): set whitespace/context
701 # TODO(marcink): set whitespace/context
701 context_lcl = 3
702 context_lcl = 3
702 ign_whitespace_lcl = False
703 ign_whitespace_lcl = False
703
704
704 for commit in c.commit_ranges:
705 for commit in c.commit_ranges:
705 commit2 = commit
706 commit2 = commit
706 commit1 = commit.first_parent
707 commit1 = commit.first_parent
707
708
708 range_diff_cache_file_path = diff_cache_exist(
709 range_diff_cache_file_path = diff_cache_exist(
709 cache_path, 'diff', commit.raw_id,
710 cache_path, 'diff', commit.raw_id,
710 ign_whitespace_lcl, context_lcl, c.fulldiff)
711 ign_whitespace_lcl, context_lcl, c.fulldiff)
711
712
712 cached_diff = None
713 cached_diff = None
713 if caching_enabled:
714 if caching_enabled:
714 cached_diff = load_cached_diff(range_diff_cache_file_path)
715 cached_diff = load_cached_diff(range_diff_cache_file_path)
715
716
716 has_proper_diff_cache = cached_diff and cached_diff.get('diff')
717 has_proper_diff_cache = cached_diff and cached_diff.get('diff')
717 if not force_recache and has_proper_diff_cache:
718 if not force_recache and has_proper_diff_cache:
718 diffset = cached_diff['diff']
719 diffset = cached_diff['diff']
719 else:
720 else:
720 diffset = self._get_range_diffset(
721 diffset = self._get_range_diffset(
721 commits_source_repo, source_repo,
722 commits_source_repo, source_repo,
722 commit1, commit2, diff_limit, file_limit,
723 commit1, commit2, diff_limit, file_limit,
723 c.fulldiff, ign_whitespace_lcl, context_lcl
724 c.fulldiff, ign_whitespace_lcl, context_lcl
724 )
725 )
725
726
726 # save cached diff
727 # save cached diff
727 if caching_enabled:
728 if caching_enabled:
728 cache_diff(range_diff_cache_file_path, diffset, None)
729 cache_diff(range_diff_cache_file_path, diffset, None)
729
730
730 c.changes[commit.raw_id] = diffset
731 c.changes[commit.raw_id] = diffset
731
732
732 # this is a hack to properly display links, when creating PR, the
733 # this is a hack to properly display links, when creating PR, the
733 # compare view and others uses different notation, and
734 # compare view and others uses different notation, and
734 # compare_commits.mako renders links based on the target_repo.
735 # compare_commits.mako renders links based on the target_repo.
735 # We need to swap that here to generate it properly on the html side
736 # We need to swap that here to generate it properly on the html side
736 c.target_repo = c.source_repo
737 c.target_repo = c.source_repo
737
738
738 c.commit_statuses = ChangesetStatus.STATUSES
739 c.commit_statuses = ChangesetStatus.STATUSES
739
740
740 c.show_version_changes = not pr_closed
741 c.show_version_changes = not pr_closed
741 if c.show_version_changes:
742 if c.show_version_changes:
742 cur_obj = pull_request_at_ver
743 cur_obj = pull_request_at_ver
743 prev_obj = prev_pull_request_at_ver
744 prev_obj = prev_pull_request_at_ver
744
745
745 old_commit_ids = prev_obj.revisions
746 old_commit_ids = prev_obj.revisions
746 new_commit_ids = cur_obj.revisions
747 new_commit_ids = cur_obj.revisions
747 commit_changes = PullRequestModel()._calculate_commit_id_changes(
748 commit_changes = PullRequestModel()._calculate_commit_id_changes(
748 old_commit_ids, new_commit_ids)
749 old_commit_ids, new_commit_ids)
749 c.commit_changes_summary = commit_changes
750 c.commit_changes_summary = commit_changes
750
751
751 # calculate the diff for commits between versions
752 # calculate the diff for commits between versions
752 c.commit_changes = []
753 c.commit_changes = []
753
754
754 def mark(cs, fw):
755 def mark(cs, fw):
755 return list(h.itertools.izip_longest([], cs, fillvalue=fw))
756 return list(h.itertools.izip_longest([], cs, fillvalue=fw))
756
757
757 for c_type, raw_id in mark(commit_changes.added, 'a') \
758 for c_type, raw_id in mark(commit_changes.added, 'a') \
758 + mark(commit_changes.removed, 'r') \
759 + mark(commit_changes.removed, 'r') \
759 + mark(commit_changes.common, 'c'):
760 + mark(commit_changes.common, 'c'):
760
761
761 if raw_id in commit_cache:
762 if raw_id in commit_cache:
762 commit = commit_cache[raw_id]
763 commit = commit_cache[raw_id]
763 else:
764 else:
764 try:
765 try:
765 commit = commits_source_repo.get_commit(raw_id)
766 commit = commits_source_repo.get_commit(raw_id)
766 except CommitDoesNotExistError:
767 except CommitDoesNotExistError:
767 # in case we fail extracting still use "dummy" commit
768 # in case we fail extracting still use "dummy" commit
768 # for display in commit diff
769 # for display in commit diff
769 commit = h.AttributeDict(
770 commit = h.AttributeDict(
770 {'raw_id': raw_id,
771 {'raw_id': raw_id,
771 'message': 'EMPTY or MISSING COMMIT'})
772 'message': 'EMPTY or MISSING COMMIT'})
772 c.commit_changes.append([c_type, commit])
773 c.commit_changes.append([c_type, commit])
773
774
774 # current user review statuses for each version
775 # current user review statuses for each version
775 c.review_versions = {}
776 c.review_versions = {}
776 is_reviewer = PullRequestModel().is_user_reviewer(
777 is_reviewer = PullRequestModel().is_user_reviewer(
777 pull_request, self._rhodecode_user)
778 pull_request, self._rhodecode_user)
778 if is_reviewer:
779 if is_reviewer:
779 for co in general_comments:
780 for co in general_comments:
780 if co.author.user_id == self._rhodecode_user.user_id:
781 if co.author.user_id == self._rhodecode_user.user_id:
781 status = co.status_change
782 status = co.status_change
782 if status:
783 if status:
783 _ver_pr = status[0].comment.pull_request_version_id
784 _ver_pr = status[0].comment.pull_request_version_id
784 c.review_versions[_ver_pr] = status[0]
785 c.review_versions[_ver_pr] = status[0]
785
786
786 return self._get_template_context(c)
787 return self._get_template_context(c)
787
788
788 def get_commits(
789 def get_commits(
789 self, commits_source_repo, pull_request_at_ver, source_commit,
790 self, commits_source_repo, pull_request_at_ver, source_commit,
790 source_ref_id, source_scm, target_commit, target_ref_id, target_scm,
791 source_ref_id, source_scm, target_commit, target_ref_id, target_scm,
791 maybe_unreachable=False):
792 maybe_unreachable=False):
792
793
793 commit_cache = collections.OrderedDict()
794 commit_cache = collections.OrderedDict()
794 missing_requirements = False
795 missing_requirements = False
795
796
796 try:
797 try:
797 pre_load = ["author", "date", "message", "branch", "parents"]
798 pre_load = ["author", "date", "message", "branch", "parents"]
798
799
799 pull_request_commits = pull_request_at_ver.revisions
800 pull_request_commits = pull_request_at_ver.revisions
800 log.debug('Loading %s commits from %s',
801 log.debug('Loading %s commits from %s',
801 len(pull_request_commits), commits_source_repo)
802 len(pull_request_commits), commits_source_repo)
802
803
803 for rev in pull_request_commits:
804 for rev in pull_request_commits:
804 comm = commits_source_repo.get_commit(commit_id=rev, pre_load=pre_load,
805 comm = commits_source_repo.get_commit(commit_id=rev, pre_load=pre_load,
805 maybe_unreachable=maybe_unreachable)
806 maybe_unreachable=maybe_unreachable)
806 commit_cache[comm.raw_id] = comm
807 commit_cache[comm.raw_id] = comm
807
808
808 # Order here matters, we first need to get target, and then
809 # Order here matters, we first need to get target, and then
809 # the source
810 # the source
810 target_commit = commits_source_repo.get_commit(
811 target_commit = commits_source_repo.get_commit(
811 commit_id=safe_str(target_ref_id))
812 commit_id=safe_str(target_ref_id))
812
813
813 source_commit = commits_source_repo.get_commit(
814 source_commit = commits_source_repo.get_commit(
814 commit_id=safe_str(source_ref_id), maybe_unreachable=True)
815 commit_id=safe_str(source_ref_id), maybe_unreachable=True)
815 except CommitDoesNotExistError:
816 except CommitDoesNotExistError:
816 log.warning('Failed to get commit from `{}` repo'.format(
817 log.warning('Failed to get commit from `{}` repo'.format(
817 commits_source_repo), exc_info=True)
818 commits_source_repo), exc_info=True)
818 except RepositoryRequirementError:
819 except RepositoryRequirementError:
819 log.warning('Failed to get all required data from repo', exc_info=True)
820 log.warning('Failed to get all required data from repo', exc_info=True)
820 missing_requirements = True
821 missing_requirements = True
821
822
822 pr_ancestor_id = pull_request_at_ver.common_ancestor_id
823 pr_ancestor_id = pull_request_at_ver.common_ancestor_id
823
824
824 try:
825 try:
825 ancestor_commit = source_scm.get_commit(pr_ancestor_id)
826 ancestor_commit = source_scm.get_commit(pr_ancestor_id)
826 except Exception:
827 except Exception:
827 ancestor_commit = None
828 ancestor_commit = None
828
829
829 return ancestor_commit, commit_cache, missing_requirements, source_commit, target_commit
830 return ancestor_commit, commit_cache, missing_requirements, source_commit, target_commit
830
831
831 def assure_not_empty_repo(self):
832 def assure_not_empty_repo(self):
832 _ = self.request.translate
833 _ = self.request.translate
833
834
834 try:
835 try:
835 self.db_repo.scm_instance().get_commit()
836 self.db_repo.scm_instance().get_commit()
836 except EmptyRepositoryError:
837 except EmptyRepositoryError:
837 h.flash(h.literal(_('There are no commits yet')),
838 h.flash(h.literal(_('There are no commits yet')),
838 category='warning')
839 category='warning')
839 raise HTTPFound(
840 raise HTTPFound(
840 h.route_path('repo_summary', repo_name=self.db_repo.repo_name))
841 h.route_path('repo_summary', repo_name=self.db_repo.repo_name))
841
842
842 @LoginRequired()
843 @LoginRequired()
843 @NotAnonymous()
844 @NotAnonymous()
844 @HasRepoPermissionAnyDecorator(
845 @HasRepoPermissionAnyDecorator(
845 'repository.read', 'repository.write', 'repository.admin')
846 'repository.read', 'repository.write', 'repository.admin')
846 def pull_request_new(self):
847 def pull_request_new(self):
847 _ = self.request.translate
848 _ = self.request.translate
848 c = self.load_default_context()
849 c = self.load_default_context()
849
850
850 self.assure_not_empty_repo()
851 self.assure_not_empty_repo()
851 source_repo = self.db_repo
852 source_repo = self.db_repo
852
853
853 commit_id = self.request.GET.get('commit')
854 commit_id = self.request.GET.get('commit')
854 branch_ref = self.request.GET.get('branch')
855 branch_ref = self.request.GET.get('branch')
855 bookmark_ref = self.request.GET.get('bookmark')
856 bookmark_ref = self.request.GET.get('bookmark')
856
857
857 try:
858 try:
858 source_repo_data = PullRequestModel().generate_repo_data(
859 source_repo_data = PullRequestModel().generate_repo_data(
859 source_repo, commit_id=commit_id,
860 source_repo, commit_id=commit_id,
860 branch=branch_ref, bookmark=bookmark_ref,
861 branch=branch_ref, bookmark=bookmark_ref,
861 translator=self.request.translate)
862 translator=self.request.translate)
862 except CommitDoesNotExistError as e:
863 except CommitDoesNotExistError as e:
863 log.exception(e)
864 log.exception(e)
864 h.flash(_('Commit does not exist'), 'error')
865 h.flash(_('Commit does not exist'), 'error')
865 raise HTTPFound(
866 raise HTTPFound(
866 h.route_path('pullrequest_new', repo_name=source_repo.repo_name))
867 h.route_path('pullrequest_new', repo_name=source_repo.repo_name))
867
868
868 default_target_repo = source_repo
869 default_target_repo = source_repo
869
870
870 if source_repo.parent and c.has_origin_repo_read_perm:
871 if source_repo.parent and c.has_origin_repo_read_perm:
871 parent_vcs_obj = source_repo.parent.scm_instance()
872 parent_vcs_obj = source_repo.parent.scm_instance()
872 if parent_vcs_obj and not parent_vcs_obj.is_empty():
873 if parent_vcs_obj and not parent_vcs_obj.is_empty():
873 # change default if we have a parent repo
874 # change default if we have a parent repo
874 default_target_repo = source_repo.parent
875 default_target_repo = source_repo.parent
875
876
876 target_repo_data = PullRequestModel().generate_repo_data(
877 target_repo_data = PullRequestModel().generate_repo_data(
877 default_target_repo, translator=self.request.translate)
878 default_target_repo, translator=self.request.translate)
878
879
879 selected_source_ref = source_repo_data['refs']['selected_ref']
880 selected_source_ref = source_repo_data['refs']['selected_ref']
880 title_source_ref = ''
881 title_source_ref = ''
881 if selected_source_ref:
882 if selected_source_ref:
882 title_source_ref = selected_source_ref.split(':', 2)[1]
883 title_source_ref = selected_source_ref.split(':', 2)[1]
883 c.default_title = PullRequestModel().generate_pullrequest_title(
884 c.default_title = PullRequestModel().generate_pullrequest_title(
884 source=source_repo.repo_name,
885 source=source_repo.repo_name,
885 source_ref=title_source_ref,
886 source_ref=title_source_ref,
886 target=default_target_repo.repo_name
887 target=default_target_repo.repo_name
887 )
888 )
888
889
889 c.default_repo_data = {
890 c.default_repo_data = {
890 'source_repo_name': source_repo.repo_name,
891 'source_repo_name': source_repo.repo_name,
891 'source_refs_json': json.dumps(source_repo_data),
892 'source_refs_json': json.dumps(source_repo_data),
892 'target_repo_name': default_target_repo.repo_name,
893 'target_repo_name': default_target_repo.repo_name,
893 'target_refs_json': json.dumps(target_repo_data),
894 'target_refs_json': json.dumps(target_repo_data),
894 }
895 }
895 c.default_source_ref = selected_source_ref
896 c.default_source_ref = selected_source_ref
896
897
897 return self._get_template_context(c)
898 return self._get_template_context(c)
898
899
899 @LoginRequired()
900 @LoginRequired()
900 @NotAnonymous()
901 @NotAnonymous()
901 @HasRepoPermissionAnyDecorator(
902 @HasRepoPermissionAnyDecorator(
902 'repository.read', 'repository.write', 'repository.admin')
903 'repository.read', 'repository.write', 'repository.admin')
903 def pull_request_repo_refs(self):
904 def pull_request_repo_refs(self):
904 self.load_default_context()
905 self.load_default_context()
905 target_repo_name = self.request.matchdict['target_repo_name']
906 target_repo_name = self.request.matchdict['target_repo_name']
906 repo = Repository.get_by_repo_name(target_repo_name)
907 repo = Repository.get_by_repo_name(target_repo_name)
907 if not repo:
908 if not repo:
908 raise HTTPNotFound()
909 raise HTTPNotFound()
909
910
910 target_perm = HasRepoPermissionAny(
911 target_perm = HasRepoPermissionAny(
911 'repository.read', 'repository.write', 'repository.admin')(
912 'repository.read', 'repository.write', 'repository.admin')(
912 target_repo_name)
913 target_repo_name)
913 if not target_perm:
914 if not target_perm:
914 raise HTTPNotFound()
915 raise HTTPNotFound()
915
916
916 return PullRequestModel().generate_repo_data(
917 return PullRequestModel().generate_repo_data(
917 repo, translator=self.request.translate)
918 repo, translator=self.request.translate)
918
919
919 @LoginRequired()
920 @LoginRequired()
920 @NotAnonymous()
921 @NotAnonymous()
921 @HasRepoPermissionAnyDecorator(
922 @HasRepoPermissionAnyDecorator(
922 'repository.read', 'repository.write', 'repository.admin')
923 'repository.read', 'repository.write', 'repository.admin')
923 def pullrequest_repo_targets(self):
924 def pullrequest_repo_targets(self):
924 _ = self.request.translate
925 _ = self.request.translate
925 filter_query = self.request.GET.get('query')
926 filter_query = self.request.GET.get('query')
926
927
927 # get the parents
928 # get the parents
928 parent_target_repos = []
929 parent_target_repos = []
929 if self.db_repo.parent:
930 if self.db_repo.parent:
930 parents_query = Repository.query() \
931 parents_query = Repository.query() \
931 .order_by(func.length(Repository.repo_name)) \
932 .order_by(func.length(Repository.repo_name)) \
932 .filter(Repository.fork_id == self.db_repo.parent.repo_id)
933 .filter(Repository.fork_id == self.db_repo.parent.repo_id)
933
934
934 if filter_query:
935 if filter_query:
935 ilike_expression = u'%{}%'.format(safe_unicode(filter_query))
936 ilike_expression = u'%{}%'.format(safe_unicode(filter_query))
936 parents_query = parents_query.filter(
937 parents_query = parents_query.filter(
937 Repository.repo_name.ilike(ilike_expression))
938 Repository.repo_name.ilike(ilike_expression))
938 parents = parents_query.limit(20).all()
939 parents = parents_query.limit(20).all()
939
940
940 for parent in parents:
941 for parent in parents:
941 parent_vcs_obj = parent.scm_instance()
942 parent_vcs_obj = parent.scm_instance()
942 if parent_vcs_obj and not parent_vcs_obj.is_empty():
943 if parent_vcs_obj and not parent_vcs_obj.is_empty():
943 parent_target_repos.append(parent)
944 parent_target_repos.append(parent)
944
945
945 # get other forks, and repo itself
946 # get other forks, and repo itself
946 query = Repository.query() \
947 query = Repository.query() \
947 .order_by(func.length(Repository.repo_name)) \
948 .order_by(func.length(Repository.repo_name)) \
948 .filter(
949 .filter(
949 or_(Repository.repo_id == self.db_repo.repo_id, # repo itself
950 or_(Repository.repo_id == self.db_repo.repo_id, # repo itself
950 Repository.fork_id == self.db_repo.repo_id) # forks of this repo
951 Repository.fork_id == self.db_repo.repo_id) # forks of this repo
951 ) \
952 ) \
952 .filter(~Repository.repo_id.in_([x.repo_id for x in parent_target_repos]))
953 .filter(~Repository.repo_id.in_([x.repo_id for x in parent_target_repos]))
953
954
954 if filter_query:
955 if filter_query:
955 ilike_expression = u'%{}%'.format(safe_unicode(filter_query))
956 ilike_expression = u'%{}%'.format(safe_unicode(filter_query))
956 query = query.filter(Repository.repo_name.ilike(ilike_expression))
957 query = query.filter(Repository.repo_name.ilike(ilike_expression))
957
958
958 limit = max(20 - len(parent_target_repos), 5) # not less then 5
959 limit = max(20 - len(parent_target_repos), 5) # not less then 5
959 target_repos = query.limit(limit).all()
960 target_repos = query.limit(limit).all()
960
961
961 all_target_repos = target_repos + parent_target_repos
962 all_target_repos = target_repos + parent_target_repos
962
963
963 repos = []
964 repos = []
964 # This checks permissions to the repositories
965 # This checks permissions to the repositories
965 for obj in ScmModel().get_repos(all_target_repos):
966 for obj in ScmModel().get_repos(all_target_repos):
966 repos.append({
967 repos.append({
967 'id': obj['name'],
968 'id': obj['name'],
968 'text': obj['name'],
969 'text': obj['name'],
969 'type': 'repo',
970 'type': 'repo',
970 'repo_id': obj['dbrepo']['repo_id'],
971 'repo_id': obj['dbrepo']['repo_id'],
971 'repo_type': obj['dbrepo']['repo_type'],
972 'repo_type': obj['dbrepo']['repo_type'],
972 'private': obj['dbrepo']['private'],
973 'private': obj['dbrepo']['private'],
973
974
974 })
975 })
975
976
976 data = {
977 data = {
977 'more': False,
978 'more': False,
978 'results': [{
979 'results': [{
979 'text': _('Repositories'),
980 'text': _('Repositories'),
980 'children': repos
981 'children': repos
981 }] if repos else []
982 }] if repos else []
982 }
983 }
983 return data
984 return data
984
985
985 @classmethod
986 @classmethod
986 def get_comment_ids(cls, post_data):
987 def get_comment_ids(cls, post_data):
987 return filter(lambda e: e > 0, map(safe_int, aslist(post_data.get('comments'), ',')))
988 return filter(lambda e: e > 0, map(safe_int, aslist(post_data.get('comments'), ',')))
988
989
989 @LoginRequired()
990 @LoginRequired()
990 @NotAnonymous()
991 @NotAnonymous()
991 @HasRepoPermissionAnyDecorator(
992 @HasRepoPermissionAnyDecorator(
992 'repository.read', 'repository.write', 'repository.admin')
993 'repository.read', 'repository.write', 'repository.admin')
993 def pullrequest_comments(self):
994 def pullrequest_comments(self):
994 self.load_default_context()
995 self.load_default_context()
995
996
996 pull_request = PullRequest.get_or_404(
997 pull_request = PullRequest.get_or_404(
997 self.request.matchdict['pull_request_id'])
998 self.request.matchdict['pull_request_id'])
998 pull_request_id = pull_request.pull_request_id
999 pull_request_id = pull_request.pull_request_id
999 version = self.request.GET.get('version')
1000 version = self.request.GET.get('version')
1000
1001
1001 _render = self.request.get_partial_renderer(
1002 _render = self.request.get_partial_renderer(
1002 'rhodecode:templates/base/sidebar.mako')
1003 'rhodecode:templates/base/sidebar.mako')
1003 c = _render.get_call_context()
1004 c = _render.get_call_context()
1004
1005
1005 (pull_request_latest,
1006 (pull_request_latest,
1006 pull_request_at_ver,
1007 pull_request_at_ver,
1007 pull_request_display_obj,
1008 pull_request_display_obj,
1008 at_version) = PullRequestModel().get_pr_version(
1009 at_version) = PullRequestModel().get_pr_version(
1009 pull_request_id, version=version)
1010 pull_request_id, version=version)
1010 versions = pull_request_display_obj.versions()
1011 versions = pull_request_display_obj.versions()
1011 latest_ver = PullRequest.get_pr_display_object(pull_request_latest, pull_request_latest)
1012 latest_ver = PullRequest.get_pr_display_object(pull_request_latest, pull_request_latest)
1012 c.versions = versions + [latest_ver]
1013 c.versions = versions + [latest_ver]
1013
1014
1014 c.at_version = at_version
1015 c.at_version = at_version
1015 c.at_version_num = (at_version
1016 c.at_version_num = (at_version
1016 if at_version and at_version != PullRequest.LATEST_VER
1017 if at_version and at_version != PullRequest.LATEST_VER
1017 else None)
1018 else None)
1018
1019
1019 self.register_comments_vars(c, pull_request_latest, versions, include_drafts=False)
1020 self.register_comments_vars(c, pull_request_latest, versions, include_drafts=False)
1020 all_comments = c.inline_comments_flat + c.comments
1021 all_comments = c.inline_comments_flat + c.comments
1021
1022
1022 existing_ids = self.get_comment_ids(self.request.POST)
1023 existing_ids = self.get_comment_ids(self.request.POST)
1023 return _render('comments_table', all_comments, len(all_comments),
1024 return _render('comments_table', all_comments, len(all_comments),
1024 existing_ids=existing_ids)
1025 existing_ids=existing_ids)
1025
1026
1026 @LoginRequired()
1027 @LoginRequired()
1027 @NotAnonymous()
1028 @NotAnonymous()
1028 @HasRepoPermissionAnyDecorator(
1029 @HasRepoPermissionAnyDecorator(
1029 'repository.read', 'repository.write', 'repository.admin')
1030 'repository.read', 'repository.write', 'repository.admin')
1030 def pullrequest_todos(self):
1031 def pullrequest_todos(self):
1031 self.load_default_context()
1032 self.load_default_context()
1032
1033
1033 pull_request = PullRequest.get_or_404(
1034 pull_request = PullRequest.get_or_404(
1034 self.request.matchdict['pull_request_id'])
1035 self.request.matchdict['pull_request_id'])
1035 pull_request_id = pull_request.pull_request_id
1036 pull_request_id = pull_request.pull_request_id
1036 version = self.request.GET.get('version')
1037 version = self.request.GET.get('version')
1037
1038
1038 _render = self.request.get_partial_renderer(
1039 _render = self.request.get_partial_renderer(
1039 'rhodecode:templates/base/sidebar.mako')
1040 'rhodecode:templates/base/sidebar.mako')
1040 c = _render.get_call_context()
1041 c = _render.get_call_context()
1041 (pull_request_latest,
1042 (pull_request_latest,
1042 pull_request_at_ver,
1043 pull_request_at_ver,
1043 pull_request_display_obj,
1044 pull_request_display_obj,
1044 at_version) = PullRequestModel().get_pr_version(
1045 at_version) = PullRequestModel().get_pr_version(
1045 pull_request_id, version=version)
1046 pull_request_id, version=version)
1046 versions = pull_request_display_obj.versions()
1047 versions = pull_request_display_obj.versions()
1047 latest_ver = PullRequest.get_pr_display_object(pull_request_latest, pull_request_latest)
1048 latest_ver = PullRequest.get_pr_display_object(pull_request_latest, pull_request_latest)
1048 c.versions = versions + [latest_ver]
1049 c.versions = versions + [latest_ver]
1049
1050
1050 c.at_version = at_version
1051 c.at_version = at_version
1051 c.at_version_num = (at_version
1052 c.at_version_num = (at_version
1052 if at_version and at_version != PullRequest.LATEST_VER
1053 if at_version and at_version != PullRequest.LATEST_VER
1053 else None)
1054 else None)
1054
1055
1055 c.unresolved_comments = CommentsModel() \
1056 c.unresolved_comments = CommentsModel() \
1056 .get_pull_request_unresolved_todos(pull_request, include_drafts=False)
1057 .get_pull_request_unresolved_todos(pull_request, include_drafts=False)
1057 c.resolved_comments = CommentsModel() \
1058 c.resolved_comments = CommentsModel() \
1058 .get_pull_request_resolved_todos(pull_request, include_drafts=False)
1059 .get_pull_request_resolved_todos(pull_request, include_drafts=False)
1059
1060
1060 all_comments = c.unresolved_comments + c.resolved_comments
1061 all_comments = c.unresolved_comments + c.resolved_comments
1061 existing_ids = self.get_comment_ids(self.request.POST)
1062 existing_ids = self.get_comment_ids(self.request.POST)
1062 return _render('comments_table', all_comments, len(c.unresolved_comments),
1063 return _render('comments_table', all_comments, len(c.unresolved_comments),
1063 todo_comments=True, existing_ids=existing_ids)
1064 todo_comments=True, existing_ids=existing_ids)
1064
1065
1065 @LoginRequired()
1066 @LoginRequired()
1066 @NotAnonymous()
1067 @NotAnonymous()
1067 @HasRepoPermissionAnyDecorator(
1068 @HasRepoPermissionAnyDecorator(
1068 'repository.read', 'repository.write', 'repository.admin')
1069 'repository.read', 'repository.write', 'repository.admin')
1069 def pullrequest_drafts(self):
1070 def pullrequest_drafts(self):
1070 self.load_default_context()
1071 self.load_default_context()
1071
1072
1072 pull_request = PullRequest.get_or_404(
1073 pull_request = PullRequest.get_or_404(
1073 self.request.matchdict['pull_request_id'])
1074 self.request.matchdict['pull_request_id'])
1074 pull_request_id = pull_request.pull_request_id
1075 pull_request_id = pull_request.pull_request_id
1075 version = self.request.GET.get('version')
1076 version = self.request.GET.get('version')
1076
1077
1077 _render = self.request.get_partial_renderer(
1078 _render = self.request.get_partial_renderer(
1078 'rhodecode:templates/base/sidebar.mako')
1079 'rhodecode:templates/base/sidebar.mako')
1079 c = _render.get_call_context()
1080 c = _render.get_call_context()
1080
1081
1081 (pull_request_latest,
1082 (pull_request_latest,
1082 pull_request_at_ver,
1083 pull_request_at_ver,
1083 pull_request_display_obj,
1084 pull_request_display_obj,
1084 at_version) = PullRequestModel().get_pr_version(
1085 at_version) = PullRequestModel().get_pr_version(
1085 pull_request_id, version=version)
1086 pull_request_id, version=version)
1086 versions = pull_request_display_obj.versions()
1087 versions = pull_request_display_obj.versions()
1087 latest_ver = PullRequest.get_pr_display_object(pull_request_latest, pull_request_latest)
1088 latest_ver = PullRequest.get_pr_display_object(pull_request_latest, pull_request_latest)
1088 c.versions = versions + [latest_ver]
1089 c.versions = versions + [latest_ver]
1089
1090
1090 c.at_version = at_version
1091 c.at_version = at_version
1091 c.at_version_num = (at_version
1092 c.at_version_num = (at_version
1092 if at_version and at_version != PullRequest.LATEST_VER
1093 if at_version and at_version != PullRequest.LATEST_VER
1093 else None)
1094 else None)
1094
1095
1095 c.draft_comments = CommentsModel() \
1096 c.draft_comments = CommentsModel() \
1096 .get_pull_request_drafts(self._rhodecode_db_user.user_id, pull_request)
1097 .get_pull_request_drafts(self._rhodecode_db_user.user_id, pull_request)
1097
1098
1098 all_comments = c.draft_comments
1099 all_comments = c.draft_comments
1099
1100
1100 existing_ids = self.get_comment_ids(self.request.POST)
1101 existing_ids = self.get_comment_ids(self.request.POST)
1101 return _render('comments_table', all_comments, len(all_comments),
1102 return _render('comments_table', all_comments, len(all_comments),
1102 existing_ids=existing_ids, draft_comments=True)
1103 existing_ids=existing_ids, draft_comments=True)
1103
1104
1104 @LoginRequired()
1105 @LoginRequired()
1105 @NotAnonymous()
1106 @NotAnonymous()
1106 @HasRepoPermissionAnyDecorator(
1107 @HasRepoPermissionAnyDecorator(
1107 'repository.read', 'repository.write', 'repository.admin')
1108 'repository.read', 'repository.write', 'repository.admin')
1108 @CSRFRequired()
1109 @CSRFRequired()
1109 def pull_request_create(self):
1110 def pull_request_create(self):
1110 _ = self.request.translate
1111 _ = self.request.translate
1111 self.assure_not_empty_repo()
1112 self.assure_not_empty_repo()
1112 self.load_default_context()
1113 self.load_default_context()
1113
1114
1114 controls = peppercorn.parse(self.request.POST.items())
1115 controls = peppercorn.parse(self.request.POST.items())
1115
1116
1116 try:
1117 try:
1117 form = PullRequestForm(
1118 form = PullRequestForm(
1118 self.request.translate, self.db_repo.repo_id)()
1119 self.request.translate, self.db_repo.repo_id)()
1119 _form = form.to_python(controls)
1120 _form = form.to_python(controls)
1120 except formencode.Invalid as errors:
1121 except formencode.Invalid as errors:
1121 if errors.error_dict.get('revisions'):
1122 if errors.error_dict.get('revisions'):
1122 msg = 'Revisions: %s' % errors.error_dict['revisions']
1123 msg = 'Revisions: %s' % errors.error_dict['revisions']
1123 elif errors.error_dict.get('pullrequest_title'):
1124 elif errors.error_dict.get('pullrequest_title'):
1124 msg = errors.error_dict.get('pullrequest_title')
1125 msg = errors.error_dict.get('pullrequest_title')
1125 else:
1126 else:
1126 msg = _('Error creating pull request: {}').format(errors)
1127 msg = _('Error creating pull request: {}').format(errors)
1127 log.exception(msg)
1128 log.exception(msg)
1128 h.flash(msg, 'error')
1129 h.flash(msg, 'error')
1129
1130
1130 # would rather just go back to form ...
1131 # would rather just go back to form ...
1131 raise HTTPFound(
1132 raise HTTPFound(
1132 h.route_path('pullrequest_new', repo_name=self.db_repo_name))
1133 h.route_path('pullrequest_new', repo_name=self.db_repo_name))
1133
1134
1134 source_repo = _form['source_repo']
1135 source_repo = _form['source_repo']
1135 source_ref = _form['source_ref']
1136 source_ref = _form['source_ref']
1136 target_repo = _form['target_repo']
1137 target_repo = _form['target_repo']
1137 target_ref = _form['target_ref']
1138 target_ref = _form['target_ref']
1138 commit_ids = _form['revisions'][::-1]
1139 commit_ids = _form['revisions'][::-1]
1139 common_ancestor_id = _form['common_ancestor']
1140 common_ancestor_id = _form['common_ancestor']
1140
1141
1141 # find the ancestor for this pr
1142 # find the ancestor for this pr
1142 source_db_repo = Repository.get_by_repo_name(_form['source_repo'])
1143 source_db_repo = Repository.get_by_repo_name(_form['source_repo'])
1143 target_db_repo = Repository.get_by_repo_name(_form['target_repo'])
1144 target_db_repo = Repository.get_by_repo_name(_form['target_repo'])
1144
1145
1145 if not (source_db_repo or target_db_repo):
1146 if not (source_db_repo or target_db_repo):
1146 h.flash(_('source_repo or target repo not found'), category='error')
1147 h.flash(_('source_repo or target repo not found'), category='error')
1147 raise HTTPFound(
1148 raise HTTPFound(
1148 h.route_path('pullrequest_new', repo_name=self.db_repo_name))
1149 h.route_path('pullrequest_new', repo_name=self.db_repo_name))
1149
1150
1150 # re-check permissions again here
1151 # re-check permissions again here
1151 # source_repo we must have read permissions
1152 # source_repo we must have read permissions
1152
1153
1153 source_perm = HasRepoPermissionAny(
1154 source_perm = HasRepoPermissionAny(
1154 'repository.read', 'repository.write', 'repository.admin')(
1155 'repository.read', 'repository.write', 'repository.admin')(
1155 source_db_repo.repo_name)
1156 source_db_repo.repo_name)
1156 if not source_perm:
1157 if not source_perm:
1157 msg = _('Not Enough permissions to source repo `{}`.'.format(
1158 msg = _('Not Enough permissions to source repo `{}`.'.format(
1158 source_db_repo.repo_name))
1159 source_db_repo.repo_name))
1159 h.flash(msg, category='error')
1160 h.flash(msg, category='error')
1160 # copy the args back to redirect
1161 # copy the args back to redirect
1161 org_query = self.request.GET.mixed()
1162 org_query = self.request.GET.mixed()
1162 raise HTTPFound(
1163 raise HTTPFound(
1163 h.route_path('pullrequest_new', repo_name=self.db_repo_name,
1164 h.route_path('pullrequest_new', repo_name=self.db_repo_name,
1164 _query=org_query))
1165 _query=org_query))
1165
1166
1166 # target repo we must have read permissions, and also later on
1167 # target repo we must have read permissions, and also later on
1167 # we want to check branch permissions here
1168 # we want to check branch permissions here
1168 target_perm = HasRepoPermissionAny(
1169 target_perm = HasRepoPermissionAny(
1169 'repository.read', 'repository.write', 'repository.admin')(
1170 'repository.read', 'repository.write', 'repository.admin')(
1170 target_db_repo.repo_name)
1171 target_db_repo.repo_name)
1171 if not target_perm:
1172 if not target_perm:
1172 msg = _('Not Enough permissions to target repo `{}`.'.format(
1173 msg = _('Not Enough permissions to target repo `{}`.'.format(
1173 target_db_repo.repo_name))
1174 target_db_repo.repo_name))
1174 h.flash(msg, category='error')
1175 h.flash(msg, category='error')
1175 # copy the args back to redirect
1176 # copy the args back to redirect
1176 org_query = self.request.GET.mixed()
1177 org_query = self.request.GET.mixed()
1177 raise HTTPFound(
1178 raise HTTPFound(
1178 h.route_path('pullrequest_new', repo_name=self.db_repo_name,
1179 h.route_path('pullrequest_new', repo_name=self.db_repo_name,
1179 _query=org_query))
1180 _query=org_query))
1180
1181
1181 source_scm = source_db_repo.scm_instance()
1182 source_scm = source_db_repo.scm_instance()
1182 target_scm = target_db_repo.scm_instance()
1183 target_scm = target_db_repo.scm_instance()
1183
1184
1184 source_ref_obj = unicode_to_reference(source_ref)
1185 source_ref_obj = unicode_to_reference(source_ref)
1185 target_ref_obj = unicode_to_reference(target_ref)
1186 target_ref_obj = unicode_to_reference(target_ref)
1186
1187
1187 source_commit = source_scm.get_commit(source_ref_obj.commit_id)
1188 source_commit = source_scm.get_commit(source_ref_obj.commit_id)
1188 target_commit = target_scm.get_commit(target_ref_obj.commit_id)
1189 target_commit = target_scm.get_commit(target_ref_obj.commit_id)
1189
1190
1190 ancestor = source_scm.get_common_ancestor(
1191 ancestor = source_scm.get_common_ancestor(
1191 source_commit.raw_id, target_commit.raw_id, target_scm)
1192 source_commit.raw_id, target_commit.raw_id, target_scm)
1192
1193
1193 # recalculate target ref based on ancestor
1194 # recalculate target ref based on ancestor
1194 target_ref = ':'.join((target_ref_obj.type, target_ref_obj.name, ancestor))
1195 target_ref = ':'.join((target_ref_obj.type, target_ref_obj.name, ancestor))
1195
1196
1196 get_default_reviewers_data, validate_default_reviewers, validate_observers = \
1197 get_default_reviewers_data, validate_default_reviewers, validate_observers = \
1197 PullRequestModel().get_reviewer_functions()
1198 PullRequestModel().get_reviewer_functions()
1198
1199
1199 # recalculate reviewers logic, to make sure we can validate this
1200 # recalculate reviewers logic, to make sure we can validate this
1200 reviewer_rules = get_default_reviewers_data(
1201 reviewer_rules = get_default_reviewers_data(
1201 self._rhodecode_db_user,
1202 self._rhodecode_db_user,
1202 source_db_repo,
1203 source_db_repo,
1203 source_ref_obj,
1204 source_ref_obj,
1204 target_db_repo,
1205 target_db_repo,
1205 target_ref_obj,
1206 target_ref_obj,
1206 include_diff_info=False)
1207 include_diff_info=False)
1207
1208
1208 reviewers = validate_default_reviewers(_form['review_members'], reviewer_rules)
1209 reviewers = validate_default_reviewers(_form['review_members'], reviewer_rules)
1209 observers = validate_observers(_form['observer_members'], reviewer_rules)
1210 observers = validate_observers(_form['observer_members'], reviewer_rules)
1210
1211
1211 pullrequest_title = _form['pullrequest_title']
1212 pullrequest_title = _form['pullrequest_title']
1212 title_source_ref = source_ref_obj.name
1213 title_source_ref = source_ref_obj.name
1213 if not pullrequest_title:
1214 if not pullrequest_title:
1214 pullrequest_title = PullRequestModel().generate_pullrequest_title(
1215 pullrequest_title = PullRequestModel().generate_pullrequest_title(
1215 source=source_repo,
1216 source=source_repo,
1216 source_ref=title_source_ref,
1217 source_ref=title_source_ref,
1217 target=target_repo
1218 target=target_repo
1218 )
1219 )
1219
1220
1220 description = _form['pullrequest_desc']
1221 description = _form['pullrequest_desc']
1221 description_renderer = _form['description_renderer']
1222 description_renderer = _form['description_renderer']
1222
1223
1223 try:
1224 try:
1224 pull_request = PullRequestModel().create(
1225 pull_request = PullRequestModel().create(
1225 created_by=self._rhodecode_user.user_id,
1226 created_by=self._rhodecode_user.user_id,
1226 source_repo=source_repo,
1227 source_repo=source_repo,
1227 source_ref=source_ref,
1228 source_ref=source_ref,
1228 target_repo=target_repo,
1229 target_repo=target_repo,
1229 target_ref=target_ref,
1230 target_ref=target_ref,
1230 revisions=commit_ids,
1231 revisions=commit_ids,
1231 common_ancestor_id=common_ancestor_id,
1232 common_ancestor_id=common_ancestor_id,
1232 reviewers=reviewers,
1233 reviewers=reviewers,
1233 observers=observers,
1234 observers=observers,
1234 title=pullrequest_title,
1235 title=pullrequest_title,
1235 description=description,
1236 description=description,
1236 description_renderer=description_renderer,
1237 description_renderer=description_renderer,
1237 reviewer_data=reviewer_rules,
1238 reviewer_data=reviewer_rules,
1238 auth_user=self._rhodecode_user
1239 auth_user=self._rhodecode_user
1239 )
1240 )
1240 Session().commit()
1241 Session().commit()
1241
1242
1242 h.flash(_('Successfully opened new pull request'),
1243 h.flash(_('Successfully opened new pull request'),
1243 category='success')
1244 category='success')
1244 except Exception:
1245 except Exception:
1245 msg = _('Error occurred during creation of this pull request.')
1246 msg = _('Error occurred during creation of this pull request.')
1246 log.exception(msg)
1247 log.exception(msg)
1247 h.flash(msg, category='error')
1248 h.flash(msg, category='error')
1248
1249
1249 # copy the args back to redirect
1250 # copy the args back to redirect
1250 org_query = self.request.GET.mixed()
1251 org_query = self.request.GET.mixed()
1251 raise HTTPFound(
1252 raise HTTPFound(
1252 h.route_path('pullrequest_new', repo_name=self.db_repo_name,
1253 h.route_path('pullrequest_new', repo_name=self.db_repo_name,
1253 _query=org_query))
1254 _query=org_query))
1254
1255
1255 raise HTTPFound(
1256 raise HTTPFound(
1256 h.route_path('pullrequest_show', repo_name=target_repo,
1257 h.route_path('pullrequest_show', repo_name=target_repo,
1257 pull_request_id=pull_request.pull_request_id))
1258 pull_request_id=pull_request.pull_request_id))
1258
1259
1259 @LoginRequired()
1260 @LoginRequired()
1260 @NotAnonymous()
1261 @NotAnonymous()
1261 @HasRepoPermissionAnyDecorator(
1262 @HasRepoPermissionAnyDecorator(
1262 'repository.read', 'repository.write', 'repository.admin')
1263 'repository.read', 'repository.write', 'repository.admin')
1263 @CSRFRequired()
1264 @CSRFRequired()
1264 def pull_request_update(self):
1265 def pull_request_update(self):
1265 pull_request = PullRequest.get_or_404(
1266 pull_request = PullRequest.get_or_404(
1266 self.request.matchdict['pull_request_id'])
1267 self.request.matchdict['pull_request_id'])
1267 _ = self.request.translate
1268 _ = self.request.translate
1268
1269
1269 c = self.load_default_context()
1270 c = self.load_default_context()
1270 redirect_url = None
1271 redirect_url = None
1271
1272
1272 if pull_request.is_closed():
1273 if pull_request.is_closed():
1273 log.debug('update: forbidden because pull request is closed')
1274 log.debug('update: forbidden because pull request is closed')
1274 msg = _(u'Cannot update closed pull requests.')
1275 msg = _(u'Cannot update closed pull requests.')
1275 h.flash(msg, category='error')
1276 h.flash(msg, category='error')
1276 return {'response': True,
1277 return {'response': True,
1277 'redirect_url': redirect_url}
1278 'redirect_url': redirect_url}
1278
1279
1279 is_state_changing = pull_request.is_state_changing()
1280 is_state_changing = pull_request.is_state_changing()
1280 c.pr_broadcast_channel = channelstream.pr_channel(pull_request)
1281 c.pr_broadcast_channel = channelstream.pr_channel(pull_request)
1281
1282
1282 # only owner or admin can update it
1283 # only owner or admin can update it
1283 allowed_to_update = PullRequestModel().check_user_update(
1284 allowed_to_update = PullRequestModel().check_user_update(
1284 pull_request, self._rhodecode_user)
1285 pull_request, self._rhodecode_user)
1285
1286
1286 if allowed_to_update:
1287 if allowed_to_update:
1287 controls = peppercorn.parse(self.request.POST.items())
1288 controls = peppercorn.parse(self.request.POST.items())
1288 force_refresh = str2bool(self.request.POST.get('force_refresh'))
1289 force_refresh = str2bool(self.request.POST.get('force_refresh'))
1289
1290
1290 if 'review_members' in controls:
1291 if 'review_members' in controls:
1291 self._update_reviewers(
1292 self._update_reviewers(
1292 c,
1293 c,
1293 pull_request, controls['review_members'],
1294 pull_request, controls['review_members'],
1294 pull_request.reviewer_data,
1295 pull_request.reviewer_data,
1295 PullRequestReviewers.ROLE_REVIEWER)
1296 PullRequestReviewers.ROLE_REVIEWER)
1296 elif 'observer_members' in controls:
1297 elif 'observer_members' in controls:
1297 self._update_reviewers(
1298 self._update_reviewers(
1298 c,
1299 c,
1299 pull_request, controls['observer_members'],
1300 pull_request, controls['observer_members'],
1300 pull_request.reviewer_data,
1301 pull_request.reviewer_data,
1301 PullRequestReviewers.ROLE_OBSERVER)
1302 PullRequestReviewers.ROLE_OBSERVER)
1302 elif str2bool(self.request.POST.get('update_commits', 'false')):
1303 elif str2bool(self.request.POST.get('update_commits', 'false')):
1303 if is_state_changing:
1304 if is_state_changing:
1304 log.debug('commits update: forbidden because pull request is in state %s',
1305 log.debug('commits update: forbidden because pull request is in state %s',
1305 pull_request.pull_request_state)
1306 pull_request.pull_request_state)
1306 msg = _(u'Cannot update pull requests commits in state other than `{}`. '
1307 msg = _(u'Cannot update pull requests commits in state other than `{}`. '
1307 u'Current state is: `{}`').format(
1308 u'Current state is: `{}`').format(
1308 PullRequest.STATE_CREATED, pull_request.pull_request_state)
1309 PullRequest.STATE_CREATED, pull_request.pull_request_state)
1309 h.flash(msg, category='error')
1310 h.flash(msg, category='error')
1310 return {'response': True,
1311 return {'response': True,
1311 'redirect_url': redirect_url}
1312 'redirect_url': redirect_url}
1312
1313
1313 self._update_commits(c, pull_request)
1314 self._update_commits(c, pull_request)
1314 if force_refresh:
1315 if force_refresh:
1315 redirect_url = h.route_path(
1316 redirect_url = h.route_path(
1316 'pullrequest_show', repo_name=self.db_repo_name,
1317 'pullrequest_show', repo_name=self.db_repo_name,
1317 pull_request_id=pull_request.pull_request_id,
1318 pull_request_id=pull_request.pull_request_id,
1318 _query={"force_refresh": 1})
1319 _query={"force_refresh": 1})
1319 elif str2bool(self.request.POST.get('edit_pull_request', 'false')):
1320 elif str2bool(self.request.POST.get('edit_pull_request', 'false')):
1320 self._edit_pull_request(pull_request)
1321 self._edit_pull_request(pull_request)
1321 else:
1322 else:
1322 log.error('Unhandled update data.')
1323 log.error('Unhandled update data.')
1323 raise HTTPBadRequest()
1324 raise HTTPBadRequest()
1324
1325
1325 return {'response': True,
1326 return {'response': True,
1326 'redirect_url': redirect_url}
1327 'redirect_url': redirect_url}
1327 raise HTTPForbidden()
1328 raise HTTPForbidden()
1328
1329
1329 def _edit_pull_request(self, pull_request):
1330 def _edit_pull_request(self, pull_request):
1330 """
1331 """
1331 Edit title and description
1332 Edit title and description
1332 """
1333 """
1333 _ = self.request.translate
1334 _ = self.request.translate
1334
1335
1335 try:
1336 try:
1336 PullRequestModel().edit(
1337 PullRequestModel().edit(
1337 pull_request,
1338 pull_request,
1338 self.request.POST.get('title'),
1339 self.request.POST.get('title'),
1339 self.request.POST.get('description'),
1340 self.request.POST.get('description'),
1340 self.request.POST.get('description_renderer'),
1341 self.request.POST.get('description_renderer'),
1341 self._rhodecode_user)
1342 self._rhodecode_user)
1342 except ValueError:
1343 except ValueError:
1343 msg = _(u'Cannot update closed pull requests.')
1344 msg = _(u'Cannot update closed pull requests.')
1344 h.flash(msg, category='error')
1345 h.flash(msg, category='error')
1345 return
1346 return
1346 else:
1347 else:
1347 Session().commit()
1348 Session().commit()
1348
1349
1349 msg = _(u'Pull request title & description updated.')
1350 msg = _(u'Pull request title & description updated.')
1350 h.flash(msg, category='success')
1351 h.flash(msg, category='success')
1351 return
1352 return
1352
1353
1353 def _update_commits(self, c, pull_request):
1354 def _update_commits(self, c, pull_request):
1354 _ = self.request.translate
1355 _ = self.request.translate
1355
1356
1356 @retry(exception=Exception, n_tries=3)
1357 @retry(exception=Exception, n_tries=3)
1357 def commits_update():
1358 def commits_update():
1358 return PullRequestModel().update_commits(
1359 return PullRequestModel().update_commits(
1359 pull_request, self._rhodecode_db_user)
1360 pull_request, self._rhodecode_db_user)
1360
1361
1361 with pull_request.set_state(PullRequest.STATE_UPDATING):
1362 with pull_request.set_state(PullRequest.STATE_UPDATING):
1362 resp = commits_update() # retry x3
1363 resp = commits_update() # retry x3
1363
1364
1364 if resp.executed:
1365 if resp.executed:
1365
1366
1366 if resp.target_changed and resp.source_changed:
1367 if resp.target_changed and resp.source_changed:
1367 changed = 'target and source repositories'
1368 changed = 'target and source repositories'
1368 elif resp.target_changed and not resp.source_changed:
1369 elif resp.target_changed and not resp.source_changed:
1369 changed = 'target repository'
1370 changed = 'target repository'
1370 elif not resp.target_changed and resp.source_changed:
1371 elif not resp.target_changed and resp.source_changed:
1371 changed = 'source repository'
1372 changed = 'source repository'
1372 else:
1373 else:
1373 changed = 'nothing'
1374 changed = 'nothing'
1374
1375
1375 msg = _(u'Pull request updated to "{source_commit_id}" with '
1376 msg = _(u'Pull request updated to "{source_commit_id}" with '
1376 u'{count_added} added, {count_removed} removed commits. '
1377 u'{count_added} added, {count_removed} removed commits. '
1377 u'Source of changes: {change_source}.')
1378 u'Source of changes: {change_source}.')
1378 msg = msg.format(
1379 msg = msg.format(
1379 source_commit_id=pull_request.source_ref_parts.commit_id,
1380 source_commit_id=pull_request.source_ref_parts.commit_id,
1380 count_added=len(resp.changes.added),
1381 count_added=len(resp.changes.added),
1381 count_removed=len(resp.changes.removed),
1382 count_removed=len(resp.changes.removed),
1382 change_source=changed)
1383 change_source=changed)
1383 h.flash(msg, category='success')
1384 h.flash(msg, category='success')
1384 channelstream.pr_update_channelstream_push(
1385 channelstream.pr_update_channelstream_push(
1385 self.request, c.pr_broadcast_channel, self._rhodecode_user, msg)
1386 self.request, c.pr_broadcast_channel, self._rhodecode_user, msg)
1386 else:
1387 else:
1387 msg = PullRequestModel.UPDATE_STATUS_MESSAGES[resp.reason]
1388 msg = PullRequestModel.UPDATE_STATUS_MESSAGES[resp.reason]
1388 warning_reasons = [
1389 warning_reasons = [
1389 UpdateFailureReason.NO_CHANGE,
1390 UpdateFailureReason.NO_CHANGE,
1390 UpdateFailureReason.WRONG_REF_TYPE,
1391 UpdateFailureReason.WRONG_REF_TYPE,
1391 ]
1392 ]
1392 category = 'warning' if resp.reason in warning_reasons else 'error'
1393 category = 'warning' if resp.reason in warning_reasons else 'error'
1393 h.flash(msg, category=category)
1394 h.flash(msg, category=category)
1394
1395
1395 def _update_reviewers(self, c, pull_request, review_members, reviewer_rules, role):
1396 def _update_reviewers(self, c, pull_request, review_members, reviewer_rules, role):
1396 _ = self.request.translate
1397 _ = self.request.translate
1397
1398
1398 get_default_reviewers_data, validate_default_reviewers, validate_observers = \
1399 get_default_reviewers_data, validate_default_reviewers, validate_observers = \
1399 PullRequestModel().get_reviewer_functions()
1400 PullRequestModel().get_reviewer_functions()
1400
1401
1401 if role == PullRequestReviewers.ROLE_REVIEWER:
1402 if role == PullRequestReviewers.ROLE_REVIEWER:
1402 try:
1403 try:
1403 reviewers = validate_default_reviewers(review_members, reviewer_rules)
1404 reviewers = validate_default_reviewers(review_members, reviewer_rules)
1404 except ValueError as e:
1405 except ValueError as e:
1405 log.error('Reviewers Validation: {}'.format(e))
1406 log.error('Reviewers Validation: {}'.format(e))
1406 h.flash(e, category='error')
1407 h.flash(e, category='error')
1407 return
1408 return
1408
1409
1409 old_calculated_status = pull_request.calculated_review_status()
1410 old_calculated_status = pull_request.calculated_review_status()
1410 PullRequestModel().update_reviewers(
1411 PullRequestModel().update_reviewers(
1411 pull_request, reviewers, self._rhodecode_db_user)
1412 pull_request, reviewers, self._rhodecode_db_user)
1412
1413
1413 Session().commit()
1414 Session().commit()
1414
1415
1415 msg = _('Pull request reviewers updated.')
1416 msg = _('Pull request reviewers updated.')
1416 h.flash(msg, category='success')
1417 h.flash(msg, category='success')
1417 channelstream.pr_update_channelstream_push(
1418 channelstream.pr_update_channelstream_push(
1418 self.request, c.pr_broadcast_channel, self._rhodecode_user, msg)
1419 self.request, c.pr_broadcast_channel, self._rhodecode_user, msg)
1419
1420
1420 # trigger status changed if change in reviewers changes the status
1421 # trigger status changed if change in reviewers changes the status
1421 calculated_status = pull_request.calculated_review_status()
1422 calculated_status = pull_request.calculated_review_status()
1422 if old_calculated_status != calculated_status:
1423 if old_calculated_status != calculated_status:
1423 PullRequestModel().trigger_pull_request_hook(
1424 PullRequestModel().trigger_pull_request_hook(
1424 pull_request, self._rhodecode_user, 'review_status_change',
1425 pull_request, self._rhodecode_user, 'review_status_change',
1425 data={'status': calculated_status})
1426 data={'status': calculated_status})
1426
1427
1427 elif role == PullRequestReviewers.ROLE_OBSERVER:
1428 elif role == PullRequestReviewers.ROLE_OBSERVER:
1428 try:
1429 try:
1429 observers = validate_observers(review_members, reviewer_rules)
1430 observers = validate_observers(review_members, reviewer_rules)
1430 except ValueError as e:
1431 except ValueError as e:
1431 log.error('Observers Validation: {}'.format(e))
1432 log.error('Observers Validation: {}'.format(e))
1432 h.flash(e, category='error')
1433 h.flash(e, category='error')
1433 return
1434 return
1434
1435
1435 PullRequestModel().update_observers(
1436 PullRequestModel().update_observers(
1436 pull_request, observers, self._rhodecode_db_user)
1437 pull_request, observers, self._rhodecode_db_user)
1437
1438
1438 Session().commit()
1439 Session().commit()
1439 msg = _('Pull request observers updated.')
1440 msg = _('Pull request observers updated.')
1440 h.flash(msg, category='success')
1441 h.flash(msg, category='success')
1441 channelstream.pr_update_channelstream_push(
1442 channelstream.pr_update_channelstream_push(
1442 self.request, c.pr_broadcast_channel, self._rhodecode_user, msg)
1443 self.request, c.pr_broadcast_channel, self._rhodecode_user, msg)
1443
1444
1444 @LoginRequired()
1445 @LoginRequired()
1445 @NotAnonymous()
1446 @NotAnonymous()
1446 @HasRepoPermissionAnyDecorator(
1447 @HasRepoPermissionAnyDecorator(
1447 'repository.read', 'repository.write', 'repository.admin')
1448 'repository.read', 'repository.write', 'repository.admin')
1448 @CSRFRequired()
1449 @CSRFRequired()
1449 def pull_request_merge(self):
1450 def pull_request_merge(self):
1450 """
1451 """
1451 Merge will perform a server-side merge of the specified
1452 Merge will perform a server-side merge of the specified
1452 pull request, if the pull request is approved and mergeable.
1453 pull request, if the pull request is approved and mergeable.
1453 After successful merging, the pull request is automatically
1454 After successful merging, the pull request is automatically
1454 closed, with a relevant comment.
1455 closed, with a relevant comment.
1455 """
1456 """
1456 pull_request = PullRequest.get_or_404(
1457 pull_request = PullRequest.get_or_404(
1457 self.request.matchdict['pull_request_id'])
1458 self.request.matchdict['pull_request_id'])
1458 _ = self.request.translate
1459 _ = self.request.translate
1459
1460
1460 if pull_request.is_state_changing():
1461 if pull_request.is_state_changing():
1461 log.debug('show: forbidden because pull request is in state %s',
1462 log.debug('show: forbidden because pull request is in state %s',
1462 pull_request.pull_request_state)
1463 pull_request.pull_request_state)
1463 msg = _(u'Cannot merge pull requests in state other than `{}`. '
1464 msg = _(u'Cannot merge pull requests in state other than `{}`. '
1464 u'Current state is: `{}`').format(PullRequest.STATE_CREATED,
1465 u'Current state is: `{}`').format(PullRequest.STATE_CREATED,
1465 pull_request.pull_request_state)
1466 pull_request.pull_request_state)
1466 h.flash(msg, category='error')
1467 h.flash(msg, category='error')
1467 raise HTTPFound(
1468 raise HTTPFound(
1468 h.route_path('pullrequest_show',
1469 h.route_path('pullrequest_show',
1469 repo_name=pull_request.target_repo.repo_name,
1470 repo_name=pull_request.target_repo.repo_name,
1470 pull_request_id=pull_request.pull_request_id))
1471 pull_request_id=pull_request.pull_request_id))
1471
1472
1472 self.load_default_context()
1473 self.load_default_context()
1473
1474
1474 with pull_request.set_state(PullRequest.STATE_UPDATING):
1475 with pull_request.set_state(PullRequest.STATE_UPDATING):
1475 check = MergeCheck.validate(
1476 check = MergeCheck.validate(
1476 pull_request, auth_user=self._rhodecode_user,
1477 pull_request, auth_user=self._rhodecode_user,
1477 translator=self.request.translate)
1478 translator=self.request.translate)
1478 merge_possible = not check.failed
1479 merge_possible = not check.failed
1479
1480
1480 for err_type, error_msg in check.errors:
1481 for err_type, error_msg in check.errors:
1481 h.flash(error_msg, category=err_type)
1482 h.flash(error_msg, category=err_type)
1482
1483
1483 if merge_possible:
1484 if merge_possible:
1484 log.debug("Pre-conditions checked, trying to merge.")
1485 log.debug("Pre-conditions checked, trying to merge.")
1485 extras = vcs_operation_context(
1486 extras = vcs_operation_context(
1486 self.request.environ, repo_name=pull_request.target_repo.repo_name,
1487 self.request.environ, repo_name=pull_request.target_repo.repo_name,
1487 username=self._rhodecode_db_user.username, action='push',
1488 username=self._rhodecode_db_user.username, action='push',
1488 scm=pull_request.target_repo.repo_type)
1489 scm=pull_request.target_repo.repo_type)
1489 with pull_request.set_state(PullRequest.STATE_UPDATING):
1490 with pull_request.set_state(PullRequest.STATE_UPDATING):
1490 self._merge_pull_request(
1491 self._merge_pull_request(
1491 pull_request, self._rhodecode_db_user, extras)
1492 pull_request, self._rhodecode_db_user, extras)
1492 else:
1493 else:
1493 log.debug("Pre-conditions failed, NOT merging.")
1494 log.debug("Pre-conditions failed, NOT merging.")
1494
1495
1495 raise HTTPFound(
1496 raise HTTPFound(
1496 h.route_path('pullrequest_show',
1497 h.route_path('pullrequest_show',
1497 repo_name=pull_request.target_repo.repo_name,
1498 repo_name=pull_request.target_repo.repo_name,
1498 pull_request_id=pull_request.pull_request_id))
1499 pull_request_id=pull_request.pull_request_id))
1499
1500
1500 def _merge_pull_request(self, pull_request, user, extras):
1501 def _merge_pull_request(self, pull_request, user, extras):
1501 _ = self.request.translate
1502 _ = self.request.translate
1502 merge_resp = PullRequestModel().merge_repo(pull_request, user, extras=extras)
1503 merge_resp = PullRequestModel().merge_repo(pull_request, user, extras=extras)
1503
1504
1504 if merge_resp.executed:
1505 if merge_resp.executed:
1505 log.debug("The merge was successful, closing the pull request.")
1506 log.debug("The merge was successful, closing the pull request.")
1506 PullRequestModel().close_pull_request(
1507 PullRequestModel().close_pull_request(
1507 pull_request.pull_request_id, user)
1508 pull_request.pull_request_id, user)
1508 Session().commit()
1509 Session().commit()
1509 msg = _('Pull request was successfully merged and closed.')
1510 msg = _('Pull request was successfully merged and closed.')
1510 h.flash(msg, category='success')
1511 h.flash(msg, category='success')
1511 else:
1512 else:
1512 log.debug(
1513 log.debug(
1513 "The merge was not successful. Merge response: %s", merge_resp)
1514 "The merge was not successful. Merge response: %s", merge_resp)
1514 msg = merge_resp.merge_status_message
1515 msg = merge_resp.merge_status_message
1515 h.flash(msg, category='error')
1516 h.flash(msg, category='error')
1516
1517
1517 @LoginRequired()
1518 @LoginRequired()
1518 @NotAnonymous()
1519 @NotAnonymous()
1519 @HasRepoPermissionAnyDecorator(
1520 @HasRepoPermissionAnyDecorator(
1520 'repository.read', 'repository.write', 'repository.admin')
1521 'repository.read', 'repository.write', 'repository.admin')
1521 @CSRFRequired()
1522 @CSRFRequired()
1522 def pull_request_delete(self):
1523 def pull_request_delete(self):
1523 _ = self.request.translate
1524 _ = self.request.translate
1524
1525
1525 pull_request = PullRequest.get_or_404(
1526 pull_request = PullRequest.get_or_404(
1526 self.request.matchdict['pull_request_id'])
1527 self.request.matchdict['pull_request_id'])
1527 self.load_default_context()
1528 self.load_default_context()
1528
1529
1529 pr_closed = pull_request.is_closed()
1530 pr_closed = pull_request.is_closed()
1530 allowed_to_delete = PullRequestModel().check_user_delete(
1531 allowed_to_delete = PullRequestModel().check_user_delete(
1531 pull_request, self._rhodecode_user) and not pr_closed
1532 pull_request, self._rhodecode_user) and not pr_closed
1532
1533
1533 # only owner can delete it !
1534 # only owner can delete it !
1534 if allowed_to_delete:
1535 if allowed_to_delete:
1535 PullRequestModel().delete(pull_request, self._rhodecode_user)
1536 PullRequestModel().delete(pull_request, self._rhodecode_user)
1536 Session().commit()
1537 Session().commit()
1537 h.flash(_('Successfully deleted pull request'),
1538 h.flash(_('Successfully deleted pull request'),
1538 category='success')
1539 category='success')
1539 raise HTTPFound(h.route_path('pullrequest_show_all',
1540 raise HTTPFound(h.route_path('pullrequest_show_all',
1540 repo_name=self.db_repo_name))
1541 repo_name=self.db_repo_name))
1541
1542
1542 log.warning('user %s tried to delete pull request without access',
1543 log.warning('user %s tried to delete pull request without access',
1543 self._rhodecode_user)
1544 self._rhodecode_user)
1544 raise HTTPNotFound()
1545 raise HTTPNotFound()
1545
1546
1546 def _pull_request_comments_create(self, pull_request, comments):
1547 def _pull_request_comments_create(self, pull_request, comments):
1547 _ = self.request.translate
1548 _ = self.request.translate
1548 data = {}
1549 data = {}
1549 if not comments:
1550 if not comments:
1550 return
1551 return
1551 pull_request_id = pull_request.pull_request_id
1552 pull_request_id = pull_request.pull_request_id
1552
1553
1553 all_drafts = len([x for x in comments if str2bool(x['is_draft'])]) == len(comments)
1554 all_drafts = len([x for x in comments if str2bool(x['is_draft'])]) == len(comments)
1554
1555
1555 for entry in comments:
1556 for entry in comments:
1556 c = self.load_default_context()
1557 c = self.load_default_context()
1557 comment_type = entry['comment_type']
1558 comment_type = entry['comment_type']
1558 text = entry['text']
1559 text = entry['text']
1559 status = entry['status']
1560 status = entry['status']
1560 is_draft = str2bool(entry['is_draft'])
1561 is_draft = str2bool(entry['is_draft'])
1561 resolves_comment_id = entry['resolves_comment_id']
1562 resolves_comment_id = entry['resolves_comment_id']
1562 close_pull_request = entry['close_pull_request']
1563 close_pull_request = entry['close_pull_request']
1563 f_path = entry['f_path']
1564 f_path = entry['f_path']
1564 line_no = entry['line']
1565 line_no = entry['line']
1565 target_elem_id = 'file-{}'.format(h.safeid(h.safe_unicode(f_path)))
1566 target_elem_id = 'file-{}'.format(h.safeid(h.safe_unicode(f_path)))
1566
1567
1567 # the logic here should work like following, if we submit close
1568 # the logic here should work like following, if we submit close
1568 # pr comment, use `close_pull_request_with_comment` function
1569 # pr comment, use `close_pull_request_with_comment` function
1569 # else handle regular comment logic
1570 # else handle regular comment logic
1570
1571
1571 if close_pull_request:
1572 if close_pull_request:
1572 # only owner or admin or person with write permissions
1573 # only owner or admin or person with write permissions
1573 allowed_to_close = PullRequestModel().check_user_update(
1574 allowed_to_close = PullRequestModel().check_user_update(
1574 pull_request, self._rhodecode_user)
1575 pull_request, self._rhodecode_user)
1575 if not allowed_to_close:
1576 if not allowed_to_close:
1576 log.debug('comment: forbidden because not allowed to close '
1577 log.debug('comment: forbidden because not allowed to close '
1577 'pull request %s', pull_request_id)
1578 'pull request %s', pull_request_id)
1578 raise HTTPForbidden()
1579 raise HTTPForbidden()
1579
1580
1580 # This also triggers `review_status_change`
1581 # This also triggers `review_status_change`
1581 comment, status = PullRequestModel().close_pull_request_with_comment(
1582 comment, status = PullRequestModel().close_pull_request_with_comment(
1582 pull_request, self._rhodecode_user, self.db_repo, message=text,
1583 pull_request, self._rhodecode_user, self.db_repo, message=text,
1583 auth_user=self._rhodecode_user)
1584 auth_user=self._rhodecode_user)
1584 Session().flush()
1585 Session().flush()
1585 is_inline = comment.is_inline
1586 is_inline = comment.is_inline
1586
1587
1587 PullRequestModel().trigger_pull_request_hook(
1588 PullRequestModel().trigger_pull_request_hook(
1588 pull_request, self._rhodecode_user, 'comment',
1589 pull_request, self._rhodecode_user, 'comment',
1589 data={'comment': comment})
1590 data={'comment': comment})
1590
1591
1591 else:
1592 else:
1592 # regular comment case, could be inline, or one with status.
1593 # regular comment case, could be inline, or one with status.
1593 # for that one we check also permissions
1594 # for that one we check also permissions
1594 # Additionally ENSURE if somehow draft is sent we're then unable to change status
1595 # Additionally ENSURE if somehow draft is sent we're then unable to change status
1595 allowed_to_change_status = PullRequestModel().check_user_change_status(
1596 allowed_to_change_status = PullRequestModel().check_user_change_status(
1596 pull_request, self._rhodecode_user) and not is_draft
1597 pull_request, self._rhodecode_user) and not is_draft
1597
1598
1598 if status and allowed_to_change_status:
1599 if status and allowed_to_change_status:
1599 message = (_('Status change %(transition_icon)s %(status)s')
1600 message = (_('Status change %(transition_icon)s %(status)s')
1600 % {'transition_icon': '>',
1601 % {'transition_icon': '>',
1601 'status': ChangesetStatus.get_status_lbl(status)})
1602 'status': ChangesetStatus.get_status_lbl(status)})
1602 text = text or message
1603 text = text or message
1603
1604
1604 comment = CommentsModel().create(
1605 comment = CommentsModel().create(
1605 text=text,
1606 text=text,
1606 repo=self.db_repo.repo_id,
1607 repo=self.db_repo.repo_id,
1607 user=self._rhodecode_user.user_id,
1608 user=self._rhodecode_user.user_id,
1608 pull_request=pull_request,
1609 pull_request=pull_request,
1609 f_path=f_path,
1610 f_path=f_path,
1610 line_no=line_no,
1611 line_no=line_no,
1611 status_change=(ChangesetStatus.get_status_lbl(status)
1612 status_change=(ChangesetStatus.get_status_lbl(status)
1612 if status and allowed_to_change_status else None),
1613 if status and allowed_to_change_status else None),
1613 status_change_type=(status
1614 status_change_type=(status
1614 if status and allowed_to_change_status else None),
1615 if status and allowed_to_change_status else None),
1615 comment_type=comment_type,
1616 comment_type=comment_type,
1616 is_draft=is_draft,
1617 is_draft=is_draft,
1617 resolves_comment_id=resolves_comment_id,
1618 resolves_comment_id=resolves_comment_id,
1618 auth_user=self._rhodecode_user,
1619 auth_user=self._rhodecode_user,
1619 send_email=not is_draft, # skip notification for draft comments
1620 send_email=not is_draft, # skip notification for draft comments
1620 )
1621 )
1621 is_inline = comment.is_inline
1622 is_inline = comment.is_inline
1622
1623
1623 if allowed_to_change_status:
1624 if allowed_to_change_status:
1624 # calculate old status before we change it
1625 # calculate old status before we change it
1625 old_calculated_status = pull_request.calculated_review_status()
1626 old_calculated_status = pull_request.calculated_review_status()
1626
1627
1627 # get status if set !
1628 # get status if set !
1628 if status:
1629 if status:
1629 ChangesetStatusModel().set_status(
1630 ChangesetStatusModel().set_status(
1630 self.db_repo.repo_id,
1631 self.db_repo.repo_id,
1631 status,
1632 status,
1632 self._rhodecode_user.user_id,
1633 self._rhodecode_user.user_id,
1633 comment,
1634 comment,
1634 pull_request=pull_request
1635 pull_request=pull_request
1635 )
1636 )
1636
1637
1637 Session().flush()
1638 Session().flush()
1638 # this is somehow required to get access to some relationship
1639 # this is somehow required to get access to some relationship
1639 # loaded on comment
1640 # loaded on comment
1640 Session().refresh(comment)
1641 Session().refresh(comment)
1641
1642
1642 # skip notifications for drafts
1643 # skip notifications for drafts
1643 if not is_draft:
1644 if not is_draft:
1644 PullRequestModel().trigger_pull_request_hook(
1645 PullRequestModel().trigger_pull_request_hook(
1645 pull_request, self._rhodecode_user, 'comment',
1646 pull_request, self._rhodecode_user, 'comment',
1646 data={'comment': comment})
1647 data={'comment': comment})
1647
1648
1648 # we now calculate the status of pull request, and based on that
1649 # we now calculate the status of pull request, and based on that
1649 # calculation we set the commits status
1650 # calculation we set the commits status
1650 calculated_status = pull_request.calculated_review_status()
1651 calculated_status = pull_request.calculated_review_status()
1651 if old_calculated_status != calculated_status:
1652 if old_calculated_status != calculated_status:
1652 PullRequestModel().trigger_pull_request_hook(
1653 PullRequestModel().trigger_pull_request_hook(
1653 pull_request, self._rhodecode_user, 'review_status_change',
1654 pull_request, self._rhodecode_user, 'review_status_change',
1654 data={'status': calculated_status})
1655 data={'status': calculated_status})
1655
1656
1656 comment_id = comment.comment_id
1657 comment_id = comment.comment_id
1657 data[comment_id] = {
1658 data[comment_id] = {
1658 'target_id': target_elem_id
1659 'target_id': target_elem_id
1659 }
1660 }
1660 Session().flush()
1661 Session().flush()
1661
1662
1662 c.co = comment
1663 c.co = comment
1663 c.at_version_num = None
1664 c.at_version_num = None
1664 c.is_new = True
1665 c.is_new = True
1665 rendered_comment = render(
1666 rendered_comment = render(
1666 'rhodecode:templates/changeset/changeset_comment_block.mako',
1667 'rhodecode:templates/changeset/changeset_comment_block.mako',
1667 self._get_template_context(c), self.request)
1668 self._get_template_context(c), self.request)
1668
1669
1669 data[comment_id].update(comment.get_dict())
1670 data[comment_id].update(comment.get_dict())
1670 data[comment_id].update({'rendered_text': rendered_comment})
1671 data[comment_id].update({'rendered_text': rendered_comment})
1671
1672
1672 Session().commit()
1673 Session().commit()
1673
1674
1674 # skip channelstream for draft comments
1675 # skip channelstream for draft comments
1675 if not all_drafts:
1676 if not all_drafts:
1676 comment_broadcast_channel = channelstream.comment_channel(
1677 comment_broadcast_channel = channelstream.comment_channel(
1677 self.db_repo_name, pull_request_obj=pull_request)
1678 self.db_repo_name, pull_request_obj=pull_request)
1678
1679
1679 comment_data = data
1680 comment_data = data
1680 posted_comment_type = 'inline' if is_inline else 'general'
1681 posted_comment_type = 'inline' if is_inline else 'general'
1681 if len(data) == 1:
1682 if len(data) == 1:
1682 msg = _('posted {} new {} comment').format(len(data), posted_comment_type)
1683 msg = _('posted {} new {} comment').format(len(data), posted_comment_type)
1683 else:
1684 else:
1684 msg = _('posted {} new {} comments').format(len(data), posted_comment_type)
1685 msg = _('posted {} new {} comments').format(len(data), posted_comment_type)
1685
1686
1686 channelstream.comment_channelstream_push(
1687 channelstream.comment_channelstream_push(
1687 self.request, comment_broadcast_channel, self._rhodecode_user, msg,
1688 self.request, comment_broadcast_channel, self._rhodecode_user, msg,
1688 comment_data=comment_data)
1689 comment_data=comment_data)
1689
1690
1690 return data
1691 return data
1691
1692
1692 @LoginRequired()
1693 @LoginRequired()
1693 @NotAnonymous()
1694 @NotAnonymous()
1694 @HasRepoPermissionAnyDecorator(
1695 @HasRepoPermissionAnyDecorator(
1695 'repository.read', 'repository.write', 'repository.admin')
1696 'repository.read', 'repository.write', 'repository.admin')
1696 @CSRFRequired()
1697 @CSRFRequired()
1697 def pull_request_comment_create(self):
1698 def pull_request_comment_create(self):
1698 _ = self.request.translate
1699 _ = self.request.translate
1699
1700
1700 pull_request = PullRequest.get_or_404(self.request.matchdict['pull_request_id'])
1701 pull_request = PullRequest.get_or_404(self.request.matchdict['pull_request_id'])
1701
1702
1702 if pull_request.is_closed():
1703 if pull_request.is_closed():
1703 log.debug('comment: forbidden because pull request is closed')
1704 log.debug('comment: forbidden because pull request is closed')
1704 raise HTTPForbidden()
1705 raise HTTPForbidden()
1705
1706
1706 allowed_to_comment = PullRequestModel().check_user_comment(
1707 allowed_to_comment = PullRequestModel().check_user_comment(
1707 pull_request, self._rhodecode_user)
1708 pull_request, self._rhodecode_user)
1708 if not allowed_to_comment:
1709 if not allowed_to_comment:
1709 log.debug('comment: forbidden because pull request is from forbidden repo')
1710 log.debug('comment: forbidden because pull request is from forbidden repo')
1710 raise HTTPForbidden()
1711 raise HTTPForbidden()
1711
1712
1712 comment_data = {
1713 comment_data = {
1713 'comment_type': self.request.POST.get('comment_type'),
1714 'comment_type': self.request.POST.get('comment_type'),
1714 'text': self.request.POST.get('text'),
1715 'text': self.request.POST.get('text'),
1715 'status': self.request.POST.get('changeset_status', None),
1716 'status': self.request.POST.get('changeset_status', None),
1716 'is_draft': self.request.POST.get('draft'),
1717 'is_draft': self.request.POST.get('draft'),
1717 'resolves_comment_id': self.request.POST.get('resolves_comment_id', None),
1718 'resolves_comment_id': self.request.POST.get('resolves_comment_id', None),
1718 'close_pull_request': self.request.POST.get('close_pull_request'),
1719 'close_pull_request': self.request.POST.get('close_pull_request'),
1719 'f_path': self.request.POST.get('f_path'),
1720 'f_path': self.request.POST.get('f_path'),
1720 'line': self.request.POST.get('line'),
1721 'line': self.request.POST.get('line'),
1721 }
1722 }
1722 data = self._pull_request_comments_create(pull_request, [comment_data])
1723 data = self._pull_request_comments_create(pull_request, [comment_data])
1723
1724
1724 return data
1725 return data
1725
1726
1726 @LoginRequired()
1727 @LoginRequired()
1727 @NotAnonymous()
1728 @NotAnonymous()
1728 @HasRepoPermissionAnyDecorator(
1729 @HasRepoPermissionAnyDecorator(
1729 'repository.read', 'repository.write', 'repository.admin')
1730 'repository.read', 'repository.write', 'repository.admin')
1730 @CSRFRequired()
1731 @CSRFRequired()
1731 def pull_request_comment_delete(self):
1732 def pull_request_comment_delete(self):
1732 pull_request = PullRequest.get_or_404(
1733 pull_request = PullRequest.get_or_404(
1733 self.request.matchdict['pull_request_id'])
1734 self.request.matchdict['pull_request_id'])
1734
1735
1735 comment = ChangesetComment.get_or_404(
1736 comment = ChangesetComment.get_or_404(
1736 self.request.matchdict['comment_id'])
1737 self.request.matchdict['comment_id'])
1737 comment_id = comment.comment_id
1738 comment_id = comment.comment_id
1738
1739
1739 if comment.immutable:
1740 if comment.immutable:
1740 # don't allow deleting comments that are immutable
1741 # don't allow deleting comments that are immutable
1741 raise HTTPForbidden()
1742 raise HTTPForbidden()
1742
1743
1743 if pull_request.is_closed():
1744 if pull_request.is_closed():
1744 log.debug('comment: forbidden because pull request is closed')
1745 log.debug('comment: forbidden because pull request is closed')
1745 raise HTTPForbidden()
1746 raise HTTPForbidden()
1746
1747
1747 if not comment:
1748 if not comment:
1748 log.debug('Comment with id:%s not found, skipping', comment_id)
1749 log.debug('Comment with id:%s not found, skipping', comment_id)
1749 # comment already deleted in another call probably
1750 # comment already deleted in another call probably
1750 return True
1751 return True
1751
1752
1752 if comment.pull_request.is_closed():
1753 if comment.pull_request.is_closed():
1753 # don't allow deleting comments on closed pull request
1754 # don't allow deleting comments on closed pull request
1754 raise HTTPForbidden()
1755 raise HTTPForbidden()
1755
1756
1756 is_repo_admin = h.HasRepoPermissionAny('repository.admin')(self.db_repo_name)
1757 is_repo_admin = h.HasRepoPermissionAny('repository.admin')(self.db_repo_name)
1757 super_admin = h.HasPermissionAny('hg.admin')()
1758 super_admin = h.HasPermissionAny('hg.admin')()
1758 comment_owner = comment.author.user_id == self._rhodecode_user.user_id
1759 comment_owner = comment.author.user_id == self._rhodecode_user.user_id
1759 is_repo_comment = comment.repo.repo_name == self.db_repo_name
1760 is_repo_comment = comment.repo.repo_name == self.db_repo_name
1760 comment_repo_admin = is_repo_admin and is_repo_comment
1761 comment_repo_admin = is_repo_admin and is_repo_comment
1761
1762
1762 if comment.draft and not comment_owner:
1763 if comment.draft and not comment_owner:
1763 # We never allow to delete draft comments for other than owners
1764 # We never allow to delete draft comments for other than owners
1764 raise HTTPNotFound()
1765 raise HTTPNotFound()
1765
1766
1766 if super_admin or comment_owner or comment_repo_admin:
1767 if super_admin or comment_owner or comment_repo_admin:
1767 old_calculated_status = comment.pull_request.calculated_review_status()
1768 old_calculated_status = comment.pull_request.calculated_review_status()
1768 CommentsModel().delete(comment=comment, auth_user=self._rhodecode_user)
1769 CommentsModel().delete(comment=comment, auth_user=self._rhodecode_user)
1769 Session().commit()
1770 Session().commit()
1770 calculated_status = comment.pull_request.calculated_review_status()
1771 calculated_status = comment.pull_request.calculated_review_status()
1771 if old_calculated_status != calculated_status:
1772 if old_calculated_status != calculated_status:
1772 PullRequestModel().trigger_pull_request_hook(
1773 PullRequestModel().trigger_pull_request_hook(
1773 comment.pull_request, self._rhodecode_user, 'review_status_change',
1774 comment.pull_request, self._rhodecode_user, 'review_status_change',
1774 data={'status': calculated_status})
1775 data={'status': calculated_status})
1775 return True
1776 return True
1776 else:
1777 else:
1777 log.warning('No permissions for user %s to delete comment_id: %s',
1778 log.warning('No permissions for user %s to delete comment_id: %s',
1778 self._rhodecode_db_user, comment_id)
1779 self._rhodecode_db_user, comment_id)
1779 raise HTTPNotFound()
1780 raise HTTPNotFound()
1780
1781
1781 @LoginRequired()
1782 @LoginRequired()
1782 @NotAnonymous()
1783 @NotAnonymous()
1783 @HasRepoPermissionAnyDecorator(
1784 @HasRepoPermissionAnyDecorator(
1784 'repository.read', 'repository.write', 'repository.admin')
1785 'repository.read', 'repository.write', 'repository.admin')
1785 @CSRFRequired()
1786 @CSRFRequired()
1786 def pull_request_comment_edit(self):
1787 def pull_request_comment_edit(self):
1787 self.load_default_context()
1788 self.load_default_context()
1788
1789
1789 pull_request = PullRequest.get_or_404(
1790 pull_request = PullRequest.get_or_404(
1790 self.request.matchdict['pull_request_id']
1791 self.request.matchdict['pull_request_id']
1791 )
1792 )
1792 comment = ChangesetComment.get_or_404(
1793 comment = ChangesetComment.get_or_404(
1793 self.request.matchdict['comment_id']
1794 self.request.matchdict['comment_id']
1794 )
1795 )
1795 comment_id = comment.comment_id
1796 comment_id = comment.comment_id
1796
1797
1797 if comment.immutable:
1798 if comment.immutable:
1798 # don't allow deleting comments that are immutable
1799 # don't allow deleting comments that are immutable
1799 raise HTTPForbidden()
1800 raise HTTPForbidden()
1800
1801
1801 if pull_request.is_closed():
1802 if pull_request.is_closed():
1802 log.debug('comment: forbidden because pull request is closed')
1803 log.debug('comment: forbidden because pull request is closed')
1803 raise HTTPForbidden()
1804 raise HTTPForbidden()
1804
1805
1805 if comment.pull_request.is_closed():
1806 if comment.pull_request.is_closed():
1806 # don't allow deleting comments on closed pull request
1807 # don't allow deleting comments on closed pull request
1807 raise HTTPForbidden()
1808 raise HTTPForbidden()
1808
1809
1809 is_repo_admin = h.HasRepoPermissionAny('repository.admin')(self.db_repo_name)
1810 is_repo_admin = h.HasRepoPermissionAny('repository.admin')(self.db_repo_name)
1810 super_admin = h.HasPermissionAny('hg.admin')()
1811 super_admin = h.HasPermissionAny('hg.admin')()
1811 comment_owner = comment.author.user_id == self._rhodecode_user.user_id
1812 comment_owner = comment.author.user_id == self._rhodecode_user.user_id
1812 is_repo_comment = comment.repo.repo_name == self.db_repo_name
1813 is_repo_comment = comment.repo.repo_name == self.db_repo_name
1813 comment_repo_admin = is_repo_admin and is_repo_comment
1814 comment_repo_admin = is_repo_admin and is_repo_comment
1814
1815
1815 if super_admin or comment_owner or comment_repo_admin:
1816 if super_admin or comment_owner or comment_repo_admin:
1816 text = self.request.POST.get('text')
1817 text = self.request.POST.get('text')
1817 version = self.request.POST.get('version')
1818 version = self.request.POST.get('version')
1818 if text == comment.text:
1819 if text == comment.text:
1819 log.warning(
1820 log.warning(
1820 'Comment(PR): '
1821 'Comment(PR): '
1821 'Trying to create new version '
1822 'Trying to create new version '
1822 'with the same comment body {}'.format(
1823 'with the same comment body {}'.format(
1823 comment_id,
1824 comment_id,
1824 )
1825 )
1825 )
1826 )
1826 raise HTTPNotFound()
1827 raise HTTPNotFound()
1827
1828
1828 if version.isdigit():
1829 if version.isdigit():
1829 version = int(version)
1830 version = int(version)
1830 else:
1831 else:
1831 log.warning(
1832 log.warning(
1832 'Comment(PR): Wrong version type {} {} '
1833 'Comment(PR): Wrong version type {} {} '
1833 'for comment {}'.format(
1834 'for comment {}'.format(
1834 version,
1835 version,
1835 type(version),
1836 type(version),
1836 comment_id,
1837 comment_id,
1837 )
1838 )
1838 )
1839 )
1839 raise HTTPNotFound()
1840 raise HTTPNotFound()
1840
1841
1841 try:
1842 try:
1842 comment_history = CommentsModel().edit(
1843 comment_history = CommentsModel().edit(
1843 comment_id=comment_id,
1844 comment_id=comment_id,
1844 text=text,
1845 text=text,
1845 auth_user=self._rhodecode_user,
1846 auth_user=self._rhodecode_user,
1846 version=version,
1847 version=version,
1847 )
1848 )
1848 except CommentVersionMismatch:
1849 except CommentVersionMismatch:
1849 raise HTTPConflict()
1850 raise HTTPConflict()
1850
1851
1851 if not comment_history:
1852 if not comment_history:
1852 raise HTTPNotFound()
1853 raise HTTPNotFound()
1853
1854
1854 Session().commit()
1855 Session().commit()
1855 if not comment.draft:
1856 if not comment.draft:
1856 PullRequestModel().trigger_pull_request_hook(
1857 PullRequestModel().trigger_pull_request_hook(
1857 pull_request, self._rhodecode_user, 'comment_edit',
1858 pull_request, self._rhodecode_user, 'comment_edit',
1858 data={'comment': comment})
1859 data={'comment': comment})
1859
1860
1860 return {
1861 return {
1861 'comment_history_id': comment_history.comment_history_id,
1862 'comment_history_id': comment_history.comment_history_id,
1862 'comment_id': comment.comment_id,
1863 'comment_id': comment.comment_id,
1863 'comment_version': comment_history.version,
1864 'comment_version': comment_history.version,
1864 'comment_author_username': comment_history.author.username,
1865 'comment_author_username': comment_history.author.username,
1865 'comment_author_gravatar': h.gravatar_url(comment_history.author.email, 16),
1866 'comment_author_gravatar': h.gravatar_url(comment_history.author.email, 16),
1866 'comment_created_on': h.age_component(comment_history.created_on,
1867 'comment_created_on': h.age_component(comment_history.created_on,
1867 time_is_local=True),
1868 time_is_local=True),
1868 }
1869 }
1869 else:
1870 else:
1870 log.warning('No permissions for user %s to edit comment_id: %s',
1871 log.warning('No permissions for user %s to edit comment_id: %s',
1871 self._rhodecode_db_user, comment_id)
1872 self._rhodecode_db_user, comment_id)
1872 raise HTTPNotFound()
1873 raise HTTPNotFound()
@@ -1,1060 +1,1061 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 <%namespace name="sidebar" file="/base/sidebar.mako"/>
4 <%namespace name="sidebar" file="/base/sidebar.mako"/>
5
5
6
6
7 <%def name="title()">
7 <%def name="title()">
8 ${_('{} Pull Request !{}').format(c.repo_name, c.pull_request.pull_request_id)}
8 ${_('{} Pull Request !{}').format(c.repo_name, c.pull_request.pull_request_id)}
9 %if c.rhodecode_name:
9 %if c.rhodecode_name:
10 &middot; ${h.branding(c.rhodecode_name)}
10 &middot; ${h.branding(c.rhodecode_name)}
11 %endif
11 %endif
12 </%def>
12 </%def>
13
13
14 <%def name="breadcrumbs_links()">
14 <%def name="breadcrumbs_links()">
15
15
16 </%def>
16 </%def>
17
17
18 <%def name="menu_bar_nav()">
18 <%def name="menu_bar_nav()">
19 ${self.menu_items(active='repositories')}
19 ${self.menu_items(active='repositories')}
20 </%def>
20 </%def>
21
21
22 <%def name="menu_bar_subnav()">
22 <%def name="menu_bar_subnav()">
23 ${self.repo_menu(active='showpullrequest')}
23 ${self.repo_menu(active='showpullrequest')}
24 </%def>
24 </%def>
25
25
26
26
27 <%def name="main()">
27 <%def name="main()">
28 ## Container to gather extracted Tickets
28 ## Container to gather extracted Tickets
29 <%
29 <%
30 c.referenced_commit_issues = h.IssuesRegistry()
30 c.referenced_commit_issues = h.IssuesRegistry()
31 c.referenced_desc_issues = h.IssuesRegistry()
31 c.referenced_desc_issues = h.IssuesRegistry()
32 %>
32 %>
33
33
34 <script type="text/javascript">
34 <script type="text/javascript">
35 templateContext.pull_request_data.pull_request_id = ${c.pull_request.pull_request_id};
35 templateContext.pull_request_data.pull_request_id = ${c.pull_request.pull_request_id};
36 templateContext.pull_request_data.pull_request_version = '${request.GET.get('version', '')}';
36 templateContext.pull_request_data.pull_request_version = '${request.GET.get('version', '')}';
37 </script>
37 </script>
38
38
39 <div class="box">
39 <div class="box">
40
40
41 <div class="box pr-summary">
41 <div class="box pr-summary">
42
42
43 <div class="summary-details block-left">
43 <div class="summary-details block-left">
44 <div id="pr-title">
44 <div id="pr-title">
45 % if c.pull_request.is_closed():
45 % if c.pull_request.is_closed():
46 <span class="pr-title-closed-tag tag">${_('Closed')}</span>
46 <span class="pr-title-closed-tag tag">${_('Closed')}</span>
47 % endif
47 % endif
48 <input class="pr-title-input large disabled" disabled="disabled" name="pullrequest_title" type="text" value="${c.pull_request.title}">
48 <input class="pr-title-input large disabled" disabled="disabled" name="pullrequest_title" type="text" value="${c.pull_request.title}">
49 </div>
49 </div>
50 <div id="pr-title-edit" class="input" style="display: none;">
50 <div id="pr-title-edit" class="input" style="display: none;">
51 <input class="pr-title-input large" id="pr-title-input" name="pullrequest_title" type="text" value="${c.pull_request.title}">
51 <input class="pr-title-input large" id="pr-title-input" name="pullrequest_title" type="text" value="${c.pull_request.title}">
52 </div>
52 </div>
53
53
54 <% summary = lambda n:{False:'summary-short'}.get(n) %>
54 <% summary = lambda n:{False:'summary-short'}.get(n) %>
55 <div class="pr-details-title">
55 <div class="pr-details-title">
56 <div class="pull-left">
56 <div class="pull-left">
57 <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>
57 <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>
58 ${_('Created on')}
58 ${_('Created on')}
59 <span class="tooltip" title="${_('Last updated on')} ${h.format_date(c.pull_request.updated_on)}">${h.format_date(c.pull_request.created_on)},</span>
59 <span class="tooltip" title="${_('Last updated on')} ${h.format_date(c.pull_request.updated_on)}">${h.format_date(c.pull_request.created_on)},</span>
60 <span class="pr-details-title-author-pref">${_('by')}</span>
60 <span class="pr-details-title-author-pref">${_('by')}</span>
61 </div>
61 </div>
62
62
63 <div class="pull-left">
63 <div class="pull-left">
64 ${self.gravatar_with_user(c.pull_request.author.email, 16, tooltip=True)}
64 ${self.gravatar_with_user(c.pull_request.author.email, 16, tooltip=True)}
65 </div>
65 </div>
66
66
67 %if c.allowed_to_update:
67 %if c.allowed_to_update:
68 <div class="pull-right">
68 <div class="pull-right">
69 <div id="edit_pull_request" class="action_button pr-save" style="display: none;">${_('Update title & description')}</div>
69 <div id="edit_pull_request" class="action_button pr-save" style="display: none;">${_('Update title & description')}</div>
70 <div id="delete_pullrequest" class="action_button pr-save ${('' if c.allowed_to_delete else 'disabled' )}" style="display: none;">
70 <div id="delete_pullrequest" class="action_button pr-save ${('' if c.allowed_to_delete else 'disabled' )}" style="display: none;">
71 % if c.allowed_to_delete:
71 % if c.allowed_to_delete:
72 ${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)}
72 ${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)}
73 <input class="btn btn-link btn-danger no-margin" id="remove_${c.pull_request.pull_request_id}" name="remove_${c.pull_request.pull_request_id}"
73 <input class="btn btn-link btn-danger no-margin" id="remove_${c.pull_request.pull_request_id}" name="remove_${c.pull_request.pull_request_id}"
74 onclick="submitConfirm(event, this, _gettext('Confirm to delete this pull request'), _gettext('Delete'), '${'!{}'.format(c.pull_request.pull_request_id)}')"
74 onclick="submitConfirm(event, this, _gettext('Confirm to delete this pull request'), _gettext('Delete'), '${'!{}'.format(c.pull_request.pull_request_id)}')"
75 type="submit" value="${_('Delete pull request')}">
75 type="submit" value="${_('Delete pull request')}">
76 ${h.end_form()}
76 ${h.end_form()}
77 % else:
77 % else:
78 <span class="tooltip" title="${_('Not allowed to delete this pull request')}">${_('Delete pull request')}</span>
78 <span class="tooltip" title="${_('Not allowed to delete this pull request')}">${_('Delete pull request')}</span>
79 % endif
79 % endif
80 </div>
80 </div>
81 <div id="open_edit_pullrequest" class="action_button">${_('Edit')}</div>
81 <div id="open_edit_pullrequest" class="action_button">${_('Edit')}</div>
82 <div id="close_edit_pullrequest" class="action_button" style="display: none;">${_('Cancel')}</div>
82 <div id="close_edit_pullrequest" class="action_button" style="display: none;">${_('Cancel')}</div>
83 </div>
83 </div>
84
84
85 %endif
85 %endif
86 </div>
86 </div>
87
87
88 <div id="pr-desc" class="input" title="${_('Rendered using {} renderer').format(c.renderer)}">
88 <div id="pr-desc" class="input" title="${_('Rendered using {} renderer').format(c.renderer)}">
89 ${h.render(c.pull_request.description, renderer=c.renderer, repo_name=c.repo_name, issues_container_callback=c.referenced_desc_issues())}
89 ${h.render(c.pull_request.description, renderer=c.renderer, repo_name=c.repo_name, issues_container_callback=c.referenced_desc_issues())}
90 </div>
90 </div>
91
91
92 <div id="pr-desc-edit" class="input textarea" style="display: none;">
92 <div id="pr-desc-edit" class="input textarea" style="display: none;">
93 <input id="pr-renderer-input" type="hidden" name="description_renderer" value="${c.visual.default_renderer}">
93 <input id="pr-renderer-input" type="hidden" name="description_renderer" value="${c.visual.default_renderer}">
94 ${dt.markup_form('pr-description-input', form_text=c.pull_request.description)}
94 ${dt.markup_form('pr-description-input', form_text=c.pull_request.description)}
95 </div>
95 </div>
96
96
97 <div id="summary" class="fields pr-details-content">
97 <div id="summary" class="fields pr-details-content">
98
98
99 ## source
99 ## source
100 <div class="field">
100 <div class="field">
101 <div class="label-pr-detail">
101 <div class="label-pr-detail">
102 <label>${_('Commit flow')}:</label>
102 <label>${_('Commit flow')}:</label>
103 </div>
103 </div>
104 <div class="input">
104 <div class="input">
105 <div class="pr-commit-flow">
105 <div class="pr-commit-flow">
106 ## Source
106 ## Source
107 %if c.pull_request.source_ref_parts.type == 'branch':
107 %if c.pull_request.source_ref_parts.type == 'branch':
108 <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>
108 <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>
109 %else:
109 %else:
110 <code class="pr-source-info">${'{}:{}'.format(c.pull_request.source_ref_parts.type, c.pull_request.source_ref_parts.name)}</code>
110 <code class="pr-source-info">${'{}:{}'.format(c.pull_request.source_ref_parts.type, c.pull_request.source_ref_parts.name)}</code>
111 %endif
111 %endif
112 ${_('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>
112 ${_('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>
113 &rarr;
113 &rarr;
114 ## Target
114 ## Target
115 %if c.pull_request.target_ref_parts.type == 'branch':
115 %if c.pull_request.target_ref_parts.type == 'branch':
116 <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>
116 <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>
117 %else:
117 %else:
118 <code class="pr-target-info">${'{}:{}'.format(c.pull_request.target_ref_parts.type, c.pull_request.target_ref_parts.name)}</code>
118 <code class="pr-target-info">${'{}:{}'.format(c.pull_request.target_ref_parts.type, c.pull_request.target_ref_parts.name)}</code>
119 %endif
119 %endif
120
120
121 ${_('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>
121 ${_('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>
122
122
123 <a class="source-details-action" href="#expand-source-details" onclick="return 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>'>
123 <a class="source-details-action" href="#expand-source-details" onclick="return 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>'>
124 <i class="icon-angle-down">more details</i>
124 <i class="icon-angle-down">more details</i>
125 </a>
125 </a>
126
126
127 </div>
127 </div>
128
128
129 <div class="source-details" style="display: none">
129 <div class="source-details" style="display: none">
130
130
131 <ul>
131 <ul>
132
132
133 ## common ancestor
133 ## common ancestor
134 <li>
134 <li>
135 ${_('Common ancestor')}:
135 ${_('Common ancestor')}:
136 % if c.ancestor_commit:
136 % if c.ancestor_commit:
137 <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>
137 <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>
138 % else:
138 % else:
139 ${_('not available')}
139 ${_('not available')}
140 % endif
140 % endif
141 </li>
141 </li>
142
142
143 ## pull url
143 ## pull url
144 <li>
144 <li>
145 %if h.is_hg(c.pull_request.source_repo):
145 %if h.is_hg(c.pull_request.source_repo):
146 <% clone_url = u'hg pull -r {} {}'.format(h.short_id(c.source_ref), c.pull_request.source_repo.clone_url()) %>
146 <% clone_url = u'hg pull -r {} {}'.format(h.short_id(c.source_ref), c.pull_request.source_repo.clone_url()) %>
147 %elif h.is_git(c.pull_request.source_repo):
147 %elif h.is_git(c.pull_request.source_repo):
148 <% clone_url = u'git pull {} {}'.format(c.pull_request.source_repo.clone_url(), c.pull_request.source_ref_parts.name) %>
148 <% clone_url = u'git pull {} {}'.format(c.pull_request.source_repo.clone_url(), c.pull_request.source_ref_parts.name) %>
149 %endif
149 %endif
150
150
151 <span>${_('Pull changes from source')}</span>: <input type="text" class="input-monospace pr-pullinfo" value="${clone_url}" readonly="readonly">
151 <span>${_('Pull changes from source')}</span>: <input type="text" class="input-monospace pr-pullinfo" value="${clone_url}" readonly="readonly">
152 <i class="tooltip icon-clipboard clipboard-action pull-right pr-pullinfo-copy" data-clipboard-text="${clone_url}" title="${_('Copy the pull url')}"></i>
152 <i class="tooltip icon-clipboard clipboard-action pull-right pr-pullinfo-copy" data-clipboard-text="${clone_url}" title="${_('Copy the pull url')}"></i>
153 </li>
153 </li>
154
154
155 ## Shadow repo
155 ## Shadow repo
156 <li>
156 <li>
157 % if not c.pull_request.is_closed() and c.pull_request.shadow_merge_ref:
157 % if not c.pull_request.is_closed() and c.pull_request.shadow_merge_ref:
158 %if h.is_hg(c.pull_request.target_repo):
158 %if h.is_hg(c.pull_request.target_repo):
159 <% 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) %>
159 <% 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) %>
160 %elif h.is_git(c.pull_request.target_repo):
160 %elif h.is_git(c.pull_request.target_repo):
161 <% 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) %>
161 <% 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) %>
162 %endif
162 %endif
163
163
164 <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">
164 <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">
165 <i class="tooltip icon-clipboard clipboard-action pull-right pr-mergeinfo-copy" data-clipboard-text="${clone_url}" title="${_('Copy the clone url')}"></i>
165 <i class="tooltip icon-clipboard clipboard-action pull-right pr-mergeinfo-copy" data-clipboard-text="${clone_url}" title="${_('Copy the clone url')}"></i>
166
166
167 % else:
167 % else:
168 <div class="">
168 <div class="">
169 ${_('Shadow repository data not available')}.
169 ${_('Shadow repository data not available')}.
170 </div>
170 </div>
171 % endif
171 % endif
172 </li>
172 </li>
173
173
174 </ul>
174 </ul>
175
175
176 </div>
176 </div>
177
177
178 </div>
178 </div>
179
179
180 </div>
180 </div>
181
181
182 ## versions
182 ## versions
183 <div class="field">
183 <div class="field">
184 <div class="label-pr-detail">
184 <div class="label-pr-detail">
185 <label>${_('Versions')}:</label>
185 <label>${_('Versions')}:</label>
186 </div>
186 </div>
187
187
188 <% outdated_comm_count_ver = len(c.inline_versions[None]['outdated']) %>
188 <% outdated_comm_count_ver = len(c.inline_versions[None]['outdated']) %>
189 <% general_outdated_comm_count_ver = len(c.comment_versions[None]['outdated']) %>
189 <% general_outdated_comm_count_ver = len(c.comment_versions[None]['outdated']) %>
190
190
191 <div class="pr-versions">
191 <div class="pr-versions">
192 % if c.show_version_changes:
192 % if c.show_version_changes:
193 <% outdated_comm_count_ver = len(c.inline_versions[c.at_version_num]['outdated']) %>
193 <% outdated_comm_count_ver = len(c.inline_versions[c.at_version_num]['outdated']) %>
194 <% general_outdated_comm_count_ver = len(c.comment_versions[c.at_version_num]['outdated']) %>
194 <% general_outdated_comm_count_ver = len(c.comment_versions[c.at_version_num]['outdated']) %>
195 ${_ungettext('{} version available for this pull request, ', '{} versions available for this pull request, ', len(c.versions)).format(len(c.versions))}
195 ${_ungettext('{} version available for this pull request, ', '{} versions available for this pull request, ', len(c.versions)).format(len(c.versions))}
196 <a id="show-pr-versions" onclick="return versionController.toggleVersionView(this)" href="#show-pr-versions"
196 <a id="show-pr-versions" onclick="return versionController.toggleVersionView(this)" href="#show-pr-versions"
197 data-toggle-on="${_('show versions')}."
197 data-toggle-on="${_('show versions')}."
198 data-toggle-off="${_('hide versions')}.">
198 data-toggle-off="${_('hide versions')}.">
199 ${_('show versions')}.
199 ${_('show versions')}.
200 </a>
200 </a>
201 <table>
201 <table>
202 ## SHOW ALL VERSIONS OF PR
202 ## SHOW ALL VERSIONS OF PR
203 <% ver_pr = None %>
203 <% ver_pr = None %>
204
204
205 % for data in reversed(list(enumerate(c.versions, 1))):
205 % for data in reversed(list(enumerate(c.versions, 1))):
206 <% ver_pos = data[0] %>
206 <% ver_pos = data[0] %>
207 <% ver = data[1] %>
207 <% ver = data[1] %>
208 <% ver_pr = ver.pull_request_version_id %>
208 <% ver_pr = ver.pull_request_version_id %>
209 <% display_row = '' if c.at_version and (c.at_version_num == ver_pr or c.from_version_num == ver_pr) else 'none' %>
209 <% display_row = '' if c.at_version and (c.at_version_num == ver_pr or c.from_version_num == ver_pr) else 'none' %>
210
210
211 <tr class="version-pr" style="display: ${display_row}">
211 <tr class="version-pr" style="display: ${display_row}">
212 <td>
212 <td>
213 <code>
213 <code>
214 <a href="${request.current_route_path(_query=dict(version=ver_pr or 'latest'))}">v${ver_pos}</a>
214 <a href="${request.current_route_path(_query=dict(version=ver_pr or 'latest'))}">v${ver_pos}</a>
215 </code>
215 </code>
216 </td>
216 </td>
217 <td>
217 <td>
218 <input ${('checked="checked"' if c.from_version_index == ver_pr else '')} class="compare-radio-button" type="radio" name="ver_source" value="${ver_pr or 'latest'}" data-ver-pos="${ver_pos}"/>
218 <input ${('checked="checked"' if c.from_version_index == ver_pr else '')} class="compare-radio-button" type="radio" name="ver_source" value="${ver_pr or 'latest'}" data-ver-pos="${ver_pos}"/>
219 <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}"/>
219 <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}"/>
220 </td>
220 </td>
221 <td>
221 <td>
222 <% review_status = c.review_versions[ver_pr].status if ver_pr in c.review_versions else 'not_reviewed' %>
222 <% review_status = c.review_versions[ver_pr].status if ver_pr in c.review_versions else 'not_reviewed' %>
223 <i class="tooltip icon-circle review-status-${review_status}" title="${_('Your review status at this version')}"></i>
223 <i class="tooltip icon-circle review-status-${review_status}" title="${_('Your review status at this version')}"></i>
224
224
225 </td>
225 </td>
226 <td>
226 <td>
227 % if c.at_version_num != ver_pr:
227 % if c.at_version_num != ver_pr:
228 <i class="tooltip icon-comment" title="${_('Comments from pull request version v{0}').format(ver_pos)}"></i>
228 <i class="tooltip icon-comment" title="${_('Comments from pull request version v{0}').format(ver_pos)}"></i>
229 <code>
229 <code>
230 General:${len(c.comment_versions[ver_pr]['at'])} / Inline:${len(c.inline_versions[ver_pr]['at'])}
230 General:${len(c.comment_versions[ver_pr]['at'])} / Inline:${len(c.inline_versions[ver_pr]['at'])}
231 </code>
231 </code>
232 % endif
232 % endif
233 </td>
233 </td>
234 <td>
234 <td>
235 ##<code>${ver.source_ref_parts.commit_id[:6]}</code>
235 ##<code>${ver.source_ref_parts.commit_id[:6]}</code>
236 </td>
236 </td>
237 <td>
237 <td>
238 <code>${h.age_component(ver.updated_on, time_is_local=True, tooltip=False)}</code>
238 <code>${h.age_component(ver.updated_on, time_is_local=True, tooltip=False)}</code>
239 </td>
239 </td>
240 </tr>
240 </tr>
241 % endfor
241 % endfor
242
242
243 <tr>
243 <tr>
244 <td colspan="6">
244 <td colspan="6">
245 <button id="show-version-diff" onclick="return versionController.showVersionDiff()" class="btn btn-sm" style="display: none"
245 <button id="show-version-diff" onclick="return versionController.showVersionDiff()" class="btn btn-sm" style="display: none"
246 data-label-text-locked="${_('select versions to show changes')}"
246 data-label-text-locked="${_('select versions to show changes')}"
247 data-label-text-diff="${_('show changes between versions')}"
247 data-label-text-diff="${_('show changes between versions')}"
248 data-label-text-show="${_('show pull request for this version')}"
248 data-label-text-show="${_('show pull request for this version')}"
249 >
249 >
250 ${_('select versions to show changes')}
250 ${_('select versions to show changes')}
251 </button>
251 </button>
252 </td>
252 </td>
253 </tr>
253 </tr>
254 </table>
254 </table>
255 % else:
255 % else:
256 <div>
256 <div>
257 ${_('Pull request versions not available')}.
257 ${_('Pull request versions not available')}.
258 </div>
258 </div>
259 % endif
259 % endif
260 </div>
260 </div>
261 </div>
261 </div>
262
262
263 </div>
263 </div>
264
264
265 </div>
265 </div>
266
266
267
267
268 </div>
268 </div>
269
269
270 </div>
270 </div>
271
271
272 <div class="box">
272 <div class="box">
273
273
274 % if c.state_progressing:
274 % if c.state_progressing:
275
275
276 <h2 style="text-align: center">
276 <h2 style="text-align: center">
277 ${_('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>
277 ${_('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><br/>
278 ${_('Consider refreshing the page to check if the status transition was finished')}.
278
279
279 % if c.is_super_admin:
280 % if c.is_super_admin or h.HasRepoPermissionAny('repository.admin')(c.repo_name):
280 <br/>
281 <br/>
281 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.
282 ${_('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.
282 % endif
283 % endif
283 </h2>
284 </h2>
284
285
285 % else:
286 % else:
286
287
287 ## Diffs rendered here
288 ## Diffs rendered here
288 <div class="table" >
289 <div class="table" >
289 <div id="changeset_compare_view_content">
290 <div id="changeset_compare_view_content">
290 ##CS
291 ##CS
291 % if c.missing_requirements:
292 % if c.missing_requirements:
292 <div class="box">
293 <div class="box">
293 <div class="alert alert-warning">
294 <div class="alert alert-warning">
294 <div>
295 <div>
295 <strong>${_('Missing requirements:')}</strong>
296 <strong>${_('Missing requirements:')}</strong>
296 ${_('These commits cannot be displayed, because this repository uses the Mercurial largefiles extension, which was not enabled.')}
297 ${_('These commits cannot be displayed, because this repository uses the Mercurial largefiles extension, which was not enabled.')}
297 </div>
298 </div>
298 </div>
299 </div>
299 </div>
300 </div>
300 % elif c.missing_commits:
301 % elif c.missing_commits:
301 <div class="box">
302 <div class="box">
302 <div class="alert alert-warning">
303 <div class="alert alert-warning">
303 <div>
304 <div>
304 <strong>${_('Missing commits')}:</strong>
305 <strong>${_('Missing commits')}:</strong>
305 ${_('This pull request cannot be displayed, because one or more commits no longer exist in the source repository.')}<br/>
306 ${_('This pull request cannot be displayed, because one or more commits no longer exist in the source repository.')}<br/>
306 ${_('Please update this pull request, push the commits back into the source repository, or consider closing this pull request.')}<br/>
307 ${_('Please update this pull request, push the commits back into the source repository, or consider closing this pull request.')}<br/>
307 ${_('Consider doing a `force update commits` in case you think this is an error.')}
308 ${_('Consider doing a `force update commits` in case you think this is an error.')}
308 </div>
309 </div>
309 </div>
310 </div>
310 </div>
311 </div>
311 % elif c.pr_merge_source_commit.changed and not c.pull_request.is_closed():
312 % elif c.pr_merge_source_commit.changed and not c.pull_request.is_closed():
312 <div class="box">
313 <div class="box">
313 <div class="alert alert-info">
314 <div class="alert alert-info">
314 <div>
315 <div>
315 <strong>${_('There are new changes for `{}:{}` in source repository, please consider updating this pull request.').format(c.pr_merge_source_commit.ref_spec.type, c.pr_merge_source_commit.ref_spec.name)}</strong>
316 <strong>${_('There are new changes for `{}:{}` in source repository, please consider updating this pull request.').format(c.pr_merge_source_commit.ref_spec.type, c.pr_merge_source_commit.ref_spec.name)}</strong>
316 </div>
317 </div>
317 </div>
318 </div>
318 </div>
319 </div>
319 % endif
320 % endif
320
321
321 <div class="compare_view_commits_title">
322 <div class="compare_view_commits_title">
322 % if not c.compare_mode:
323 % if not c.compare_mode:
323
324
324 % if c.at_version_index:
325 % if c.at_version_index:
325 <h4>
326 <h4>
326 ${_('Showing changes at v{}, commenting is disabled.').format(c.at_version_index)}
327 ${_('Showing changes at v{}, commenting is disabled.').format(c.at_version_index)}
327 </h4>
328 </h4>
328 % endif
329 % endif
329
330
330 <div class="pull-left">
331 <div class="pull-left">
331 <div class="btn-group">
332 <div class="btn-group">
332 <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)} >
333 <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)} >
333 % if c.collapse_all_commits:
334 % if c.collapse_all_commits:
334 <i class="icon-plus-squared-alt icon-no-margin"></i>
335 <i class="icon-plus-squared-alt icon-no-margin"></i>
335 ${_ungettext('Expand {} commit', 'Expand {} commits', len(c.commit_ranges)).format(len(c.commit_ranges))}
336 ${_ungettext('Expand {} commit', 'Expand {} commits', len(c.commit_ranges)).format(len(c.commit_ranges))}
336 % else:
337 % else:
337 <i class="icon-minus-squared-alt icon-no-margin"></i>
338 <i class="icon-minus-squared-alt icon-no-margin"></i>
338 ${_ungettext('Collapse {} commit', 'Collapse {} commits', len(c.commit_ranges)).format(len(c.commit_ranges))}
339 ${_ungettext('Collapse {} commit', 'Collapse {} commits', len(c.commit_ranges)).format(len(c.commit_ranges))}
339 % endif
340 % endif
340 </a>
341 </a>
341 </div>
342 </div>
342 </div>
343 </div>
343
344
344 <div class="pull-right">
345 <div class="pull-right">
345 % if c.allowed_to_update and not c.pull_request.is_closed():
346 % if c.allowed_to_update and not c.pull_request.is_closed():
346
347
347 <div class="btn-group btn-group-actions">
348 <div class="btn-group btn-group-actions">
348 <a id="update_commits" class="btn btn-primary no-margin" onclick="updateController.updateCommits(this); return false">
349 <a id="update_commits" class="btn btn-primary no-margin" onclick="updateController.updateCommits(this); return false">
349 ${_('Update commits')}
350 ${_('Update commits')}
350 </a>
351 </a>
351
352
352 <a id="update_commits_switcher" class="tooltip btn btn-primary btn-more-option" data-toggle="dropdown" aria-pressed="false" role="button" title="${_('more update options')}">
353 <a id="update_commits_switcher" class="tooltip btn btn-primary btn-more-option" data-toggle="dropdown" aria-pressed="false" role="button" title="${_('more update options')}">
353 <i class="icon-down"></i>
354 <i class="icon-down"></i>
354 </a>
355 </a>
355
356
356 <div class="btn-action-switcher-container right-align" id="update-commits-switcher">
357 <div class="btn-action-switcher-container right-align" id="update-commits-switcher">
357 <ul class="btn-action-switcher" role="menu" style="min-width: 300px;">
358 <ul class="btn-action-switcher" role="menu" style="min-width: 300px;">
358 <li>
359 <li>
359 <a href="#forceUpdate" onclick="updateController.forceUpdateCommits(this); return false">
360 <a href="#forceUpdate" onclick="updateController.forceUpdateCommits(this); return false">
360 ${_('Force update commits')}
361 ${_('Force update commits')}
361 </a>
362 </a>
362 <div class="action-help-block">
363 <div class="action-help-block">
363 ${_('Update commits and force refresh this pull request.')}
364 ${_('Update commits and force refresh this pull request.')}
364 </div>
365 </div>
365 </li>
366 </li>
366 </ul>
367 </ul>
367 </div>
368 </div>
368 </div>
369 </div>
369
370
370 % else:
371 % else:
371 <a class="tooltip btn disabled pull-right" disabled="disabled" title="${_('Update is disabled for current view')}">${_('Update commits')}</a>
372 <a class="tooltip btn disabled pull-right" disabled="disabled" title="${_('Update is disabled for current view')}">${_('Update commits')}</a>
372 % endif
373 % endif
373
374
374 </div>
375 </div>
375 % endif
376 % endif
376 </div>
377 </div>
377
378
378 % if not c.missing_commits:
379 % if not c.missing_commits:
379 ## COMPARE RANGE DIFF MODE
380 ## COMPARE RANGE DIFF MODE
380 % if c.compare_mode:
381 % if c.compare_mode:
381 % if c.at_version:
382 % if c.at_version:
382 <h4>
383 <h4>
383 ${_('Commits and changes between v{ver_from} and {ver_to} of this pull request, commenting is disabled').format(ver_from=c.from_version_index, ver_to=c.at_version_index if c.at_version_index else 'latest')}:
384 ${_('Commits and changes between v{ver_from} and {ver_to} of this pull request, commenting is disabled').format(ver_from=c.from_version_index, ver_to=c.at_version_index if c.at_version_index else 'latest')}:
384 </h4>
385 </h4>
385
386
386 <div class="subtitle-compare">
387 <div class="subtitle-compare">
387 ${_('commits added: {}, removed: {}').format(len(c.commit_changes_summary.added), len(c.commit_changes_summary.removed))}
388 ${_('commits added: {}, removed: {}').format(len(c.commit_changes_summary.added), len(c.commit_changes_summary.removed))}
388 </div>
389 </div>
389
390
390 <div class="container">
391 <div class="container">
391 <table class="rctable compare_view_commits">
392 <table class="rctable compare_view_commits">
392 <tr>
393 <tr>
393 <th></th>
394 <th></th>
394 <th>${_('Time')}</th>
395 <th>${_('Time')}</th>
395 <th>${_('Author')}</th>
396 <th>${_('Author')}</th>
396 <th>${_('Commit')}</th>
397 <th>${_('Commit')}</th>
397 <th></th>
398 <th></th>
398 <th>${_('Description')}</th>
399 <th>${_('Description')}</th>
399 </tr>
400 </tr>
400
401
401 % for c_type, commit in c.commit_changes:
402 % for c_type, commit in c.commit_changes:
402 % if c_type in ['a', 'r']:
403 % if c_type in ['a', 'r']:
403 <%
404 <%
404 if c_type == 'a':
405 if c_type == 'a':
405 cc_title = _('Commit added in displayed changes')
406 cc_title = _('Commit added in displayed changes')
406 elif c_type == 'r':
407 elif c_type == 'r':
407 cc_title = _('Commit removed in displayed changes')
408 cc_title = _('Commit removed in displayed changes')
408 else:
409 else:
409 cc_title = ''
410 cc_title = ''
410 %>
411 %>
411 <tr id="row-${commit.raw_id}" commit_id="${commit.raw_id}" class="compare_select">
412 <tr id="row-${commit.raw_id}" commit_id="${commit.raw_id}" class="compare_select">
412 <td>
413 <td>
413 <div class="commit-change-indicator color-${c_type}-border">
414 <div class="commit-change-indicator color-${c_type}-border">
414 <div class="commit-change-content color-${c_type} tooltip" title="${h.tooltip(cc_title)}">
415 <div class="commit-change-content color-${c_type} tooltip" title="${h.tooltip(cc_title)}">
415 ${c_type.upper()}
416 ${c_type.upper()}
416 </div>
417 </div>
417 </div>
418 </div>
418 </td>
419 </td>
419 <td class="td-time">
420 <td class="td-time">
420 ${h.age_component(commit.date)}
421 ${h.age_component(commit.date)}
421 </td>
422 </td>
422 <td class="td-user">
423 <td class="td-user">
423 ${base.gravatar_with_user(commit.author, 16, tooltip=True)}
424 ${base.gravatar_with_user(commit.author, 16, tooltip=True)}
424 </td>
425 </td>
425 <td class="td-hash">
426 <td class="td-hash">
426 <code>
427 <code>
427 <a href="${h.route_path('repo_commit', repo_name=c.target_repo.repo_name, commit_id=commit.raw_id)}">
428 <a href="${h.route_path('repo_commit', repo_name=c.target_repo.repo_name, commit_id=commit.raw_id)}">
428 r${commit.idx}:${h.short_id(commit.raw_id)}
429 r${commit.idx}:${h.short_id(commit.raw_id)}
429 </a>
430 </a>
430 ${h.hidden('revisions', commit.raw_id)}
431 ${h.hidden('revisions', commit.raw_id)}
431 </code>
432 </code>
432 </td>
433 </td>
433 <td class="td-message expand_commit" data-commit-id="${commit.raw_id}" title="${_( 'Expand commit message')}" onclick="commitsController.expandCommit(this); return false">
434 <td class="td-message expand_commit" data-commit-id="${commit.raw_id}" title="${_( 'Expand commit message')}" onclick="commitsController.expandCommit(this); return false">
434 <i class="icon-expand-linked"></i>
435 <i class="icon-expand-linked"></i>
435 </td>
436 </td>
436 <td class="mid td-description">
437 <td class="mid td-description">
437 <div class="log-container truncate-wrap">
438 <div class="log-container truncate-wrap">
438 <div class="message truncate" id="c-${commit.raw_id}" data-message-raw="${commit.message}">${h.urlify_commit_message(commit.message, c.repo_name, issues_container_callback=c.referenced_commit_issues(commit.serialize()))}</div>
439 <div class="message truncate" id="c-${commit.raw_id}" data-message-raw="${commit.message}">${h.urlify_commit_message(commit.message, c.repo_name, issues_container_callback=c.referenced_commit_issues(commit.serialize()))}</div>
439 </div>
440 </div>
440 </td>
441 </td>
441 </tr>
442 </tr>
442 % endif
443 % endif
443 % endfor
444 % endfor
444 </table>
445 </table>
445 </div>
446 </div>
446
447
447 % endif
448 % endif
448
449
449 ## Regular DIFF
450 ## Regular DIFF
450 % else:
451 % else:
451 <%include file="/compare/compare_commits.mako" />
452 <%include file="/compare/compare_commits.mako" />
452 % endif
453 % endif
453
454
454 <div class="cs_files">
455 <div class="cs_files">
455 <%namespace name="cbdiffs" file="/codeblocks/diffs.mako"/>
456 <%namespace name="cbdiffs" file="/codeblocks/diffs.mako"/>
456
457
457 <%
458 <%
458 pr_menu_data = {
459 pr_menu_data = {
459 'outdated_comm_count_ver': outdated_comm_count_ver,
460 'outdated_comm_count_ver': outdated_comm_count_ver,
460 'pull_request': c.pull_request
461 'pull_request': c.pull_request
461 }
462 }
462 %>
463 %>
463
464
464 ${cbdiffs.render_diffset_menu(c.diffset, range_diff_on=c.range_diff_on, pull_request_menu=pr_menu_data)}
465 ${cbdiffs.render_diffset_menu(c.diffset, range_diff_on=c.range_diff_on, pull_request_menu=pr_menu_data)}
465
466
466 % if c.range_diff_on:
467 % if c.range_diff_on:
467 % for commit in c.commit_ranges:
468 % for commit in c.commit_ranges:
468 ${cbdiffs.render_diffset(
469 ${cbdiffs.render_diffset(
469 c.changes[commit.raw_id],
470 c.changes[commit.raw_id],
470 commit=commit, use_comments=True,
471 commit=commit, use_comments=True,
471 collapse_when_files_over=5,
472 collapse_when_files_over=5,
472 disable_new_comments=True,
473 disable_new_comments=True,
473 deleted_files_comments=c.deleted_files_comments,
474 deleted_files_comments=c.deleted_files_comments,
474 inline_comments=c.inline_comments,
475 inline_comments=c.inline_comments,
475 pull_request_menu=pr_menu_data, show_todos=False)}
476 pull_request_menu=pr_menu_data, show_todos=False)}
476 % endfor
477 % endfor
477 % else:
478 % else:
478 ${cbdiffs.render_diffset(
479 ${cbdiffs.render_diffset(
479 c.diffset, use_comments=True,
480 c.diffset, use_comments=True,
480 collapse_when_files_over=30,
481 collapse_when_files_over=30,
481 disable_new_comments=not c.allowed_to_comment,
482 disable_new_comments=not c.allowed_to_comment,
482 deleted_files_comments=c.deleted_files_comments,
483 deleted_files_comments=c.deleted_files_comments,
483 inline_comments=c.inline_comments,
484 inline_comments=c.inline_comments,
484 pull_request_menu=pr_menu_data, show_todos=False)}
485 pull_request_menu=pr_menu_data, show_todos=False)}
485 % endif
486 % endif
486
487
487 </div>
488 </div>
488 % else:
489 % else:
489 ## skipping commits we need to clear the view for missing commits
490 ## skipping commits we need to clear the view for missing commits
490 <div style="clear:both;"></div>
491 <div style="clear:both;"></div>
491 % endif
492 % endif
492
493
493 </div>
494 </div>
494 </div>
495 </div>
495
496
496 ## template for inline comment form
497 ## template for inline comment form
497 <%namespace name="comment" file="/changeset/changeset_file_comment.mako"/>
498 <%namespace name="comment" file="/changeset/changeset_file_comment.mako"/>
498
499
499 ## comments heading with count
500 ## comments heading with count
500 <div class="comments-heading">
501 <div class="comments-heading">
501 <i class="icon-comment"></i>
502 <i class="icon-comment"></i>
502 ${_('General Comments')} ${len(c.comments)}
503 ${_('General Comments')} ${len(c.comments)}
503 </div>
504 </div>
504
505
505 ## render general comments
506 ## render general comments
506 <div id="comment-tr-show">
507 <div id="comment-tr-show">
507 % if general_outdated_comm_count_ver:
508 % if general_outdated_comm_count_ver:
508 <div class="info-box">
509 <div class="info-box">
509 % if general_outdated_comm_count_ver == 1:
510 % if general_outdated_comm_count_ver == 1:
510 ${_('there is {num} general comment from older versions').format(num=general_outdated_comm_count_ver)},
511 ${_('there is {num} general comment from older versions').format(num=general_outdated_comm_count_ver)},
511 <a href="#show-hidden-comments" onclick="$('.comment-general.comment-outdated').show(); $(this).parent().hide(); return false;">${_('show it')}</a>
512 <a href="#show-hidden-comments" onclick="$('.comment-general.comment-outdated').show(); $(this).parent().hide(); return false;">${_('show it')}</a>
512 % else:
513 % else:
513 ${_('there are {num} general comments from older versions').format(num=general_outdated_comm_count_ver)},
514 ${_('there are {num} general comments from older versions').format(num=general_outdated_comm_count_ver)},
514 <a href="#show-hidden-comments" onclick="$('.comment-general.comment-outdated').show(); $(this).parent().hide(); return false;">${_('show them')}</a>
515 <a href="#show-hidden-comments" onclick="$('.comment-general.comment-outdated').show(); $(this).parent().hide(); return false;">${_('show them')}</a>
515 % endif
516 % endif
516 </div>
517 </div>
517 % endif
518 % endif
518 </div>
519 </div>
519
520
520 ${comment.generate_comments(c.comments, include_pull_request=True, is_pull_request=True)}
521 ${comment.generate_comments(c.comments, include_pull_request=True, is_pull_request=True)}
521
522
522 % if not c.pull_request.is_closed():
523 % if not c.pull_request.is_closed():
523 ## main comment form and it status
524 ## main comment form and it status
524 ${comment.comments(h.route_path('pullrequest_comment_create', repo_name=c.repo_name,
525 ${comment.comments(h.route_path('pullrequest_comment_create', repo_name=c.repo_name,
525 pull_request_id=c.pull_request.pull_request_id),
526 pull_request_id=c.pull_request.pull_request_id),
526 c.pull_request_review_status,
527 c.pull_request_review_status,
527 is_pull_request=True, change_status=c.allowed_to_change_status)}
528 is_pull_request=True, change_status=c.allowed_to_change_status)}
528
529
529 ## merge status, and merge action
530 ## merge status, and merge action
530 <div class="pull-request-merge">
531 <div class="pull-request-merge">
531 <%include file="/pullrequests/pullrequest_merge_checks.mako"/>
532 <%include file="/pullrequests/pullrequest_merge_checks.mako"/>
532 </div>
533 </div>
533
534
534 %endif
535 %endif
535
536
536 % endif
537 % endif
537 </div>
538 </div>
538
539
539
540
540 ### NAV SIDEBAR
541 ### NAV SIDEBAR
541 <aside class="right-sidebar right-sidebar-expanded" id="pr-nav-sticky" style="display: none">
542 <aside class="right-sidebar right-sidebar-expanded" id="pr-nav-sticky" style="display: none">
542 <div class="sidenav navbar__inner" >
543 <div class="sidenav navbar__inner" >
543 ## TOGGLE
544 ## TOGGLE
544 <div class="sidebar-toggle" onclick="toggleSidebar(); return false">
545 <div class="sidebar-toggle" onclick="toggleSidebar(); return false">
545 <a href="#toggleSidebar" class="grey-link-action">
546 <a href="#toggleSidebar" class="grey-link-action">
546
547
547 </a>
548 </a>
548 </div>
549 </div>
549
550
550 ## CONTENT
551 ## CONTENT
551 <div class="sidebar-content">
552 <div class="sidebar-content">
552
553
553 ## Drafts
554 ## Drafts
554 % if c.rhodecode_edition_id == 'EE':
555 % if c.rhodecode_edition_id == 'EE':
555 <div id="draftsTable" class="sidebar-element clear-both" style="display: ${'block' if c.draft_comments else 'none'}">
556 <div id="draftsTable" class="sidebar-element clear-both" style="display: ${'block' if c.draft_comments else 'none'}">
556 <div class="tooltip right-sidebar-collapsed-state" style="display: none;" onclick="toggleSidebar(); return false" title="${_('Drafts')}">
557 <div class="tooltip right-sidebar-collapsed-state" style="display: none;" onclick="toggleSidebar(); return false" title="${_('Drafts')}">
557 <i class="icon-comment icon-draft"></i>
558 <i class="icon-comment icon-draft"></i>
558 <span id="drafts-count">${len(c.draft_comments)}</span>
559 <span id="drafts-count">${len(c.draft_comments)}</span>
559 </div>
560 </div>
560
561
561 <div class="right-sidebar-expanded-state pr-details-title">
562 <div class="right-sidebar-expanded-state pr-details-title">
562 <span style="padding-left: 2px">
563 <span style="padding-left: 2px">
563 <input name="select_all_drafts" type="checkbox" onclick="selectDraftComments(event)">
564 <input name="select_all_drafts" type="checkbox" onclick="selectDraftComments(event)">
564 </span>
565 </span>
565 <span class="sidebar-heading noselect" onclick="refreshDraftComments(); return false">
566 <span class="sidebar-heading noselect" onclick="refreshDraftComments(); return false">
566 <i class="icon-comment icon-draft"></i>
567 <i class="icon-comment icon-draft"></i>
567 ${_('Drafts')}
568 ${_('Drafts')}
568 </span>
569 </span>
569 <span class="block-right action_button last-item" onclick="submitDrafts(event)">${_('Submit')}</span>
570 <span class="block-right action_button last-item" onclick="submitDrafts(event)">${_('Submit')}</span>
570 </div>
571 </div>
571
572
572 <div id="drafts" class="right-sidebar-expanded-state pr-details-content reviewers">
573 <div id="drafts" class="right-sidebar-expanded-state pr-details-content reviewers">
573 % if c.draft_comments:
574 % if c.draft_comments:
574 ${sidebar.comments_table(c.draft_comments, len(c.draft_comments), draft_comments=True)}
575 ${sidebar.comments_table(c.draft_comments, len(c.draft_comments), draft_comments=True)}
575 % else:
576 % else:
576 <table class="drafts-content-table">
577 <table class="drafts-content-table">
577 <tr>
578 <tr>
578 <td>
579 <td>
579 ${_('No TODOs yet')}
580 ${_('No TODOs yet')}
580 </td>
581 </td>
581 </tr>
582 </tr>
582 </table>
583 </table>
583 % endif
584 % endif
584 </div>
585 </div>
585
586
586 </div>
587 </div>
587 % endif
588 % endif
588
589
589 ## RULES SUMMARY/RULES
590 ## RULES SUMMARY/RULES
590 <div class="sidebar-element clear-both">
591 <div class="sidebar-element clear-both">
591 <% vote_title = _ungettext(
592 <% vote_title = _ungettext(
592 'Status calculated based on votes from {} reviewer',
593 'Status calculated based on votes from {} reviewer',
593 'Status calculated based on votes from {} reviewers', c.reviewers_count).format(c.reviewers_count)
594 'Status calculated based on votes from {} reviewers', c.reviewers_count).format(c.reviewers_count)
594 %>
595 %>
595
596
596 <div class="tooltip right-sidebar-collapsed-state" style="display: none" onclick="toggleSidebar(); return false" title="${vote_title}">
597 <div class="tooltip right-sidebar-collapsed-state" style="display: none" onclick="toggleSidebar(); return false" title="${vote_title}">
597 <i class="icon-circle review-status-${c.pull_request_review_status}"></i>
598 <i class="icon-circle review-status-${c.pull_request_review_status}"></i>
598 ${c.reviewers_count}
599 ${c.reviewers_count}
599 </div>
600 </div>
600
601
601 ## REVIEWERS
602 ## REVIEWERS
602 <div class="right-sidebar-expanded-state pr-details-title">
603 <div class="right-sidebar-expanded-state pr-details-title">
603 <span class="tooltip sidebar-heading" title="${vote_title}">
604 <span class="tooltip sidebar-heading" title="${vote_title}">
604 <i class="icon-circle review-status-${c.pull_request_review_status}"></i>
605 <i class="icon-circle review-status-${c.pull_request_review_status}"></i>
605 ${_('Reviewers')}
606 ${_('Reviewers')}
606 </span>
607 </span>
607
608
608 %if c.allowed_to_update:
609 %if c.allowed_to_update:
609 <span id="open_edit_reviewers" class="block-right action_button last-item">${_('Edit')}</span>
610 <span id="open_edit_reviewers" class="block-right action_button last-item">${_('Edit')}</span>
610 <span id="close_edit_reviewers" class="block-right action_button last-item" style="display: none;">${_('Close')}</span>
611 <span id="close_edit_reviewers" class="block-right action_button last-item" style="display: none;">${_('Close')}</span>
611 %else:
612 %else:
612 <span id="open_edit_reviewers" class="block-right action_button last-item">${_('Show rules')}</span>
613 <span id="open_edit_reviewers" class="block-right action_button last-item">${_('Show rules')}</span>
613 <span id="close_edit_reviewers" class="block-right action_button last-item" style="display: none;">${_('Close')}</span>
614 <span id="close_edit_reviewers" class="block-right action_button last-item" style="display: none;">${_('Close')}</span>
614 %endif
615 %endif
615 </div>
616 </div>
616
617
617 <div id="reviewers" class="right-sidebar-expanded-state pr-details-content reviewers">
618 <div id="reviewers" class="right-sidebar-expanded-state pr-details-content reviewers">
618
619
619 <div id="review_rules" style="display: none" class="">
620 <div id="review_rules" style="display: none" class="">
620
621
621 <strong>${_('Reviewer rules')}</strong>
622 <strong>${_('Reviewer rules')}</strong>
622 <div class="pr-reviewer-rules">
623 <div class="pr-reviewer-rules">
623 ## review rules will be appended here, by default reviewers logic
624 ## review rules will be appended here, by default reviewers logic
624 </div>
625 </div>
625 <input id="review_data" type="hidden" name="review_data" value="">
626 <input id="review_data" type="hidden" name="review_data" value="">
626 </div>
627 </div>
627
628
628 ## members redering block
629 ## members redering block
629 <input type="hidden" name="__start__" value="review_members:sequence">
630 <input type="hidden" name="__start__" value="review_members:sequence">
630
631
631 <table id="review_members" class="group_members">
632 <table id="review_members" class="group_members">
632 ## This content is loaded via JS and ReviewersPanel
633 ## This content is loaded via JS and ReviewersPanel
633 </table>
634 </table>
634
635
635 <input type="hidden" name="__end__" value="review_members:sequence">
636 <input type="hidden" name="__end__" value="review_members:sequence">
636 ## end members redering block
637 ## end members redering block
637
638
638 %if not c.pull_request.is_closed():
639 %if not c.pull_request.is_closed():
639 <div id="add_reviewer" class="ac" style="display: none;">
640 <div id="add_reviewer" class="ac" style="display: none;">
640 %if c.allowed_to_update:
641 %if c.allowed_to_update:
641 % if not c.forbid_adding_reviewers:
642 % if not c.forbid_adding_reviewers:
642 <div id="add_reviewer_input" class="reviewer_ac" style="width: 240px">
643 <div id="add_reviewer_input" class="reviewer_ac" style="width: 240px">
643 <input class="ac-input" id="user" name="user" placeholder="${_('Add reviewer or reviewer group')}" type="text" autocomplete="off">
644 <input class="ac-input" id="user" name="user" placeholder="${_('Add reviewer or reviewer group')}" type="text" autocomplete="off">
644 <div id="reviewers_container"></div>
645 <div id="reviewers_container"></div>
645 </div>
646 </div>
646 % endif
647 % endif
647 <div class="pull-right" style="margin-bottom: 15px">
648 <div class="pull-right" style="margin-bottom: 15px">
648 <button data-role="reviewer" id="update_reviewers" class="btn btn-sm no-margin">${_('Save Changes')}</button>
649 <button data-role="reviewer" id="update_reviewers" class="btn btn-sm no-margin">${_('Save Changes')}</button>
649 </div>
650 </div>
650 %endif
651 %endif
651 </div>
652 </div>
652 %endif
653 %endif
653 </div>
654 </div>
654 </div>
655 </div>
655
656
656 ## OBSERVERS
657 ## OBSERVERS
657 % if c.rhodecode_edition_id == 'EE':
658 % if c.rhodecode_edition_id == 'EE':
658 <div class="sidebar-element clear-both">
659 <div class="sidebar-element clear-both">
659 <% vote_title = _ungettext(
660 <% vote_title = _ungettext(
660 '{} observer without voting right.',
661 '{} observer without voting right.',
661 '{} observers without voting right.', c.observers_count).format(c.observers_count)
662 '{} observers without voting right.', c.observers_count).format(c.observers_count)
662 %>
663 %>
663
664
664 <div class="tooltip right-sidebar-collapsed-state" style="display: none" onclick="toggleSidebar(); return false" title="${vote_title}">
665 <div class="tooltip right-sidebar-collapsed-state" style="display: none" onclick="toggleSidebar(); return false" title="${vote_title}">
665 <i class="icon-circle-thin"></i>
666 <i class="icon-circle-thin"></i>
666 ${c.observers_count}
667 ${c.observers_count}
667 </div>
668 </div>
668
669
669 <div class="right-sidebar-expanded-state pr-details-title">
670 <div class="right-sidebar-expanded-state pr-details-title">
670 <span class="tooltip sidebar-heading" title="${vote_title}">
671 <span class="tooltip sidebar-heading" title="${vote_title}">
671 <i class="icon-circle-thin"></i>
672 <i class="icon-circle-thin"></i>
672 ${_('Observers')}
673 ${_('Observers')}
673 </span>
674 </span>
674 %if c.allowed_to_update:
675 %if c.allowed_to_update:
675 <span id="open_edit_observers" class="block-right action_button last-item">${_('Edit')}</span>
676 <span id="open_edit_observers" class="block-right action_button last-item">${_('Edit')}</span>
676 <span id="close_edit_observers" class="block-right action_button last-item" style="display: none;">${_('Close')}</span>
677 <span id="close_edit_observers" class="block-right action_button last-item" style="display: none;">${_('Close')}</span>
677 %endif
678 %endif
678 </div>
679 </div>
679
680
680 <div id="observers" class="right-sidebar-expanded-state pr-details-content reviewers">
681 <div id="observers" class="right-sidebar-expanded-state pr-details-content reviewers">
681 ## members redering block
682 ## members redering block
682 <input type="hidden" name="__start__" value="observer_members:sequence">
683 <input type="hidden" name="__start__" value="observer_members:sequence">
683
684
684 <table id="observer_members" class="group_members">
685 <table id="observer_members" class="group_members">
685 ## This content is loaded via JS and ReviewersPanel
686 ## This content is loaded via JS and ReviewersPanel
686 </table>
687 </table>
687
688
688 <input type="hidden" name="__end__" value="observer_members:sequence">
689 <input type="hidden" name="__end__" value="observer_members:sequence">
689 ## end members redering block
690 ## end members redering block
690
691
691 %if not c.pull_request.is_closed():
692 %if not c.pull_request.is_closed():
692 <div id="add_observer" class="ac" style="display: none;">
693 <div id="add_observer" class="ac" style="display: none;">
693 %if c.allowed_to_update:
694 %if c.allowed_to_update:
694 % if not c.forbid_adding_reviewers or 1:
695 % if not c.forbid_adding_reviewers or 1:
695 <div id="add_reviewer_input" class="reviewer_ac" style="width: 240px" >
696 <div id="add_reviewer_input" class="reviewer_ac" style="width: 240px" >
696 <input class="ac-input" id="observer" name="observer" placeholder="${_('Add observer or observer group')}" type="text" autocomplete="off">
697 <input class="ac-input" id="observer" name="observer" placeholder="${_('Add observer or observer group')}" type="text" autocomplete="off">
697 <div id="observers_container"></div>
698 <div id="observers_container"></div>
698 </div>
699 </div>
699 % endif
700 % endif
700 <div class="pull-right" style="margin-bottom: 15px">
701 <div class="pull-right" style="margin-bottom: 15px">
701 <button data-role="observer" id="update_observers" class="btn btn-sm no-margin">${_('Save Changes')}</button>
702 <button data-role="observer" id="update_observers" class="btn btn-sm no-margin">${_('Save Changes')}</button>
702 </div>
703 </div>
703 %endif
704 %endif
704 </div>
705 </div>
705 %endif
706 %endif
706 </div>
707 </div>
707 </div>
708 </div>
708 % endif
709 % endif
709
710
710 ## TODOs
711 ## TODOs
711 <div id="todosTable" class="sidebar-element clear-both">
712 <div id="todosTable" class="sidebar-element clear-both">
712 <div class="tooltip right-sidebar-collapsed-state" style="display: none" onclick="toggleSidebar(); return false" title="TODOs">
713 <div class="tooltip right-sidebar-collapsed-state" style="display: none" onclick="toggleSidebar(); return false" title="TODOs">
713 <i class="icon-flag-filled"></i>
714 <i class="icon-flag-filled"></i>
714 <span id="todos-count">${len(c.unresolved_comments)}</span>
715 <span id="todos-count">${len(c.unresolved_comments)}</span>
715 </div>
716 </div>
716
717
717 <div class="right-sidebar-expanded-state pr-details-title">
718 <div class="right-sidebar-expanded-state pr-details-title">
718 ## Only show unresolved, that is only what matters
719 ## Only show unresolved, that is only what matters
719 <span class="sidebar-heading noselect" onclick="refreshTODOs(); return false">
720 <span class="sidebar-heading noselect" onclick="refreshTODOs(); return false">
720 <i class="icon-flag-filled"></i>
721 <i class="icon-flag-filled"></i>
721 TODOs
722 TODOs
722 </span>
723 </span>
723
724
724 % if not c.at_version:
725 % if not c.at_version:
725 % if c.resolved_comments:
726 % if c.resolved_comments:
726 <span class="block-right action_button last-item noselect" onclick="$('.unresolved-todo-text').toggle(); return toggleElement(this, '.resolved-todo');" data-toggle-on="Show resolved" data-toggle-off="Hide resolved">Show resolved</span>
727 <span class="block-right action_button last-item noselect" onclick="$('.unresolved-todo-text').toggle(); return toggleElement(this, '.resolved-todo');" data-toggle-on="Show resolved" data-toggle-off="Hide resolved">Show resolved</span>
727 % else:
728 % else:
728 <span class="block-right last-item noselect">Show resolved</span>
729 <span class="block-right last-item noselect">Show resolved</span>
729 % endif
730 % endif
730 % endif
731 % endif
731 </div>
732 </div>
732
733
733 <div class="right-sidebar-expanded-state pr-details-content">
734 <div class="right-sidebar-expanded-state pr-details-content">
734
735
735 % if c.at_version:
736 % if c.at_version:
736 <table>
737 <table>
737 <tr>
738 <tr>
738 <td class="unresolved-todo-text">${_('TODOs unavailable when browsing versions')}.</td>
739 <td class="unresolved-todo-text">${_('TODOs unavailable when browsing versions')}.</td>
739 </tr>
740 </tr>
740 </table>
741 </table>
741 % else:
742 % else:
742 % if c.unresolved_comments + c.resolved_comments:
743 % if c.unresolved_comments + c.resolved_comments:
743 ${sidebar.comments_table(c.unresolved_comments + c.resolved_comments, len(c.unresolved_comments), todo_comments=True)}
744 ${sidebar.comments_table(c.unresolved_comments + c.resolved_comments, len(c.unresolved_comments), todo_comments=True)}
744 % else:
745 % else:
745 <table class="todos-content-table">
746 <table class="todos-content-table">
746 <tr>
747 <tr>
747 <td>
748 <td>
748 ${_('No TODOs yet')}
749 ${_('No TODOs yet')}
749 </td>
750 </td>
750 </tr>
751 </tr>
751 </table>
752 </table>
752 % endif
753 % endif
753 % endif
754 % endif
754 </div>
755 </div>
755 </div>
756 </div>
756
757
757 ## COMMENTS
758 ## COMMENTS
758 <div id="commentsTable" class="sidebar-element clear-both">
759 <div id="commentsTable" class="sidebar-element clear-both">
759 <div class="tooltip right-sidebar-collapsed-state" style="display: none" onclick="toggleSidebar(); return false" title="${_('Comments')}">
760 <div class="tooltip right-sidebar-collapsed-state" style="display: none" onclick="toggleSidebar(); return false" title="${_('Comments')}">
760 <i class="icon-comment" style="color: #949494"></i>
761 <i class="icon-comment" style="color: #949494"></i>
761 <span id="comments-count">${len(c.inline_comments_flat+c.comments)}</span>
762 <span id="comments-count">${len(c.inline_comments_flat+c.comments)}</span>
762 <span class="display-none" id="general-comments-count">${len(c.comments)}</span>
763 <span class="display-none" id="general-comments-count">${len(c.comments)}</span>
763 <span class="display-none" id="inline-comments-count">${len(c.inline_comments_flat)}</span>
764 <span class="display-none" id="inline-comments-count">${len(c.inline_comments_flat)}</span>
764 </div>
765 </div>
765
766
766 <div class="right-sidebar-expanded-state pr-details-title">
767 <div class="right-sidebar-expanded-state pr-details-title">
767 <span class="sidebar-heading noselect" onclick="refreshComments(); return false">
768 <span class="sidebar-heading noselect" onclick="refreshComments(); return false">
768 <i class="icon-comment" style="color: #949494"></i>
769 <i class="icon-comment" style="color: #949494"></i>
769 ${_('Comments')}
770 ${_('Comments')}
770
771
771 ## % if outdated_comm_count_ver:
772 ## % if outdated_comm_count_ver:
772 ## <a href="#" onclick="showOutdated(); Rhodecode.comments.nextOutdatedComment(); return false;">
773 ## <a href="#" onclick="showOutdated(); Rhodecode.comments.nextOutdatedComment(); return false;">
773 ## (${_("{} Outdated").format(outdated_comm_count_ver)})
774 ## (${_("{} Outdated").format(outdated_comm_count_ver)})
774 ## </a>
775 ## </a>
775 ## <a href="#" class="showOutdatedComments" onclick="showOutdated(this); return false;"> | ${_('show outdated')}</a>
776 ## <a href="#" class="showOutdatedComments" onclick="showOutdated(this); return false;"> | ${_('show outdated')}</a>
776 ## <a href="#" class="hideOutdatedComments" style="display: none" onclick="hideOutdated(this); return false;"> | ${_('hide outdated')}</a>
777 ## <a href="#" class="hideOutdatedComments" style="display: none" onclick="hideOutdated(this); return false;"> | ${_('hide outdated')}</a>
777
778
778 ## % else:
779 ## % else:
779 ## (${_("{} Outdated").format(outdated_comm_count_ver)})
780 ## (${_("{} Outdated").format(outdated_comm_count_ver)})
780 ## % endif
781 ## % endif
781
782
782 </span>
783 </span>
783
784
784 % if outdated_comm_count_ver:
785 % if outdated_comm_count_ver:
785 <span class="block-right action_button last-item noselect" onclick="return toggleElement(this, '.hidden-comment');" data-toggle-on="Show outdated" data-toggle-off="Hide outdated">Show outdated</span>
786 <span class="block-right action_button last-item noselect" onclick="return toggleElement(this, '.hidden-comment');" data-toggle-on="Show outdated" data-toggle-off="Hide outdated">Show outdated</span>
786 % else:
787 % else:
787 <span class="block-right last-item noselect">Show hidden</span>
788 <span class="block-right last-item noselect">Show hidden</span>
788 % endif
789 % endif
789
790
790 </div>
791 </div>
791
792
792 <div class="right-sidebar-expanded-state pr-details-content">
793 <div class="right-sidebar-expanded-state pr-details-content">
793 % if c.inline_comments_flat + c.comments:
794 % if c.inline_comments_flat + c.comments:
794 ${sidebar.comments_table(c.inline_comments_flat + c.comments, len(c.inline_comments_flat+c.comments))}
795 ${sidebar.comments_table(c.inline_comments_flat + c.comments, len(c.inline_comments_flat+c.comments))}
795 % else:
796 % else:
796 <table class="comments-content-table">
797 <table class="comments-content-table">
797 <tr>
798 <tr>
798 <td>
799 <td>
799 ${_('No Comments yet')}
800 ${_('No Comments yet')}
800 </td>
801 </td>
801 </tr>
802 </tr>
802 </table>
803 </table>
803 % endif
804 % endif
804 </div>
805 </div>
805
806
806 </div>
807 </div>
807
808
808 ## Referenced Tickets
809 ## Referenced Tickets
809 <div class="sidebar-element clear-both">
810 <div class="sidebar-element clear-both">
810 <div class="tooltip right-sidebar-collapsed-state" style="display: none" onclick="toggleSidebar(); return false" title="${_('Referenced Tickets')}">
811 <div class="tooltip right-sidebar-collapsed-state" style="display: none" onclick="toggleSidebar(); return false" title="${_('Referenced Tickets')}">
811 <i class="icon-info-circled"></i>
812 <i class="icon-info-circled"></i>
812 ${(c.referenced_desc_issues.issues_unique_count + c.referenced_commit_issues.issues_unique_count)}
813 ${(c.referenced_desc_issues.issues_unique_count + c.referenced_commit_issues.issues_unique_count)}
813 </div>
814 </div>
814
815
815 <div class="right-sidebar-expanded-state pr-details-title">
816 <div class="right-sidebar-expanded-state pr-details-title">
816 <span class="sidebar-heading">
817 <span class="sidebar-heading">
817 <i class="icon-info-circled"></i>
818 <i class="icon-info-circled"></i>
818 ${_('Referenced Tickets')}
819 ${_('Referenced Tickets')}
819 </span>
820 </span>
820 </div>
821 </div>
821 <div class="right-sidebar-expanded-state pr-details-content">
822 <div class="right-sidebar-expanded-state pr-details-content">
822 <table>
823 <table>
823
824
824 <tr><td><code>${_('In pull request description')}:</code></td></tr>
825 <tr><td><code>${_('In pull request description')}:</code></td></tr>
825 % if c.referenced_desc_issues.issues:
826 % if c.referenced_desc_issues.issues:
826
827
827 % for ticket_id, ticket_dict in c.referenced_desc_issues.unique_issues.items():
828 % for ticket_id, ticket_dict in c.referenced_desc_issues.unique_issues.items():
828 <tr>
829 <tr>
829 <td>
830 <td>
830 <a href="${ticket_dict[0].get('url')}">
831 <a href="${ticket_dict[0].get('url')}">
831 ${ticket_id}
832 ${ticket_id}
832 </a>
833 </a>
833 </td>
834 </td>
834 </tr>
835 </tr>
835
836
836 % endfor
837 % endfor
837 % else:
838 % else:
838 <tr>
839 <tr>
839 <td>
840 <td>
840 ${_('No Ticket data found.')}
841 ${_('No Ticket data found.')}
841 </td>
842 </td>
842 </tr>
843 </tr>
843 % endif
844 % endif
844
845
845 <tr><td style="padding-top: 10px"><code>${_('In commit messages')}:</code></td></tr>
846 <tr><td style="padding-top: 10px"><code>${_('In commit messages')}:</code></td></tr>
846 % if c.referenced_commit_issues.issues:
847 % if c.referenced_commit_issues.issues:
847 % for ticket_id, ticket_dict in c.referenced_commit_issues.unique_issues.items():
848 % for ticket_id, ticket_dict in c.referenced_commit_issues.unique_issues.items():
848 <tr>
849 <tr>
849 <td>
850 <td>
850 <a href="${ticket_dict[0].get('url')}">
851 <a href="${ticket_dict[0].get('url')}">
851 ${ticket_id}
852 ${ticket_id}
852 </a>
853 </a>
853 - ${_ungettext('in %s commit', 'in %s commits', len(ticket_dict)) % (len(ticket_dict))}
854 - ${_ungettext('in %s commit', 'in %s commits', len(ticket_dict)) % (len(ticket_dict))}
854 </td>
855 </td>
855 </tr>
856 </tr>
856 % endfor
857 % endfor
857 % else:
858 % else:
858 <tr>
859 <tr>
859 <td>
860 <td>
860 ${_('No Ticket data found.')}
861 ${_('No Ticket data found.')}
861 </td>
862 </td>
862 </tr>
863 </tr>
863 % endif
864 % endif
864 </table>
865 </table>
865
866
866 </div>
867 </div>
867 </div>
868 </div>
868
869
869 </div>
870 </div>
870
871
871 </div>
872 </div>
872 </aside>
873 </aside>
873
874
874 ## This JS needs to be at the end
875 ## This JS needs to be at the end
875 <script type="text/javascript">
876 <script type="text/javascript">
876
877
877 versionController = new VersionController();
878 versionController = new VersionController();
878 versionController.init();
879 versionController.init();
879
880
880 reviewersController = new ReviewersController();
881 reviewersController = new ReviewersController();
881 commitsController = new CommitsController();
882 commitsController = new CommitsController();
882 commentsController = new CommentsController();
883 commentsController = new CommentsController();
883
884
884 updateController = new UpdatePrController();
885 updateController = new UpdatePrController();
885
886
886 window.reviewerRulesData = ${c.pull_request_default_reviewers_data_json | n};
887 window.reviewerRulesData = ${c.pull_request_default_reviewers_data_json | n};
887 window.setReviewersData = ${c.pull_request_set_reviewers_data_json | n};
888 window.setReviewersData = ${c.pull_request_set_reviewers_data_json | n};
888 window.setObserversData = ${c.pull_request_set_observers_data_json | n};
889 window.setObserversData = ${c.pull_request_set_observers_data_json | n};
889
890
890 (function () {
891 (function () {
891 "use strict";
892 "use strict";
892
893
893 // custom code mirror
894 // custom code mirror
894 var codeMirrorInstance = $('#pr-description-input').get(0).MarkupForm.cm;
895 var codeMirrorInstance = $('#pr-description-input').get(0).MarkupForm.cm;
895
896
896 PRDetails.init();
897 PRDetails.init();
897 ReviewersPanel.init(reviewersController, reviewerRulesData, setReviewersData);
898 ReviewersPanel.init(reviewersController, reviewerRulesData, setReviewersData);
898 ObserversPanel.init(reviewersController, reviewerRulesData, setObserversData);
899 ObserversPanel.init(reviewersController, reviewerRulesData, setObserversData);
899
900
900 window.showOutdated = function (self) {
901 window.showOutdated = function (self) {
901 $('.comment-inline.comment-outdated').show();
902 $('.comment-inline.comment-outdated').show();
902 $('.filediff-outdated').show();
903 $('.filediff-outdated').show();
903 $('.showOutdatedComments').hide();
904 $('.showOutdatedComments').hide();
904 $('.hideOutdatedComments').show();
905 $('.hideOutdatedComments').show();
905 };
906 };
906
907
907 window.hideOutdated = function (self) {
908 window.hideOutdated = function (self) {
908 $('.comment-inline.comment-outdated').hide();
909 $('.comment-inline.comment-outdated').hide();
909 $('.filediff-outdated').hide();
910 $('.filediff-outdated').hide();
910 $('.hideOutdatedComments').hide();
911 $('.hideOutdatedComments').hide();
911 $('.showOutdatedComments').show();
912 $('.showOutdatedComments').show();
912 };
913 };
913
914
914 window.refreshMergeChecks = function () {
915 window.refreshMergeChecks = function () {
915 var loadUrl = "${request.current_route_path(_query=dict(merge_checks=1))}";
916 var loadUrl = "${request.current_route_path(_query=dict(merge_checks=1))}";
916 $('.pull-request-merge').css('opacity', 0.3);
917 $('.pull-request-merge').css('opacity', 0.3);
917 $('.action-buttons-extra').css('opacity', 0.3);
918 $('.action-buttons-extra').css('opacity', 0.3);
918
919
919 $('.pull-request-merge').load(
920 $('.pull-request-merge').load(
920 loadUrl, function () {
921 loadUrl, function () {
921 $('.pull-request-merge').css('opacity', 1);
922 $('.pull-request-merge').css('opacity', 1);
922
923
923 $('.action-buttons-extra').css('opacity', 1);
924 $('.action-buttons-extra').css('opacity', 1);
924 }
925 }
925 );
926 );
926 };
927 };
927
928
928 window.submitDrafts = function (event) {
929 window.submitDrafts = function (event) {
929 var target = $(event.currentTarget);
930 var target = $(event.currentTarget);
930 var callback = function (result) {
931 var callback = function (result) {
931 target.removeAttr('onclick').html('saving...');
932 target.removeAttr('onclick').html('saving...');
932 }
933 }
933 var draftIds = [];
934 var draftIds = [];
934 $.each($('[name=submit_draft]:checked'), function (idx, val) {
935 $.each($('[name=submit_draft]:checked'), function (idx, val) {
935 draftIds.push(parseInt($(val).val()));
936 draftIds.push(parseInt($(val).val()));
936 })
937 })
937 if (draftIds.length > 0) {
938 if (draftIds.length > 0) {
938 Rhodecode.comments.finalizeDrafts(draftIds, callback);
939 Rhodecode.comments.finalizeDrafts(draftIds, callback);
939 }
940 }
940 else {
941 else {
941
942
942 }
943 }
943 }
944 }
944
945
945 window.selectDraftComments = function (event) {
946 window.selectDraftComments = function (event) {
946 var $target = $(event.currentTarget);
947 var $target = $(event.currentTarget);
947 $('[name=submit_draft]').prop('checked', $target.prop('checked'))
948 $('[name=submit_draft]').prop('checked', $target.prop('checked'))
948 }
949 }
949
950
950 window.closePullRequest = function (status) {
951 window.closePullRequest = function (status) {
951 if (!confirm(_gettext('Are you sure to close this pull request without merging?'))) {
952 if (!confirm(_gettext('Are you sure to close this pull request without merging?'))) {
952 return false;
953 return false;
953 }
954 }
954 // inject closing flag
955 // inject closing flag
955 $('.action-buttons-extra').append('<input type="hidden" class="close-pr-input" id="close_pull_request" value="1">');
956 $('.action-buttons-extra').append('<input type="hidden" class="close-pr-input" id="close_pull_request" value="1">');
956 $(generalCommentForm.statusChange).select2("val", status).trigger('change');
957 $(generalCommentForm.statusChange).select2("val", status).trigger('change');
957 $(generalCommentForm.submitForm).submit();
958 $(generalCommentForm.submitForm).submit();
958 };
959 };
959
960
960 //TODO this functionality is now missing
961 //TODO this functionality is now missing
961 $('#show-outdated-comments').on('click', function (e) {
962 $('#show-outdated-comments').on('click', function (e) {
962 var button = $(this);
963 var button = $(this);
963 var outdated = $('.comment-outdated');
964 var outdated = $('.comment-outdated');
964
965
965 if (button.html() === "(Show)") {
966 if (button.html() === "(Show)") {
966 button.html("(Hide)");
967 button.html("(Hide)");
967 outdated.show();
968 outdated.show();
968 } else {
969 } else {
969 button.html("(Show)");
970 button.html("(Show)");
970 outdated.hide();
971 outdated.hide();
971 }
972 }
972 });
973 });
973
974
974 $('#merge_pull_request_form').submit(function () {
975 $('#merge_pull_request_form').submit(function () {
975 if (!$('#merge_pull_request').attr('disabled')) {
976 if (!$('#merge_pull_request').attr('disabled')) {
976 $('#merge_pull_request').attr('disabled', 'disabled');
977 $('#merge_pull_request').attr('disabled', 'disabled');
977 }
978 }
978 return true;
979 return true;
979 });
980 });
980
981
981 $('#edit_pull_request').on('click', function (e) {
982 $('#edit_pull_request').on('click', function (e) {
982 var title = $('#pr-title-input').val();
983 var title = $('#pr-title-input').val();
983 var description = codeMirrorInstance.getValue();
984 var description = codeMirrorInstance.getValue();
984 var renderer = $('#pr-renderer-input').val();
985 var renderer = $('#pr-renderer-input').val();
985 editPullRequest(
986 editPullRequest(
986 "${c.repo_name}", "${c.pull_request.pull_request_id}",
987 "${c.repo_name}", "${c.pull_request.pull_request_id}",
987 title, description, renderer);
988 title, description, renderer);
988 });
989 });
989
990
990 var $updateButtons = $('#update_reviewers,#update_observers');
991 var $updateButtons = $('#update_reviewers,#update_observers');
991 $updateButtons.on('click', function (e) {
992 $updateButtons.on('click', function (e) {
992 var role = $(this).data('role');
993 var role = $(this).data('role');
993 $updateButtons.attr('disabled', 'disabled');
994 $updateButtons.attr('disabled', 'disabled');
994 $updateButtons.addClass('disabled');
995 $updateButtons.addClass('disabled');
995 $updateButtons.html(_gettext('Saving...'));
996 $updateButtons.html(_gettext('Saving...'));
996 reviewersController.updateReviewers(
997 reviewersController.updateReviewers(
997 templateContext.repo_name,
998 templateContext.repo_name,
998 templateContext.pull_request_data.pull_request_id,
999 templateContext.pull_request_data.pull_request_id,
999 role
1000 role
1000 );
1001 );
1001 });
1002 });
1002
1003
1003 // fixing issue with caches on firefox
1004 // fixing issue with caches on firefox
1004 $('#update_commits').removeAttr("disabled");
1005 $('#update_commits').removeAttr("disabled");
1005
1006
1006 $('.show-inline-comments').on('click', function (e) {
1007 $('.show-inline-comments').on('click', function (e) {
1007 var boxid = $(this).attr('data-comment-id');
1008 var boxid = $(this).attr('data-comment-id');
1008 var button = $(this);
1009 var button = $(this);
1009
1010
1010 if (button.hasClass("comments-visible")) {
1011 if (button.hasClass("comments-visible")) {
1011 $('#{0} .inline-comments'.format(boxid)).each(function (index) {
1012 $('#{0} .inline-comments'.format(boxid)).each(function (index) {
1012 $(this).hide();
1013 $(this).hide();
1013 });
1014 });
1014 button.removeClass("comments-visible");
1015 button.removeClass("comments-visible");
1015 } else {
1016 } else {
1016 $('#{0} .inline-comments'.format(boxid)).each(function (index) {
1017 $('#{0} .inline-comments'.format(boxid)).each(function (index) {
1017 $(this).show();
1018 $(this).show();
1018 });
1019 });
1019 button.addClass("comments-visible");
1020 button.addClass("comments-visible");
1020 }
1021 }
1021 });
1022 });
1022
1023
1023 $('.show-inline-comments').on('change', function (e) {
1024 $('.show-inline-comments').on('change', function (e) {
1024 var show = 'none';
1025 var show = 'none';
1025 var target = e.currentTarget;
1026 var target = e.currentTarget;
1026 if (target.checked) {
1027 if (target.checked) {
1027 show = ''
1028 show = ''
1028 }
1029 }
1029 var boxid = $(target).attr('id_for');
1030 var boxid = $(target).attr('id_for');
1030 var comments = $('#{0} .inline-comments'.format(boxid));
1031 var comments = $('#{0} .inline-comments'.format(boxid));
1031 var fn_display = function (idx) {
1032 var fn_display = function (idx) {
1032 $(this).css('display', show);
1033 $(this).css('display', show);
1033 };
1034 };
1034 $(comments).each(fn_display);
1035 $(comments).each(fn_display);
1035 var btns = $('#{0} .inline-comments-button'.format(boxid));
1036 var btns = $('#{0} .inline-comments-button'.format(boxid));
1036 $(btns).each(fn_display);
1037 $(btns).each(fn_display);
1037 });
1038 });
1038
1039
1039 // register submit callback on commentForm form to track TODOs, and refresh mergeChecks conditions
1040 // register submit callback on commentForm form to track TODOs, and refresh mergeChecks conditions
1040 window.commentFormGlobalSubmitSuccessCallback = function (comment) {
1041 window.commentFormGlobalSubmitSuccessCallback = function (comment) {
1041 if (!comment.draft) {
1042 if (!comment.draft) {
1042 refreshMergeChecks();
1043 refreshMergeChecks();
1043 }
1044 }
1044 };
1045 };
1045
1046
1046 ReviewerAutoComplete('#user', reviewersController);
1047 ReviewerAutoComplete('#user', reviewersController);
1047 ObserverAutoComplete('#observer', reviewersController);
1048 ObserverAutoComplete('#observer', reviewersController);
1048
1049
1049 })();
1050 })();
1050
1051
1051 $(document).ready(function () {
1052 $(document).ready(function () {
1052
1053
1053 var channel = '${c.pr_broadcast_channel}';
1054 var channel = '${c.pr_broadcast_channel}';
1054 new ReviewerPresenceController(channel)
1055 new ReviewerPresenceController(channel)
1055 // register globally so inject comment logic can re-use it.
1056 // register globally so inject comment logic can re-use it.
1056 window.commentsController = commentsController;
1057 window.commentsController = commentsController;
1057 })
1058 })
1058 </script>
1059 </script>
1059
1060
1060 </%def>
1061 </%def>
General Comments 0
You need to be logged in to leave comments. Login now