##// END OF EJS Templates
pull-requests: use consistent check who is allowed to delete a pull request.
marcink -
r1607:5a387f60 default
parent child Browse files
Show More
@@ -1,1083 +1,1091 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2012-2017 RhodeCode GmbH
3 # Copyright (C) 2012-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 pull requests controller for rhodecode for initializing pull requests
22 pull requests controller for rhodecode for initializing pull requests
23 """
23 """
24 import types
24 import types
25
25
26 import peppercorn
26 import peppercorn
27 import formencode
27 import formencode
28 import logging
28 import logging
29 import collections
29 import collections
30
30
31 from webob.exc import HTTPNotFound, HTTPForbidden, HTTPBadRequest
31 from webob.exc import HTTPNotFound, HTTPForbidden, HTTPBadRequest
32 from pylons import request, tmpl_context as c, url
32 from pylons import request, tmpl_context as c, url
33 from pylons.controllers.util import redirect
33 from pylons.controllers.util import redirect
34 from pylons.i18n.translation import _
34 from pylons.i18n.translation import _
35 from pyramid.threadlocal import get_current_registry
35 from pyramid.threadlocal import get_current_registry
36 from sqlalchemy.sql import func
36 from sqlalchemy.sql import func
37 from sqlalchemy.sql.expression import or_
37 from sqlalchemy.sql.expression import or_
38
38
39 from rhodecode import events
39 from rhodecode import events
40 from rhodecode.lib import auth, diffs, helpers as h, codeblocks
40 from rhodecode.lib import auth, diffs, helpers as h, codeblocks
41 from rhodecode.lib.ext_json import json
41 from rhodecode.lib.ext_json import json
42 from rhodecode.lib.base import (
42 from rhodecode.lib.base import (
43 BaseRepoController, render, vcs_operation_context)
43 BaseRepoController, render, vcs_operation_context)
44 from rhodecode.lib.auth import (
44 from rhodecode.lib.auth import (
45 LoginRequired, HasRepoPermissionAnyDecorator, NotAnonymous,
45 LoginRequired, HasRepoPermissionAnyDecorator, NotAnonymous,
46 HasAcceptedRepoType, XHRRequired)
46 HasAcceptedRepoType, XHRRequired)
47 from rhodecode.lib.channelstream import channelstream_request
47 from rhodecode.lib.channelstream import channelstream_request
48 from rhodecode.lib.utils import jsonify
48 from rhodecode.lib.utils import jsonify
49 from rhodecode.lib.utils2 import (
49 from rhodecode.lib.utils2 import (
50 safe_int, safe_str, str2bool, safe_unicode)
50 safe_int, safe_str, str2bool, safe_unicode)
51 from rhodecode.lib.vcs.backends.base import (
51 from rhodecode.lib.vcs.backends.base import (
52 EmptyCommit, UpdateFailureReason, EmptyRepository)
52 EmptyCommit, UpdateFailureReason, EmptyRepository)
53 from rhodecode.lib.vcs.exceptions import (
53 from rhodecode.lib.vcs.exceptions import (
54 EmptyRepositoryError, CommitDoesNotExistError, RepositoryRequirementError,
54 EmptyRepositoryError, CommitDoesNotExistError, RepositoryRequirementError,
55 NodeDoesNotExistError)
55 NodeDoesNotExistError)
56
56
57 from rhodecode.model.changeset_status import ChangesetStatusModel
57 from rhodecode.model.changeset_status import ChangesetStatusModel
58 from rhodecode.model.comment import CommentsModel
58 from rhodecode.model.comment import CommentsModel
59 from rhodecode.model.db import (PullRequest, ChangesetStatus, ChangesetComment,
59 from rhodecode.model.db import (PullRequest, ChangesetStatus, ChangesetComment,
60 Repository, PullRequestVersion)
60 Repository, PullRequestVersion)
61 from rhodecode.model.forms import PullRequestForm
61 from rhodecode.model.forms import PullRequestForm
62 from rhodecode.model.meta import Session
62 from rhodecode.model.meta import Session
63 from rhodecode.model.pull_request import PullRequestModel, MergeCheck
63 from rhodecode.model.pull_request import PullRequestModel, MergeCheck
64
64
65 log = logging.getLogger(__name__)
65 log = logging.getLogger(__name__)
66
66
67
67
68 class PullrequestsController(BaseRepoController):
68 class PullrequestsController(BaseRepoController):
69
69
70 def __before__(self):
70 def __before__(self):
71 super(PullrequestsController, self).__before__()
71 super(PullrequestsController, self).__before__()
72 c.REVIEW_STATUS_APPROVED = ChangesetStatus.STATUS_APPROVED
72 c.REVIEW_STATUS_APPROVED = ChangesetStatus.STATUS_APPROVED
73 c.REVIEW_STATUS_REJECTED = ChangesetStatus.STATUS_REJECTED
73 c.REVIEW_STATUS_REJECTED = ChangesetStatus.STATUS_REJECTED
74
74
75 def _extract_ordering(self, request):
75 def _extract_ordering(self, request):
76 column_index = safe_int(request.GET.get('order[0][column]'))
76 column_index = safe_int(request.GET.get('order[0][column]'))
77 order_dir = request.GET.get('order[0][dir]', 'desc')
77 order_dir = request.GET.get('order[0][dir]', 'desc')
78 order_by = request.GET.get(
78 order_by = request.GET.get(
79 'columns[%s][data][sort]' % column_index, 'name_raw')
79 'columns[%s][data][sort]' % column_index, 'name_raw')
80 return order_by, order_dir
80 return order_by, order_dir
81
81
82 @LoginRequired()
82 @LoginRequired()
83 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
83 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
84 'repository.admin')
84 'repository.admin')
85 @HasAcceptedRepoType('git', 'hg')
85 @HasAcceptedRepoType('git', 'hg')
86 def show_all(self, repo_name):
86 def show_all(self, repo_name):
87 # filter types
87 # filter types
88 c.active = 'open'
88 c.active = 'open'
89 c.source = str2bool(request.GET.get('source'))
89 c.source = str2bool(request.GET.get('source'))
90 c.closed = str2bool(request.GET.get('closed'))
90 c.closed = str2bool(request.GET.get('closed'))
91 c.my = str2bool(request.GET.get('my'))
91 c.my = str2bool(request.GET.get('my'))
92 c.awaiting_review = str2bool(request.GET.get('awaiting_review'))
92 c.awaiting_review = str2bool(request.GET.get('awaiting_review'))
93 c.awaiting_my_review = str2bool(request.GET.get('awaiting_my_review'))
93 c.awaiting_my_review = str2bool(request.GET.get('awaiting_my_review'))
94 c.repo_name = repo_name
94 c.repo_name = repo_name
95
95
96 opened_by = None
96 opened_by = None
97 if c.my:
97 if c.my:
98 c.active = 'my'
98 c.active = 'my'
99 opened_by = [c.rhodecode_user.user_id]
99 opened_by = [c.rhodecode_user.user_id]
100
100
101 statuses = [PullRequest.STATUS_NEW, PullRequest.STATUS_OPEN]
101 statuses = [PullRequest.STATUS_NEW, PullRequest.STATUS_OPEN]
102 if c.closed:
102 if c.closed:
103 c.active = 'closed'
103 c.active = 'closed'
104 statuses = [PullRequest.STATUS_CLOSED]
104 statuses = [PullRequest.STATUS_CLOSED]
105
105
106 if c.awaiting_review and not c.source:
106 if c.awaiting_review and not c.source:
107 c.active = 'awaiting'
107 c.active = 'awaiting'
108 if c.source and not c.awaiting_review:
108 if c.source and not c.awaiting_review:
109 c.active = 'source'
109 c.active = 'source'
110 if c.awaiting_my_review:
110 if c.awaiting_my_review:
111 c.active = 'awaiting_my'
111 c.active = 'awaiting_my'
112
112
113 data = self._get_pull_requests_list(
113 data = self._get_pull_requests_list(
114 repo_name=repo_name, opened_by=opened_by, statuses=statuses)
114 repo_name=repo_name, opened_by=opened_by, statuses=statuses)
115 if not request.is_xhr:
115 if not request.is_xhr:
116 c.data = json.dumps(data['data'])
116 c.data = json.dumps(data['data'])
117 c.records_total = data['recordsTotal']
117 c.records_total = data['recordsTotal']
118 return render('/pullrequests/pullrequests.mako')
118 return render('/pullrequests/pullrequests.mako')
119 else:
119 else:
120 return json.dumps(data)
120 return json.dumps(data)
121
121
122 def _get_pull_requests_list(self, repo_name, opened_by, statuses):
122 def _get_pull_requests_list(self, repo_name, opened_by, statuses):
123 # pagination
123 # pagination
124 start = safe_int(request.GET.get('start'), 0)
124 start = safe_int(request.GET.get('start'), 0)
125 length = safe_int(request.GET.get('length'), c.visual.dashboard_items)
125 length = safe_int(request.GET.get('length'), c.visual.dashboard_items)
126 order_by, order_dir = self._extract_ordering(request)
126 order_by, order_dir = self._extract_ordering(request)
127
127
128 if c.awaiting_review:
128 if c.awaiting_review:
129 pull_requests = PullRequestModel().get_awaiting_review(
129 pull_requests = PullRequestModel().get_awaiting_review(
130 repo_name, source=c.source, opened_by=opened_by,
130 repo_name, source=c.source, opened_by=opened_by,
131 statuses=statuses, offset=start, length=length,
131 statuses=statuses, offset=start, length=length,
132 order_by=order_by, order_dir=order_dir)
132 order_by=order_by, order_dir=order_dir)
133 pull_requests_total_count = PullRequestModel(
133 pull_requests_total_count = PullRequestModel(
134 ).count_awaiting_review(
134 ).count_awaiting_review(
135 repo_name, source=c.source, statuses=statuses,
135 repo_name, source=c.source, statuses=statuses,
136 opened_by=opened_by)
136 opened_by=opened_by)
137 elif c.awaiting_my_review:
137 elif c.awaiting_my_review:
138 pull_requests = PullRequestModel().get_awaiting_my_review(
138 pull_requests = PullRequestModel().get_awaiting_my_review(
139 repo_name, source=c.source, opened_by=opened_by,
139 repo_name, source=c.source, opened_by=opened_by,
140 user_id=c.rhodecode_user.user_id, statuses=statuses,
140 user_id=c.rhodecode_user.user_id, statuses=statuses,
141 offset=start, length=length, order_by=order_by,
141 offset=start, length=length, order_by=order_by,
142 order_dir=order_dir)
142 order_dir=order_dir)
143 pull_requests_total_count = PullRequestModel(
143 pull_requests_total_count = PullRequestModel(
144 ).count_awaiting_my_review(
144 ).count_awaiting_my_review(
145 repo_name, source=c.source, user_id=c.rhodecode_user.user_id,
145 repo_name, source=c.source, user_id=c.rhodecode_user.user_id,
146 statuses=statuses, opened_by=opened_by)
146 statuses=statuses, opened_by=opened_by)
147 else:
147 else:
148 pull_requests = PullRequestModel().get_all(
148 pull_requests = PullRequestModel().get_all(
149 repo_name, source=c.source, opened_by=opened_by,
149 repo_name, source=c.source, opened_by=opened_by,
150 statuses=statuses, offset=start, length=length,
150 statuses=statuses, offset=start, length=length,
151 order_by=order_by, order_dir=order_dir)
151 order_by=order_by, order_dir=order_dir)
152 pull_requests_total_count = PullRequestModel().count_all(
152 pull_requests_total_count = PullRequestModel().count_all(
153 repo_name, source=c.source, statuses=statuses,
153 repo_name, source=c.source, statuses=statuses,
154 opened_by=opened_by)
154 opened_by=opened_by)
155
155
156 from rhodecode.lib.utils import PartialRenderer
156 from rhodecode.lib.utils import PartialRenderer
157 _render = PartialRenderer('data_table/_dt_elements.mako')
157 _render = PartialRenderer('data_table/_dt_elements.mako')
158 data = []
158 data = []
159 for pr in pull_requests:
159 for pr in pull_requests:
160 comments = CommentsModel().get_all_comments(
160 comments = CommentsModel().get_all_comments(
161 c.rhodecode_db_repo.repo_id, pull_request=pr)
161 c.rhodecode_db_repo.repo_id, pull_request=pr)
162
162
163 data.append({
163 data.append({
164 'name': _render('pullrequest_name',
164 'name': _render('pullrequest_name',
165 pr.pull_request_id, pr.target_repo.repo_name),
165 pr.pull_request_id, pr.target_repo.repo_name),
166 'name_raw': pr.pull_request_id,
166 'name_raw': pr.pull_request_id,
167 'status': _render('pullrequest_status',
167 'status': _render('pullrequest_status',
168 pr.calculated_review_status()),
168 pr.calculated_review_status()),
169 'title': _render(
169 'title': _render(
170 'pullrequest_title', pr.title, pr.description),
170 'pullrequest_title', pr.title, pr.description),
171 'description': h.escape(pr.description),
171 'description': h.escape(pr.description),
172 'updated_on': _render('pullrequest_updated_on',
172 'updated_on': _render('pullrequest_updated_on',
173 h.datetime_to_time(pr.updated_on)),
173 h.datetime_to_time(pr.updated_on)),
174 'updated_on_raw': h.datetime_to_time(pr.updated_on),
174 'updated_on_raw': h.datetime_to_time(pr.updated_on),
175 'created_on': _render('pullrequest_updated_on',
175 'created_on': _render('pullrequest_updated_on',
176 h.datetime_to_time(pr.created_on)),
176 h.datetime_to_time(pr.created_on)),
177 'created_on_raw': h.datetime_to_time(pr.created_on),
177 'created_on_raw': h.datetime_to_time(pr.created_on),
178 'author': _render('pullrequest_author',
178 'author': _render('pullrequest_author',
179 pr.author.full_contact, ),
179 pr.author.full_contact, ),
180 'author_raw': pr.author.full_name,
180 'author_raw': pr.author.full_name,
181 'comments': _render('pullrequest_comments', len(comments)),
181 'comments': _render('pullrequest_comments', len(comments)),
182 'comments_raw': len(comments),
182 'comments_raw': len(comments),
183 'closed': pr.is_closed(),
183 'closed': pr.is_closed(),
184 })
184 })
185 # json used to render the grid
185 # json used to render the grid
186 data = ({
186 data = ({
187 'data': data,
187 'data': data,
188 'recordsTotal': pull_requests_total_count,
188 'recordsTotal': pull_requests_total_count,
189 'recordsFiltered': pull_requests_total_count,
189 'recordsFiltered': pull_requests_total_count,
190 })
190 })
191 return data
191 return data
192
192
193 @LoginRequired()
193 @LoginRequired()
194 @NotAnonymous()
194 @NotAnonymous()
195 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
195 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
196 'repository.admin')
196 'repository.admin')
197 @HasAcceptedRepoType('git', 'hg')
197 @HasAcceptedRepoType('git', 'hg')
198 def index(self):
198 def index(self):
199 source_repo = c.rhodecode_db_repo
199 source_repo = c.rhodecode_db_repo
200
200
201 try:
201 try:
202 source_repo.scm_instance().get_commit()
202 source_repo.scm_instance().get_commit()
203 except EmptyRepositoryError:
203 except EmptyRepositoryError:
204 h.flash(h.literal(_('There are no commits yet')),
204 h.flash(h.literal(_('There are no commits yet')),
205 category='warning')
205 category='warning')
206 redirect(url('summary_home', repo_name=source_repo.repo_name))
206 redirect(url('summary_home', repo_name=source_repo.repo_name))
207
207
208 commit_id = request.GET.get('commit')
208 commit_id = request.GET.get('commit')
209 branch_ref = request.GET.get('branch')
209 branch_ref = request.GET.get('branch')
210 bookmark_ref = request.GET.get('bookmark')
210 bookmark_ref = request.GET.get('bookmark')
211
211
212 try:
212 try:
213 source_repo_data = PullRequestModel().generate_repo_data(
213 source_repo_data = PullRequestModel().generate_repo_data(
214 source_repo, commit_id=commit_id,
214 source_repo, commit_id=commit_id,
215 branch=branch_ref, bookmark=bookmark_ref)
215 branch=branch_ref, bookmark=bookmark_ref)
216 except CommitDoesNotExistError as e:
216 except CommitDoesNotExistError as e:
217 log.exception(e)
217 log.exception(e)
218 h.flash(_('Commit does not exist'), 'error')
218 h.flash(_('Commit does not exist'), 'error')
219 redirect(url('pullrequest_home', repo_name=source_repo.repo_name))
219 redirect(url('pullrequest_home', repo_name=source_repo.repo_name))
220
220
221 default_target_repo = source_repo
221 default_target_repo = source_repo
222
222
223 if source_repo.parent:
223 if source_repo.parent:
224 parent_vcs_obj = source_repo.parent.scm_instance()
224 parent_vcs_obj = source_repo.parent.scm_instance()
225 if parent_vcs_obj and not parent_vcs_obj.is_empty():
225 if parent_vcs_obj and not parent_vcs_obj.is_empty():
226 # change default if we have a parent repo
226 # change default if we have a parent repo
227 default_target_repo = source_repo.parent
227 default_target_repo = source_repo.parent
228
228
229 target_repo_data = PullRequestModel().generate_repo_data(
229 target_repo_data = PullRequestModel().generate_repo_data(
230 default_target_repo)
230 default_target_repo)
231
231
232 selected_source_ref = source_repo_data['refs']['selected_ref']
232 selected_source_ref = source_repo_data['refs']['selected_ref']
233
233
234 title_source_ref = selected_source_ref.split(':', 2)[1]
234 title_source_ref = selected_source_ref.split(':', 2)[1]
235 c.default_title = PullRequestModel().generate_pullrequest_title(
235 c.default_title = PullRequestModel().generate_pullrequest_title(
236 source=source_repo.repo_name,
236 source=source_repo.repo_name,
237 source_ref=title_source_ref,
237 source_ref=title_source_ref,
238 target=default_target_repo.repo_name
238 target=default_target_repo.repo_name
239 )
239 )
240
240
241 c.default_repo_data = {
241 c.default_repo_data = {
242 'source_repo_name': source_repo.repo_name,
242 'source_repo_name': source_repo.repo_name,
243 'source_refs_json': json.dumps(source_repo_data),
243 'source_refs_json': json.dumps(source_repo_data),
244 'target_repo_name': default_target_repo.repo_name,
244 'target_repo_name': default_target_repo.repo_name,
245 'target_refs_json': json.dumps(target_repo_data),
245 'target_refs_json': json.dumps(target_repo_data),
246 }
246 }
247 c.default_source_ref = selected_source_ref
247 c.default_source_ref = selected_source_ref
248
248
249 return render('/pullrequests/pullrequest.mako')
249 return render('/pullrequests/pullrequest.mako')
250
250
251 @LoginRequired()
251 @LoginRequired()
252 @NotAnonymous()
252 @NotAnonymous()
253 @XHRRequired()
253 @XHRRequired()
254 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
254 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
255 'repository.admin')
255 'repository.admin')
256 @jsonify
256 @jsonify
257 def get_repo_refs(self, repo_name, target_repo_name):
257 def get_repo_refs(self, repo_name, target_repo_name):
258 repo = Repository.get_by_repo_name(target_repo_name)
258 repo = Repository.get_by_repo_name(target_repo_name)
259 if not repo:
259 if not repo:
260 raise HTTPNotFound
260 raise HTTPNotFound
261 return PullRequestModel().generate_repo_data(repo)
261 return PullRequestModel().generate_repo_data(repo)
262
262
263 @LoginRequired()
263 @LoginRequired()
264 @NotAnonymous()
264 @NotAnonymous()
265 @XHRRequired()
265 @XHRRequired()
266 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
266 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
267 'repository.admin')
267 'repository.admin')
268 @jsonify
268 @jsonify
269 def get_repo_destinations(self, repo_name):
269 def get_repo_destinations(self, repo_name):
270 repo = Repository.get_by_repo_name(repo_name)
270 repo = Repository.get_by_repo_name(repo_name)
271 if not repo:
271 if not repo:
272 raise HTTPNotFound
272 raise HTTPNotFound
273 filter_query = request.GET.get('query')
273 filter_query = request.GET.get('query')
274
274
275 query = Repository.query() \
275 query = Repository.query() \
276 .order_by(func.length(Repository.repo_name)) \
276 .order_by(func.length(Repository.repo_name)) \
277 .filter(or_(
277 .filter(or_(
278 Repository.repo_name == repo.repo_name,
278 Repository.repo_name == repo.repo_name,
279 Repository.fork_id == repo.repo_id))
279 Repository.fork_id == repo.repo_id))
280
280
281 if filter_query:
281 if filter_query:
282 ilike_expression = u'%{}%'.format(safe_unicode(filter_query))
282 ilike_expression = u'%{}%'.format(safe_unicode(filter_query))
283 query = query.filter(
283 query = query.filter(
284 Repository.repo_name.ilike(ilike_expression))
284 Repository.repo_name.ilike(ilike_expression))
285
285
286 add_parent = False
286 add_parent = False
287 if repo.parent:
287 if repo.parent:
288 if filter_query in repo.parent.repo_name:
288 if filter_query in repo.parent.repo_name:
289 parent_vcs_obj = repo.parent.scm_instance()
289 parent_vcs_obj = repo.parent.scm_instance()
290 if parent_vcs_obj and not parent_vcs_obj.is_empty():
290 if parent_vcs_obj and not parent_vcs_obj.is_empty():
291 add_parent = True
291 add_parent = True
292
292
293 limit = 20 - 1 if add_parent else 20
293 limit = 20 - 1 if add_parent else 20
294 all_repos = query.limit(limit).all()
294 all_repos = query.limit(limit).all()
295 if add_parent:
295 if add_parent:
296 all_repos += [repo.parent]
296 all_repos += [repo.parent]
297
297
298 repos = []
298 repos = []
299 for obj in self.scm_model.get_repos(all_repos):
299 for obj in self.scm_model.get_repos(all_repos):
300 repos.append({
300 repos.append({
301 'id': obj['name'],
301 'id': obj['name'],
302 'text': obj['name'],
302 'text': obj['name'],
303 'type': 'repo',
303 'type': 'repo',
304 'obj': obj['dbrepo']
304 'obj': obj['dbrepo']
305 })
305 })
306
306
307 data = {
307 data = {
308 'more': False,
308 'more': False,
309 'results': [{
309 'results': [{
310 'text': _('Repositories'),
310 'text': _('Repositories'),
311 'children': repos
311 'children': repos
312 }] if repos else []
312 }] if repos else []
313 }
313 }
314 return data
314 return data
315
315
316 @LoginRequired()
316 @LoginRequired()
317 @NotAnonymous()
317 @NotAnonymous()
318 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
318 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
319 'repository.admin')
319 'repository.admin')
320 @HasAcceptedRepoType('git', 'hg')
320 @HasAcceptedRepoType('git', 'hg')
321 @auth.CSRFRequired()
321 @auth.CSRFRequired()
322 def create(self, repo_name):
322 def create(self, repo_name):
323 repo = Repository.get_by_repo_name(repo_name)
323 repo = Repository.get_by_repo_name(repo_name)
324 if not repo:
324 if not repo:
325 raise HTTPNotFound
325 raise HTTPNotFound
326
326
327 controls = peppercorn.parse(request.POST.items())
327 controls = peppercorn.parse(request.POST.items())
328
328
329 try:
329 try:
330 _form = PullRequestForm(repo.repo_id)().to_python(controls)
330 _form = PullRequestForm(repo.repo_id)().to_python(controls)
331 except formencode.Invalid as errors:
331 except formencode.Invalid as errors:
332 if errors.error_dict.get('revisions'):
332 if errors.error_dict.get('revisions'):
333 msg = 'Revisions: %s' % errors.error_dict['revisions']
333 msg = 'Revisions: %s' % errors.error_dict['revisions']
334 elif errors.error_dict.get('pullrequest_title'):
334 elif errors.error_dict.get('pullrequest_title'):
335 msg = _('Pull request requires a title with min. 3 chars')
335 msg = _('Pull request requires a title with min. 3 chars')
336 else:
336 else:
337 msg = _('Error creating pull request: {}').format(errors)
337 msg = _('Error creating pull request: {}').format(errors)
338 log.exception(msg)
338 log.exception(msg)
339 h.flash(msg, 'error')
339 h.flash(msg, 'error')
340
340
341 # would rather just go back to form ...
341 # would rather just go back to form ...
342 return redirect(url('pullrequest_home', repo_name=repo_name))
342 return redirect(url('pullrequest_home', repo_name=repo_name))
343
343
344 source_repo = _form['source_repo']
344 source_repo = _form['source_repo']
345 source_ref = _form['source_ref']
345 source_ref = _form['source_ref']
346 target_repo = _form['target_repo']
346 target_repo = _form['target_repo']
347 target_ref = _form['target_ref']
347 target_ref = _form['target_ref']
348 commit_ids = _form['revisions'][::-1]
348 commit_ids = _form['revisions'][::-1]
349 reviewers = [
349 reviewers = [
350 (r['user_id'], r['reasons']) for r in _form['review_members']]
350 (r['user_id'], r['reasons']) for r in _form['review_members']]
351
351
352 # find the ancestor for this pr
352 # find the ancestor for this pr
353 source_db_repo = Repository.get_by_repo_name(_form['source_repo'])
353 source_db_repo = Repository.get_by_repo_name(_form['source_repo'])
354 target_db_repo = Repository.get_by_repo_name(_form['target_repo'])
354 target_db_repo = Repository.get_by_repo_name(_form['target_repo'])
355
355
356 source_scm = source_db_repo.scm_instance()
356 source_scm = source_db_repo.scm_instance()
357 target_scm = target_db_repo.scm_instance()
357 target_scm = target_db_repo.scm_instance()
358
358
359 source_commit = source_scm.get_commit(source_ref.split(':')[-1])
359 source_commit = source_scm.get_commit(source_ref.split(':')[-1])
360 target_commit = target_scm.get_commit(target_ref.split(':')[-1])
360 target_commit = target_scm.get_commit(target_ref.split(':')[-1])
361
361
362 ancestor = source_scm.get_common_ancestor(
362 ancestor = source_scm.get_common_ancestor(
363 source_commit.raw_id, target_commit.raw_id, target_scm)
363 source_commit.raw_id, target_commit.raw_id, target_scm)
364
364
365 target_ref_type, target_ref_name, __ = _form['target_ref'].split(':')
365 target_ref_type, target_ref_name, __ = _form['target_ref'].split(':')
366 target_ref = ':'.join((target_ref_type, target_ref_name, ancestor))
366 target_ref = ':'.join((target_ref_type, target_ref_name, ancestor))
367
367
368 pullrequest_title = _form['pullrequest_title']
368 pullrequest_title = _form['pullrequest_title']
369 title_source_ref = source_ref.split(':', 2)[1]
369 title_source_ref = source_ref.split(':', 2)[1]
370 if not pullrequest_title:
370 if not pullrequest_title:
371 pullrequest_title = PullRequestModel().generate_pullrequest_title(
371 pullrequest_title = PullRequestModel().generate_pullrequest_title(
372 source=source_repo,
372 source=source_repo,
373 source_ref=title_source_ref,
373 source_ref=title_source_ref,
374 target=target_repo
374 target=target_repo
375 )
375 )
376
376
377 description = _form['pullrequest_desc']
377 description = _form['pullrequest_desc']
378 try:
378 try:
379 pull_request = PullRequestModel().create(
379 pull_request = PullRequestModel().create(
380 c.rhodecode_user.user_id, source_repo, source_ref, target_repo,
380 c.rhodecode_user.user_id, source_repo, source_ref, target_repo,
381 target_ref, commit_ids, reviewers, pullrequest_title,
381 target_ref, commit_ids, reviewers, pullrequest_title,
382 description
382 description
383 )
383 )
384 Session().commit()
384 Session().commit()
385 h.flash(_('Successfully opened new pull request'),
385 h.flash(_('Successfully opened new pull request'),
386 category='success')
386 category='success')
387 except Exception as e:
387 except Exception as e:
388 msg = _('Error occurred during sending pull request')
388 msg = _('Error occurred during sending pull request')
389 log.exception(msg)
389 log.exception(msg)
390 h.flash(msg, category='error')
390 h.flash(msg, category='error')
391 return redirect(url('pullrequest_home', repo_name=repo_name))
391 return redirect(url('pullrequest_home', repo_name=repo_name))
392
392
393 return redirect(url('pullrequest_show', repo_name=target_repo,
393 return redirect(url('pullrequest_show', repo_name=target_repo,
394 pull_request_id=pull_request.pull_request_id))
394 pull_request_id=pull_request.pull_request_id))
395
395
396 @LoginRequired()
396 @LoginRequired()
397 @NotAnonymous()
397 @NotAnonymous()
398 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
398 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
399 'repository.admin')
399 'repository.admin')
400 @auth.CSRFRequired()
400 @auth.CSRFRequired()
401 @jsonify
401 @jsonify
402 def update(self, repo_name, pull_request_id):
402 def update(self, repo_name, pull_request_id):
403 pull_request_id = safe_int(pull_request_id)
403 pull_request_id = safe_int(pull_request_id)
404 pull_request = PullRequest.get_or_404(pull_request_id)
404 pull_request = PullRequest.get_or_404(pull_request_id)
405 # only owner or admin can update it
405 # only owner or admin can update it
406 allowed_to_update = PullRequestModel().check_user_update(
406 allowed_to_update = PullRequestModel().check_user_update(
407 pull_request, c.rhodecode_user)
407 pull_request, c.rhodecode_user)
408 if allowed_to_update:
408 if allowed_to_update:
409 controls = peppercorn.parse(request.POST.items())
409 controls = peppercorn.parse(request.POST.items())
410
410
411 if 'review_members' in controls:
411 if 'review_members' in controls:
412 self._update_reviewers(
412 self._update_reviewers(
413 pull_request_id, controls['review_members'])
413 pull_request_id, controls['review_members'])
414 elif str2bool(request.POST.get('update_commits', 'false')):
414 elif str2bool(request.POST.get('update_commits', 'false')):
415 self._update_commits(pull_request)
415 self._update_commits(pull_request)
416 elif str2bool(request.POST.get('close_pull_request', 'false')):
416 elif str2bool(request.POST.get('close_pull_request', 'false')):
417 self._reject_close(pull_request)
417 self._reject_close(pull_request)
418 elif str2bool(request.POST.get('edit_pull_request', 'false')):
418 elif str2bool(request.POST.get('edit_pull_request', 'false')):
419 self._edit_pull_request(pull_request)
419 self._edit_pull_request(pull_request)
420 else:
420 else:
421 raise HTTPBadRequest()
421 raise HTTPBadRequest()
422 return True
422 return True
423 raise HTTPForbidden()
423 raise HTTPForbidden()
424
424
425 def _edit_pull_request(self, pull_request):
425 def _edit_pull_request(self, pull_request):
426 try:
426 try:
427 PullRequestModel().edit(
427 PullRequestModel().edit(
428 pull_request, request.POST.get('title'),
428 pull_request, request.POST.get('title'),
429 request.POST.get('description'))
429 request.POST.get('description'))
430 except ValueError:
430 except ValueError:
431 msg = _(u'Cannot update closed pull requests.')
431 msg = _(u'Cannot update closed pull requests.')
432 h.flash(msg, category='error')
432 h.flash(msg, category='error')
433 return
433 return
434 else:
434 else:
435 Session().commit()
435 Session().commit()
436
436
437 msg = _(u'Pull request title & description updated.')
437 msg = _(u'Pull request title & description updated.')
438 h.flash(msg, category='success')
438 h.flash(msg, category='success')
439 return
439 return
440
440
441 def _update_commits(self, pull_request):
441 def _update_commits(self, pull_request):
442 resp = PullRequestModel().update_commits(pull_request)
442 resp = PullRequestModel().update_commits(pull_request)
443
443
444 if resp.executed:
444 if resp.executed:
445
445
446 if resp.target_changed and resp.source_changed:
446 if resp.target_changed and resp.source_changed:
447 changed = 'target and source repositories'
447 changed = 'target and source repositories'
448 elif resp.target_changed and not resp.source_changed:
448 elif resp.target_changed and not resp.source_changed:
449 changed = 'target repository'
449 changed = 'target repository'
450 elif not resp.target_changed and resp.source_changed:
450 elif not resp.target_changed and resp.source_changed:
451 changed = 'source repository'
451 changed = 'source repository'
452 else:
452 else:
453 changed = 'nothing'
453 changed = 'nothing'
454
454
455 msg = _(
455 msg = _(
456 u'Pull request updated to "{source_commit_id}" with '
456 u'Pull request updated to "{source_commit_id}" with '
457 u'{count_added} added, {count_removed} removed commits. '
457 u'{count_added} added, {count_removed} removed commits. '
458 u'Source of changes: {change_source}')
458 u'Source of changes: {change_source}')
459 msg = msg.format(
459 msg = msg.format(
460 source_commit_id=pull_request.source_ref_parts.commit_id,
460 source_commit_id=pull_request.source_ref_parts.commit_id,
461 count_added=len(resp.changes.added),
461 count_added=len(resp.changes.added),
462 count_removed=len(resp.changes.removed),
462 count_removed=len(resp.changes.removed),
463 change_source=changed)
463 change_source=changed)
464 h.flash(msg, category='success')
464 h.flash(msg, category='success')
465
465
466 registry = get_current_registry()
466 registry = get_current_registry()
467 rhodecode_plugins = getattr(registry, 'rhodecode_plugins', {})
467 rhodecode_plugins = getattr(registry, 'rhodecode_plugins', {})
468 channelstream_config = rhodecode_plugins.get('channelstream', {})
468 channelstream_config = rhodecode_plugins.get('channelstream', {})
469 if channelstream_config.get('enabled'):
469 if channelstream_config.get('enabled'):
470 message = msg + (
470 message = msg + (
471 ' - <a onclick="window.location.reload()">'
471 ' - <a onclick="window.location.reload()">'
472 '<strong>{}</strong></a>'.format(_('Reload page')))
472 '<strong>{}</strong></a>'.format(_('Reload page')))
473 channel = '/repo${}$/pr/{}'.format(
473 channel = '/repo${}$/pr/{}'.format(
474 pull_request.target_repo.repo_name,
474 pull_request.target_repo.repo_name,
475 pull_request.pull_request_id
475 pull_request.pull_request_id
476 )
476 )
477 payload = {
477 payload = {
478 'type': 'message',
478 'type': 'message',
479 'user': 'system',
479 'user': 'system',
480 'exclude_users': [request.user.username],
480 'exclude_users': [request.user.username],
481 'channel': channel,
481 'channel': channel,
482 'message': {
482 'message': {
483 'message': message,
483 'message': message,
484 'level': 'success',
484 'level': 'success',
485 'topic': '/notifications'
485 'topic': '/notifications'
486 }
486 }
487 }
487 }
488 channelstream_request(
488 channelstream_request(
489 channelstream_config, [payload], '/message',
489 channelstream_config, [payload], '/message',
490 raise_exc=False)
490 raise_exc=False)
491 else:
491 else:
492 msg = PullRequestModel.UPDATE_STATUS_MESSAGES[resp.reason]
492 msg = PullRequestModel.UPDATE_STATUS_MESSAGES[resp.reason]
493 warning_reasons = [
493 warning_reasons = [
494 UpdateFailureReason.NO_CHANGE,
494 UpdateFailureReason.NO_CHANGE,
495 UpdateFailureReason.WRONG_REF_TPYE,
495 UpdateFailureReason.WRONG_REF_TPYE,
496 ]
496 ]
497 category = 'warning' if resp.reason in warning_reasons else 'error'
497 category = 'warning' if resp.reason in warning_reasons else 'error'
498 h.flash(msg, category=category)
498 h.flash(msg, category=category)
499
499
500 @auth.CSRFRequired()
500 @auth.CSRFRequired()
501 @LoginRequired()
501 @LoginRequired()
502 @NotAnonymous()
502 @NotAnonymous()
503 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
503 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
504 'repository.admin')
504 'repository.admin')
505 def merge(self, repo_name, pull_request_id):
505 def merge(self, repo_name, pull_request_id):
506 """
506 """
507 POST /{repo_name}/pull-request/{pull_request_id}
507 POST /{repo_name}/pull-request/{pull_request_id}
508
508
509 Merge will perform a server-side merge of the specified
509 Merge will perform a server-side merge of the specified
510 pull request, if the pull request is approved and mergeable.
510 pull request, if the pull request is approved and mergeable.
511 After successful merging, the pull request is automatically
511 After successful merging, the pull request is automatically
512 closed, with a relevant comment.
512 closed, with a relevant comment.
513 """
513 """
514 pull_request_id = safe_int(pull_request_id)
514 pull_request_id = safe_int(pull_request_id)
515 pull_request = PullRequest.get_or_404(pull_request_id)
515 pull_request = PullRequest.get_or_404(pull_request_id)
516 user = c.rhodecode_user
516 user = c.rhodecode_user
517
517
518 check = MergeCheck.validate(pull_request, user)
518 check = MergeCheck.validate(pull_request, user)
519 merge_possible = not check.failed
519 merge_possible = not check.failed
520
520
521 for err_type, error_msg in check.errors:
521 for err_type, error_msg in check.errors:
522 h.flash(error_msg, category=err_type)
522 h.flash(error_msg, category=err_type)
523
523
524 if merge_possible:
524 if merge_possible:
525 log.debug("Pre-conditions checked, trying to merge.")
525 log.debug("Pre-conditions checked, trying to merge.")
526 extras = vcs_operation_context(
526 extras = vcs_operation_context(
527 request.environ, repo_name=pull_request.target_repo.repo_name,
527 request.environ, repo_name=pull_request.target_repo.repo_name,
528 username=user.username, action='push',
528 username=user.username, action='push',
529 scm=pull_request.target_repo.repo_type)
529 scm=pull_request.target_repo.repo_type)
530 self._merge_pull_request(pull_request, user, extras)
530 self._merge_pull_request(pull_request, user, extras)
531
531
532 return redirect(url(
532 return redirect(url(
533 'pullrequest_show',
533 'pullrequest_show',
534 repo_name=pull_request.target_repo.repo_name,
534 repo_name=pull_request.target_repo.repo_name,
535 pull_request_id=pull_request.pull_request_id))
535 pull_request_id=pull_request.pull_request_id))
536
536
537 def _merge_pull_request(self, pull_request, user, extras):
537 def _merge_pull_request(self, pull_request, user, extras):
538 merge_resp = PullRequestModel().merge(
538 merge_resp = PullRequestModel().merge(
539 pull_request, user, extras=extras)
539 pull_request, user, extras=extras)
540
540
541 if merge_resp.executed:
541 if merge_resp.executed:
542 log.debug("The merge was successful, closing the pull request.")
542 log.debug("The merge was successful, closing the pull request.")
543 PullRequestModel().close_pull_request(
543 PullRequestModel().close_pull_request(
544 pull_request.pull_request_id, user)
544 pull_request.pull_request_id, user)
545 Session().commit()
545 Session().commit()
546 msg = _('Pull request was successfully merged and closed.')
546 msg = _('Pull request was successfully merged and closed.')
547 h.flash(msg, category='success')
547 h.flash(msg, category='success')
548 else:
548 else:
549 log.debug(
549 log.debug(
550 "The merge was not successful. Merge response: %s",
550 "The merge was not successful. Merge response: %s",
551 merge_resp)
551 merge_resp)
552 msg = PullRequestModel().merge_status_message(
552 msg = PullRequestModel().merge_status_message(
553 merge_resp.failure_reason)
553 merge_resp.failure_reason)
554 h.flash(msg, category='error')
554 h.flash(msg, category='error')
555
555
556 def _update_reviewers(self, pull_request_id, review_members):
556 def _update_reviewers(self, pull_request_id, review_members):
557 reviewers = [
557 reviewers = [
558 (int(r['user_id']), r['reasons']) for r in review_members]
558 (int(r['user_id']), r['reasons']) for r in review_members]
559 PullRequestModel().update_reviewers(pull_request_id, reviewers)
559 PullRequestModel().update_reviewers(pull_request_id, reviewers)
560 Session().commit()
560 Session().commit()
561
561
562 def _reject_close(self, pull_request):
562 def _reject_close(self, pull_request):
563 if pull_request.is_closed():
563 if pull_request.is_closed():
564 raise HTTPForbidden()
564 raise HTTPForbidden()
565
565
566 PullRequestModel().close_pull_request_with_comment(
566 PullRequestModel().close_pull_request_with_comment(
567 pull_request, c.rhodecode_user, c.rhodecode_db_repo)
567 pull_request, c.rhodecode_user, c.rhodecode_db_repo)
568 Session().commit()
568 Session().commit()
569
569
570 @LoginRequired()
570 @LoginRequired()
571 @NotAnonymous()
571 @NotAnonymous()
572 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
572 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
573 'repository.admin')
573 'repository.admin')
574 @auth.CSRFRequired()
574 @auth.CSRFRequired()
575 @jsonify
575 @jsonify
576 def delete(self, repo_name, pull_request_id):
576 def delete(self, repo_name, pull_request_id):
577 pull_request_id = safe_int(pull_request_id)
577 pull_request_id = safe_int(pull_request_id)
578 pull_request = PullRequest.get_or_404(pull_request_id)
578 pull_request = PullRequest.get_or_404(pull_request_id)
579
580 pr_closed = pull_request.is_closed()
581 allowed_to_delete = PullRequestModel().check_user_delete(
582 pull_request, c.rhodecode_user) and not pr_closed
583
579 # only owner can delete it !
584 # only owner can delete it !
580 if pull_request.author.user_id == c.rhodecode_user.user_id:
585 if allowed_to_delete:
581 PullRequestModel().delete(pull_request)
586 PullRequestModel().delete(pull_request)
582 Session().commit()
587 Session().commit()
583 h.flash(_('Successfully deleted pull request'),
588 h.flash(_('Successfully deleted pull request'),
584 category='success')
589 category='success')
585 return redirect(url('my_account_pullrequests'))
590 return redirect(url('my_account_pullrequests'))
591
592 h.flash(_('Your are not allowed to delete this pull request'),
593 category='error')
586 raise HTTPForbidden()
594 raise HTTPForbidden()
587
595
588 def _get_pr_version(self, pull_request_id, version=None):
596 def _get_pr_version(self, pull_request_id, version=None):
589 pull_request_id = safe_int(pull_request_id)
597 pull_request_id = safe_int(pull_request_id)
590 at_version = None
598 at_version = None
591
599
592 if version and version == 'latest':
600 if version and version == 'latest':
593 pull_request_ver = PullRequest.get(pull_request_id)
601 pull_request_ver = PullRequest.get(pull_request_id)
594 pull_request_obj = pull_request_ver
602 pull_request_obj = pull_request_ver
595 _org_pull_request_obj = pull_request_obj
603 _org_pull_request_obj = pull_request_obj
596 at_version = 'latest'
604 at_version = 'latest'
597 elif version:
605 elif version:
598 pull_request_ver = PullRequestVersion.get_or_404(version)
606 pull_request_ver = PullRequestVersion.get_or_404(version)
599 pull_request_obj = pull_request_ver
607 pull_request_obj = pull_request_ver
600 _org_pull_request_obj = pull_request_ver.pull_request
608 _org_pull_request_obj = pull_request_ver.pull_request
601 at_version = pull_request_ver.pull_request_version_id
609 at_version = pull_request_ver.pull_request_version_id
602 else:
610 else:
603 _org_pull_request_obj = pull_request_obj = PullRequest.get_or_404(pull_request_id)
611 _org_pull_request_obj = pull_request_obj = PullRequest.get_or_404(pull_request_id)
604
612
605 pull_request_display_obj = PullRequest.get_pr_display_object(
613 pull_request_display_obj = PullRequest.get_pr_display_object(
606 pull_request_obj, _org_pull_request_obj)
614 pull_request_obj, _org_pull_request_obj)
607
615
608 return _org_pull_request_obj, pull_request_obj, \
616 return _org_pull_request_obj, pull_request_obj, \
609 pull_request_display_obj, at_version
617 pull_request_display_obj, at_version
610
618
611 def _get_diffset(
619 def _get_diffset(
612 self, source_repo, source_ref_id, target_ref_id, target_commit,
620 self, source_repo, source_ref_id, target_ref_id, target_commit,
613 source_commit, diff_limit, file_limit, display_inline_comments):
621 source_commit, diff_limit, file_limit, display_inline_comments):
614 vcs_diff = PullRequestModel().get_diff(
622 vcs_diff = PullRequestModel().get_diff(
615 source_repo, source_ref_id, target_ref_id)
623 source_repo, source_ref_id, target_ref_id)
616
624
617 diff_processor = diffs.DiffProcessor(
625 diff_processor = diffs.DiffProcessor(
618 vcs_diff, format='newdiff', diff_limit=diff_limit,
626 vcs_diff, format='newdiff', diff_limit=diff_limit,
619 file_limit=file_limit, show_full_diff=c.fulldiff)
627 file_limit=file_limit, show_full_diff=c.fulldiff)
620
628
621 _parsed = diff_processor.prepare()
629 _parsed = diff_processor.prepare()
622
630
623 def _node_getter(commit):
631 def _node_getter(commit):
624 def get_node(fname):
632 def get_node(fname):
625 try:
633 try:
626 return commit.get_node(fname)
634 return commit.get_node(fname)
627 except NodeDoesNotExistError:
635 except NodeDoesNotExistError:
628 return None
636 return None
629
637
630 return get_node
638 return get_node
631
639
632 diffset = codeblocks.DiffSet(
640 diffset = codeblocks.DiffSet(
633 repo_name=c.repo_name,
641 repo_name=c.repo_name,
634 source_repo_name=c.source_repo.repo_name,
642 source_repo_name=c.source_repo.repo_name,
635 source_node_getter=_node_getter(target_commit),
643 source_node_getter=_node_getter(target_commit),
636 target_node_getter=_node_getter(source_commit),
644 target_node_getter=_node_getter(source_commit),
637 comments=display_inline_comments
645 comments=display_inline_comments
638 )
646 )
639 diffset = diffset.render_patchset(
647 diffset = diffset.render_patchset(
640 _parsed, target_commit.raw_id, source_commit.raw_id)
648 _parsed, target_commit.raw_id, source_commit.raw_id)
641
649
642 return diffset
650 return diffset
643
651
644 @LoginRequired()
652 @LoginRequired()
645 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
653 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
646 'repository.admin')
654 'repository.admin')
647 def show(self, repo_name, pull_request_id):
655 def show(self, repo_name, pull_request_id):
648 pull_request_id = safe_int(pull_request_id)
656 pull_request_id = safe_int(pull_request_id)
649 version = request.GET.get('version')
657 version = request.GET.get('version')
650 from_version = request.GET.get('from_version') or version
658 from_version = request.GET.get('from_version') or version
651 merge_checks = request.GET.get('merge_checks')
659 merge_checks = request.GET.get('merge_checks')
652 c.fulldiff = str2bool(request.GET.get('fulldiff'))
660 c.fulldiff = str2bool(request.GET.get('fulldiff'))
653
661
654 (pull_request_latest,
662 (pull_request_latest,
655 pull_request_at_ver,
663 pull_request_at_ver,
656 pull_request_display_obj,
664 pull_request_display_obj,
657 at_version) = self._get_pr_version(
665 at_version) = self._get_pr_version(
658 pull_request_id, version=version)
666 pull_request_id, version=version)
659 pr_closed = pull_request_latest.is_closed()
667 pr_closed = pull_request_latest.is_closed()
660
668
661 if pr_closed and (version or from_version):
669 if pr_closed and (version or from_version):
662 # not allow to browse versions
670 # not allow to browse versions
663 return redirect(h.url('pullrequest_show', repo_name=repo_name,
671 return redirect(h.url('pullrequest_show', repo_name=repo_name,
664 pull_request_id=pull_request_id))
672 pull_request_id=pull_request_id))
665
673
666 versions = pull_request_display_obj.versions()
674 versions = pull_request_display_obj.versions()
667
675
668 c.at_version = at_version
676 c.at_version = at_version
669 c.at_version_num = (at_version
677 c.at_version_num = (at_version
670 if at_version and at_version != 'latest'
678 if at_version and at_version != 'latest'
671 else None)
679 else None)
672 c.at_version_pos = ChangesetComment.get_index_from_version(
680 c.at_version_pos = ChangesetComment.get_index_from_version(
673 c.at_version_num, versions)
681 c.at_version_num, versions)
674
682
675 (prev_pull_request_latest,
683 (prev_pull_request_latest,
676 prev_pull_request_at_ver,
684 prev_pull_request_at_ver,
677 prev_pull_request_display_obj,
685 prev_pull_request_display_obj,
678 prev_at_version) = self._get_pr_version(
686 prev_at_version) = self._get_pr_version(
679 pull_request_id, version=from_version)
687 pull_request_id, version=from_version)
680
688
681 c.from_version = prev_at_version
689 c.from_version = prev_at_version
682 c.from_version_num = (prev_at_version
690 c.from_version_num = (prev_at_version
683 if prev_at_version and prev_at_version != 'latest'
691 if prev_at_version and prev_at_version != 'latest'
684 else None)
692 else None)
685 c.from_version_pos = ChangesetComment.get_index_from_version(
693 c.from_version_pos = ChangesetComment.get_index_from_version(
686 c.from_version_num, versions)
694 c.from_version_num, versions)
687
695
688 # define if we're in COMPARE mode or VIEW at version mode
696 # define if we're in COMPARE mode or VIEW at version mode
689 compare = at_version != prev_at_version
697 compare = at_version != prev_at_version
690
698
691 # pull_requests repo_name we opened it against
699 # pull_requests repo_name we opened it against
692 # ie. target_repo must match
700 # ie. target_repo must match
693 if repo_name != pull_request_at_ver.target_repo.repo_name:
701 if repo_name != pull_request_at_ver.target_repo.repo_name:
694 raise HTTPNotFound
702 raise HTTPNotFound
695
703
696 c.shadow_clone_url = PullRequestModel().get_shadow_clone_url(
704 c.shadow_clone_url = PullRequestModel().get_shadow_clone_url(
697 pull_request_at_ver)
705 pull_request_at_ver)
698
706
699 c.pull_request = pull_request_display_obj
707 c.pull_request = pull_request_display_obj
700 c.pull_request_latest = pull_request_latest
708 c.pull_request_latest = pull_request_latest
701
709
702 if compare or (at_version and not at_version == 'latest'):
710 if compare or (at_version and not at_version == 'latest'):
703 c.allowed_to_change_status = False
711 c.allowed_to_change_status = False
704 c.allowed_to_update = False
712 c.allowed_to_update = False
705 c.allowed_to_merge = False
713 c.allowed_to_merge = False
706 c.allowed_to_delete = False
714 c.allowed_to_delete = False
707 c.allowed_to_comment = False
715 c.allowed_to_comment = False
708 c.allowed_to_close = False
716 c.allowed_to_close = False
709 else:
717 else:
710 c.allowed_to_change_status = PullRequestModel(). \
718 c.allowed_to_change_status = PullRequestModel(). \
711 check_user_change_status(pull_request_at_ver, c.rhodecode_user) \
719 check_user_change_status(pull_request_at_ver, c.rhodecode_user) \
712 and not pr_closed
720 and not pr_closed
713
721
714 c.allowed_to_update = PullRequestModel().check_user_update(
722 c.allowed_to_update = PullRequestModel().check_user_update(
715 pull_request_latest, c.rhodecode_user) and not pr_closed
723 pull_request_latest, c.rhodecode_user) and not pr_closed
716 c.allowed_to_merge = PullRequestModel().check_user_merge(
724 c.allowed_to_merge = PullRequestModel().check_user_merge(
717 pull_request_latest, c.rhodecode_user) and not pr_closed
725 pull_request_latest, c.rhodecode_user) and not pr_closed
718 c.allowed_to_delete = PullRequestModel().check_user_delete(
726 c.allowed_to_delete = PullRequestModel().check_user_delete(
719 pull_request_latest, c.rhodecode_user) and not pr_closed
727 pull_request_latest, c.rhodecode_user) and not pr_closed
720 c.allowed_to_comment = not pr_closed
728 c.allowed_to_comment = not pr_closed
721 c.allowed_to_close = c.allowed_to_change_status and not pr_closed
729 c.allowed_to_close = c.allowed_to_change_status and not pr_closed
722
730
723 # check merge capabilities
731 # check merge capabilities
724 _merge_check = MergeCheck.validate(
732 _merge_check = MergeCheck.validate(
725 pull_request_latest, user=c.rhodecode_user)
733 pull_request_latest, user=c.rhodecode_user)
726 c.pr_merge_errors = _merge_check.error_details
734 c.pr_merge_errors = _merge_check.error_details
727 c.pr_merge_possible = not _merge_check.failed
735 c.pr_merge_possible = not _merge_check.failed
728 c.pr_merge_message = _merge_check.merge_msg
736 c.pr_merge_message = _merge_check.merge_msg
729
737
730 c.pull_request_review_status = _merge_check.review_status
738 c.pull_request_review_status = _merge_check.review_status
731 if merge_checks:
739 if merge_checks:
732 return render('/pullrequests/pullrequest_merge_checks.mako')
740 return render('/pullrequests/pullrequest_merge_checks.mako')
733
741
734 comments_model = CommentsModel()
742 comments_model = CommentsModel()
735
743
736 # reviewers and statuses
744 # reviewers and statuses
737 c.pull_request_reviewers = pull_request_at_ver.reviewers_statuses()
745 c.pull_request_reviewers = pull_request_at_ver.reviewers_statuses()
738 allowed_reviewers = [x[0].user_id for x in c.pull_request_reviewers]
746 allowed_reviewers = [x[0].user_id for x in c.pull_request_reviewers]
739
747
740 # GENERAL COMMENTS with versions #
748 # GENERAL COMMENTS with versions #
741 q = comments_model._all_general_comments_of_pull_request(pull_request_latest)
749 q = comments_model._all_general_comments_of_pull_request(pull_request_latest)
742 q = q.order_by(ChangesetComment.comment_id.asc())
750 q = q.order_by(ChangesetComment.comment_id.asc())
743 general_comments = q.order_by(ChangesetComment.pull_request_version_id.asc())
751 general_comments = q.order_by(ChangesetComment.pull_request_version_id.asc())
744
752
745 # pick comments we want to render at current version
753 # pick comments we want to render at current version
746 c.comment_versions = comments_model.aggregate_comments(
754 c.comment_versions = comments_model.aggregate_comments(
747 general_comments, versions, c.at_version_num)
755 general_comments, versions, c.at_version_num)
748 c.comments = c.comment_versions[c.at_version_num]['until']
756 c.comments = c.comment_versions[c.at_version_num]['until']
749
757
750 # INLINE COMMENTS with versions #
758 # INLINE COMMENTS with versions #
751 q = comments_model._all_inline_comments_of_pull_request(pull_request_latest)
759 q = comments_model._all_inline_comments_of_pull_request(pull_request_latest)
752 q = q.order_by(ChangesetComment.comment_id.asc())
760 q = q.order_by(ChangesetComment.comment_id.asc())
753 inline_comments = q.order_by(ChangesetComment.pull_request_version_id.asc())
761 inline_comments = q.order_by(ChangesetComment.pull_request_version_id.asc())
754 c.inline_versions = comments_model.aggregate_comments(
762 c.inline_versions = comments_model.aggregate_comments(
755 inline_comments, versions, c.at_version_num, inline=True)
763 inline_comments, versions, c.at_version_num, inline=True)
756
764
757 # inject latest version
765 # inject latest version
758 latest_ver = PullRequest.get_pr_display_object(
766 latest_ver = PullRequest.get_pr_display_object(
759 pull_request_latest, pull_request_latest)
767 pull_request_latest, pull_request_latest)
760
768
761 c.versions = versions + [latest_ver]
769 c.versions = versions + [latest_ver]
762
770
763 # if we use version, then do not show later comments
771 # if we use version, then do not show later comments
764 # than current version
772 # than current version
765 display_inline_comments = collections.defaultdict(
773 display_inline_comments = collections.defaultdict(
766 lambda: collections.defaultdict(list))
774 lambda: collections.defaultdict(list))
767 for co in inline_comments:
775 for co in inline_comments:
768 if c.at_version_num:
776 if c.at_version_num:
769 # pick comments that are at least UPTO given version, so we
777 # pick comments that are at least UPTO given version, so we
770 # don't render comments for higher version
778 # don't render comments for higher version
771 should_render = co.pull_request_version_id and \
779 should_render = co.pull_request_version_id and \
772 co.pull_request_version_id <= c.at_version_num
780 co.pull_request_version_id <= c.at_version_num
773 else:
781 else:
774 # showing all, for 'latest'
782 # showing all, for 'latest'
775 should_render = True
783 should_render = True
776
784
777 if should_render:
785 if should_render:
778 display_inline_comments[co.f_path][co.line_no].append(co)
786 display_inline_comments[co.f_path][co.line_no].append(co)
779
787
780 # load diff data into template context, if we use compare mode then
788 # load diff data into template context, if we use compare mode then
781 # diff is calculated based on changes between versions of PR
789 # diff is calculated based on changes between versions of PR
782
790
783 source_repo = pull_request_at_ver.source_repo
791 source_repo = pull_request_at_ver.source_repo
784 source_ref_id = pull_request_at_ver.source_ref_parts.commit_id
792 source_ref_id = pull_request_at_ver.source_ref_parts.commit_id
785
793
786 target_repo = pull_request_at_ver.target_repo
794 target_repo = pull_request_at_ver.target_repo
787 target_ref_id = pull_request_at_ver.target_ref_parts.commit_id
795 target_ref_id = pull_request_at_ver.target_ref_parts.commit_id
788
796
789 if compare:
797 if compare:
790 # in compare switch the diff base to latest commit from prev version
798 # in compare switch the diff base to latest commit from prev version
791 target_ref_id = prev_pull_request_display_obj.revisions[0]
799 target_ref_id = prev_pull_request_display_obj.revisions[0]
792
800
793 # despite opening commits for bookmarks/branches/tags, we always
801 # despite opening commits for bookmarks/branches/tags, we always
794 # convert this to rev to prevent changes after bookmark or branch change
802 # convert this to rev to prevent changes after bookmark or branch change
795 c.source_ref_type = 'rev'
803 c.source_ref_type = 'rev'
796 c.source_ref = source_ref_id
804 c.source_ref = source_ref_id
797
805
798 c.target_ref_type = 'rev'
806 c.target_ref_type = 'rev'
799 c.target_ref = target_ref_id
807 c.target_ref = target_ref_id
800
808
801 c.source_repo = source_repo
809 c.source_repo = source_repo
802 c.target_repo = target_repo
810 c.target_repo = target_repo
803
811
804 # diff_limit is the old behavior, will cut off the whole diff
812 # diff_limit is the old behavior, will cut off the whole diff
805 # if the limit is applied otherwise will just hide the
813 # if the limit is applied otherwise will just hide the
806 # big files from the front-end
814 # big files from the front-end
807 diff_limit = self.cut_off_limit_diff
815 diff_limit = self.cut_off_limit_diff
808 file_limit = self.cut_off_limit_file
816 file_limit = self.cut_off_limit_file
809
817
810 c.commit_ranges = []
818 c.commit_ranges = []
811 source_commit = EmptyCommit()
819 source_commit = EmptyCommit()
812 target_commit = EmptyCommit()
820 target_commit = EmptyCommit()
813 c.missing_requirements = False
821 c.missing_requirements = False
814
822
815 source_scm = source_repo.scm_instance()
823 source_scm = source_repo.scm_instance()
816 target_scm = target_repo.scm_instance()
824 target_scm = target_repo.scm_instance()
817
825
818 # try first shadow repo, fallback to regular repo
826 # try first shadow repo, fallback to regular repo
819 try:
827 try:
820 commits_source_repo = pull_request_latest.get_shadow_repo()
828 commits_source_repo = pull_request_latest.get_shadow_repo()
821 except Exception:
829 except Exception:
822 log.debug('Failed to get shadow repo', exc_info=True)
830 log.debug('Failed to get shadow repo', exc_info=True)
823 commits_source_repo = source_scm
831 commits_source_repo = source_scm
824
832
825 c.commits_source_repo = commits_source_repo
833 c.commits_source_repo = commits_source_repo
826 commit_cache = {}
834 commit_cache = {}
827 try:
835 try:
828 pre_load = ["author", "branch", "date", "message"]
836 pre_load = ["author", "branch", "date", "message"]
829 show_revs = pull_request_at_ver.revisions
837 show_revs = pull_request_at_ver.revisions
830 for rev in show_revs:
838 for rev in show_revs:
831 comm = commits_source_repo.get_commit(
839 comm = commits_source_repo.get_commit(
832 commit_id=rev, pre_load=pre_load)
840 commit_id=rev, pre_load=pre_load)
833 c.commit_ranges.append(comm)
841 c.commit_ranges.append(comm)
834 commit_cache[comm.raw_id] = comm
842 commit_cache[comm.raw_id] = comm
835
843
836 target_commit = commits_source_repo.get_commit(
844 target_commit = commits_source_repo.get_commit(
837 commit_id=safe_str(target_ref_id))
845 commit_id=safe_str(target_ref_id))
838 source_commit = commits_source_repo.get_commit(
846 source_commit = commits_source_repo.get_commit(
839 commit_id=safe_str(source_ref_id))
847 commit_id=safe_str(source_ref_id))
840 except CommitDoesNotExistError:
848 except CommitDoesNotExistError:
841 pass
849 pass
842 except RepositoryRequirementError:
850 except RepositoryRequirementError:
843 log.warning(
851 log.warning(
844 'Failed to get all required data from repo', exc_info=True)
852 'Failed to get all required data from repo', exc_info=True)
845 c.missing_requirements = True
853 c.missing_requirements = True
846
854
847 c.ancestor = None # set it to None, to hide it from PR view
855 c.ancestor = None # set it to None, to hide it from PR view
848
856
849 try:
857 try:
850 ancestor_id = source_scm.get_common_ancestor(
858 ancestor_id = source_scm.get_common_ancestor(
851 source_commit.raw_id, target_commit.raw_id, target_scm)
859 source_commit.raw_id, target_commit.raw_id, target_scm)
852 c.ancestor_commit = source_scm.get_commit(ancestor_id)
860 c.ancestor_commit = source_scm.get_commit(ancestor_id)
853 except Exception:
861 except Exception:
854 c.ancestor_commit = None
862 c.ancestor_commit = None
855
863
856 c.statuses = source_repo.statuses(
864 c.statuses = source_repo.statuses(
857 [x.raw_id for x in c.commit_ranges])
865 [x.raw_id for x in c.commit_ranges])
858
866
859 # auto collapse if we have more than limit
867 # auto collapse if we have more than limit
860 collapse_limit = diffs.DiffProcessor._collapse_commits_over
868 collapse_limit = diffs.DiffProcessor._collapse_commits_over
861 c.collapse_all_commits = len(c.commit_ranges) > collapse_limit
869 c.collapse_all_commits = len(c.commit_ranges) > collapse_limit
862 c.compare_mode = compare
870 c.compare_mode = compare
863
871
864 c.missing_commits = False
872 c.missing_commits = False
865 if (c.missing_requirements or isinstance(source_commit, EmptyCommit)
873 if (c.missing_requirements or isinstance(source_commit, EmptyCommit)
866 or source_commit == target_commit):
874 or source_commit == target_commit):
867
875
868 c.missing_commits = True
876 c.missing_commits = True
869 else:
877 else:
870
878
871 c.diffset = self._get_diffset(
879 c.diffset = self._get_diffset(
872 commits_source_repo, source_ref_id, target_ref_id,
880 commits_source_repo, source_ref_id, target_ref_id,
873 target_commit, source_commit,
881 target_commit, source_commit,
874 diff_limit, file_limit, display_inline_comments)
882 diff_limit, file_limit, display_inline_comments)
875
883
876 c.limited_diff = c.diffset.limited_diff
884 c.limited_diff = c.diffset.limited_diff
877
885
878 # calculate removed files that are bound to comments
886 # calculate removed files that are bound to comments
879 comment_deleted_files = [
887 comment_deleted_files = [
880 fname for fname in display_inline_comments
888 fname for fname in display_inline_comments
881 if fname not in c.diffset.file_stats]
889 if fname not in c.diffset.file_stats]
882
890
883 c.deleted_files_comments = collections.defaultdict(dict)
891 c.deleted_files_comments = collections.defaultdict(dict)
884 for fname, per_line_comments in display_inline_comments.items():
892 for fname, per_line_comments in display_inline_comments.items():
885 if fname in comment_deleted_files:
893 if fname in comment_deleted_files:
886 c.deleted_files_comments[fname]['stats'] = 0
894 c.deleted_files_comments[fname]['stats'] = 0
887 c.deleted_files_comments[fname]['comments'] = list()
895 c.deleted_files_comments[fname]['comments'] = list()
888 for lno, comments in per_line_comments.items():
896 for lno, comments in per_line_comments.items():
889 c.deleted_files_comments[fname]['comments'].extend(
897 c.deleted_files_comments[fname]['comments'].extend(
890 comments)
898 comments)
891
899
892 # this is a hack to properly display links, when creating PR, the
900 # this is a hack to properly display links, when creating PR, the
893 # compare view and others uses different notation, and
901 # compare view and others uses different notation, and
894 # compare_commits.mako renders links based on the target_repo.
902 # compare_commits.mako renders links based on the target_repo.
895 # We need to swap that here to generate it properly on the html side
903 # We need to swap that here to generate it properly on the html side
896 c.target_repo = c.source_repo
904 c.target_repo = c.source_repo
897
905
898 c.commit_statuses = ChangesetStatus.STATUSES
906 c.commit_statuses = ChangesetStatus.STATUSES
899
907
900 c.show_version_changes = not pr_closed
908 c.show_version_changes = not pr_closed
901 if c.show_version_changes:
909 if c.show_version_changes:
902 cur_obj = pull_request_at_ver
910 cur_obj = pull_request_at_ver
903 prev_obj = prev_pull_request_at_ver
911 prev_obj = prev_pull_request_at_ver
904
912
905 old_commit_ids = prev_obj.revisions
913 old_commit_ids = prev_obj.revisions
906 new_commit_ids = cur_obj.revisions
914 new_commit_ids = cur_obj.revisions
907 commit_changes = PullRequestModel()._calculate_commit_id_changes(
915 commit_changes = PullRequestModel()._calculate_commit_id_changes(
908 old_commit_ids, new_commit_ids)
916 old_commit_ids, new_commit_ids)
909 c.commit_changes_summary = commit_changes
917 c.commit_changes_summary = commit_changes
910
918
911 # calculate the diff for commits between versions
919 # calculate the diff for commits between versions
912 c.commit_changes = []
920 c.commit_changes = []
913 mark = lambda cs, fw: list(
921 mark = lambda cs, fw: list(
914 h.itertools.izip_longest([], cs, fillvalue=fw))
922 h.itertools.izip_longest([], cs, fillvalue=fw))
915 for c_type, raw_id in mark(commit_changes.added, 'a') \
923 for c_type, raw_id in mark(commit_changes.added, 'a') \
916 + mark(commit_changes.removed, 'r') \
924 + mark(commit_changes.removed, 'r') \
917 + mark(commit_changes.common, 'c'):
925 + mark(commit_changes.common, 'c'):
918
926
919 if raw_id in commit_cache:
927 if raw_id in commit_cache:
920 commit = commit_cache[raw_id]
928 commit = commit_cache[raw_id]
921 else:
929 else:
922 try:
930 try:
923 commit = commits_source_repo.get_commit(raw_id)
931 commit = commits_source_repo.get_commit(raw_id)
924 except CommitDoesNotExistError:
932 except CommitDoesNotExistError:
925 # in case we fail extracting still use "dummy" commit
933 # in case we fail extracting still use "dummy" commit
926 # for display in commit diff
934 # for display in commit diff
927 commit = h.AttributeDict(
935 commit = h.AttributeDict(
928 {'raw_id': raw_id,
936 {'raw_id': raw_id,
929 'message': 'EMPTY or MISSING COMMIT'})
937 'message': 'EMPTY or MISSING COMMIT'})
930 c.commit_changes.append([c_type, commit])
938 c.commit_changes.append([c_type, commit])
931
939
932 # current user review statuses for each version
940 # current user review statuses for each version
933 c.review_versions = {}
941 c.review_versions = {}
934 if c.rhodecode_user.user_id in allowed_reviewers:
942 if c.rhodecode_user.user_id in allowed_reviewers:
935 for co in general_comments:
943 for co in general_comments:
936 if co.author.user_id == c.rhodecode_user.user_id:
944 if co.author.user_id == c.rhodecode_user.user_id:
937 # each comment has a status change
945 # each comment has a status change
938 status = co.status_change
946 status = co.status_change
939 if status:
947 if status:
940 _ver_pr = status[0].comment.pull_request_version_id
948 _ver_pr = status[0].comment.pull_request_version_id
941 c.review_versions[_ver_pr] = status[0]
949 c.review_versions[_ver_pr] = status[0]
942
950
943 return render('/pullrequests/pullrequest_show.mako')
951 return render('/pullrequests/pullrequest_show.mako')
944
952
945 @LoginRequired()
953 @LoginRequired()
946 @NotAnonymous()
954 @NotAnonymous()
947 @HasRepoPermissionAnyDecorator(
955 @HasRepoPermissionAnyDecorator(
948 'repository.read', 'repository.write', 'repository.admin')
956 'repository.read', 'repository.write', 'repository.admin')
949 @auth.CSRFRequired()
957 @auth.CSRFRequired()
950 @jsonify
958 @jsonify
951 def comment(self, repo_name, pull_request_id):
959 def comment(self, repo_name, pull_request_id):
952 pull_request_id = safe_int(pull_request_id)
960 pull_request_id = safe_int(pull_request_id)
953 pull_request = PullRequest.get_or_404(pull_request_id)
961 pull_request = PullRequest.get_or_404(pull_request_id)
954 if pull_request.is_closed():
962 if pull_request.is_closed():
955 raise HTTPForbidden()
963 raise HTTPForbidden()
956
964
957 status = request.POST.get('changeset_status', None)
965 status = request.POST.get('changeset_status', None)
958 text = request.POST.get('text')
966 text = request.POST.get('text')
959 comment_type = request.POST.get('comment_type')
967 comment_type = request.POST.get('comment_type')
960 resolves_comment_id = request.POST.get('resolves_comment_id', None)
968 resolves_comment_id = request.POST.get('resolves_comment_id', None)
961 close_pull_request = request.POST.get('close_pull_request')
969 close_pull_request = request.POST.get('close_pull_request')
962
970
963 close_pr = False
971 close_pr = False
964 if close_pull_request:
972 if close_pull_request:
965 close_pr = True
973 close_pr = True
966 pull_request_review_status = pull_request.calculated_review_status()
974 pull_request_review_status = pull_request.calculated_review_status()
967 if pull_request_review_status == ChangesetStatus.STATUS_APPROVED:
975 if pull_request_review_status == ChangesetStatus.STATUS_APPROVED:
968 # approved only if we have voting consent
976 # approved only if we have voting consent
969 status = ChangesetStatus.STATUS_APPROVED
977 status = ChangesetStatus.STATUS_APPROVED
970 else:
978 else:
971 status = ChangesetStatus.STATUS_REJECTED
979 status = ChangesetStatus.STATUS_REJECTED
972
980
973 allowed_to_change_status = PullRequestModel().check_user_change_status(
981 allowed_to_change_status = PullRequestModel().check_user_change_status(
974 pull_request, c.rhodecode_user)
982 pull_request, c.rhodecode_user)
975
983
976 if status and allowed_to_change_status:
984 if status and allowed_to_change_status:
977 message = (_('Status change %(transition_icon)s %(status)s')
985 message = (_('Status change %(transition_icon)s %(status)s')
978 % {'transition_icon': '>',
986 % {'transition_icon': '>',
979 'status': ChangesetStatus.get_status_lbl(status)})
987 'status': ChangesetStatus.get_status_lbl(status)})
980 if close_pr:
988 if close_pr:
981 message = _('Closing with') + ' ' + message
989 message = _('Closing with') + ' ' + message
982 text = text or message
990 text = text or message
983 comm = CommentsModel().create(
991 comm = CommentsModel().create(
984 text=text,
992 text=text,
985 repo=c.rhodecode_db_repo.repo_id,
993 repo=c.rhodecode_db_repo.repo_id,
986 user=c.rhodecode_user.user_id,
994 user=c.rhodecode_user.user_id,
987 pull_request=pull_request_id,
995 pull_request=pull_request_id,
988 f_path=request.POST.get('f_path'),
996 f_path=request.POST.get('f_path'),
989 line_no=request.POST.get('line'),
997 line_no=request.POST.get('line'),
990 status_change=(ChangesetStatus.get_status_lbl(status)
998 status_change=(ChangesetStatus.get_status_lbl(status)
991 if status and allowed_to_change_status else None),
999 if status and allowed_to_change_status else None),
992 status_change_type=(status
1000 status_change_type=(status
993 if status and allowed_to_change_status else None),
1001 if status and allowed_to_change_status else None),
994 closing_pr=close_pr,
1002 closing_pr=close_pr,
995 comment_type=comment_type,
1003 comment_type=comment_type,
996 resolves_comment_id=resolves_comment_id
1004 resolves_comment_id=resolves_comment_id
997 )
1005 )
998
1006
999 if allowed_to_change_status:
1007 if allowed_to_change_status:
1000 old_calculated_status = pull_request.calculated_review_status()
1008 old_calculated_status = pull_request.calculated_review_status()
1001 # get status if set !
1009 # get status if set !
1002 if status:
1010 if status:
1003 ChangesetStatusModel().set_status(
1011 ChangesetStatusModel().set_status(
1004 c.rhodecode_db_repo.repo_id,
1012 c.rhodecode_db_repo.repo_id,
1005 status,
1013 status,
1006 c.rhodecode_user.user_id,
1014 c.rhodecode_user.user_id,
1007 comm,
1015 comm,
1008 pull_request=pull_request_id
1016 pull_request=pull_request_id
1009 )
1017 )
1010
1018
1011 Session().flush()
1019 Session().flush()
1012 events.trigger(events.PullRequestCommentEvent(pull_request, comm))
1020 events.trigger(events.PullRequestCommentEvent(pull_request, comm))
1013 # we now calculate the status of pull request, and based on that
1021 # we now calculate the status of pull request, and based on that
1014 # calculation we set the commits status
1022 # calculation we set the commits status
1015 calculated_status = pull_request.calculated_review_status()
1023 calculated_status = pull_request.calculated_review_status()
1016 if old_calculated_status != calculated_status:
1024 if old_calculated_status != calculated_status:
1017 PullRequestModel()._trigger_pull_request_hook(
1025 PullRequestModel()._trigger_pull_request_hook(
1018 pull_request, c.rhodecode_user, 'review_status_change')
1026 pull_request, c.rhodecode_user, 'review_status_change')
1019
1027
1020 calculated_status_lbl = ChangesetStatus.get_status_lbl(
1028 calculated_status_lbl = ChangesetStatus.get_status_lbl(
1021 calculated_status)
1029 calculated_status)
1022
1030
1023 if close_pr:
1031 if close_pr:
1024 status_completed = (
1032 status_completed = (
1025 calculated_status in [ChangesetStatus.STATUS_APPROVED,
1033 calculated_status in [ChangesetStatus.STATUS_APPROVED,
1026 ChangesetStatus.STATUS_REJECTED])
1034 ChangesetStatus.STATUS_REJECTED])
1027 if close_pull_request or status_completed:
1035 if close_pull_request or status_completed:
1028 PullRequestModel().close_pull_request(
1036 PullRequestModel().close_pull_request(
1029 pull_request_id, c.rhodecode_user)
1037 pull_request_id, c.rhodecode_user)
1030 else:
1038 else:
1031 h.flash(_('Closing pull request on other statuses than '
1039 h.flash(_('Closing pull request on other statuses than '
1032 'rejected or approved is forbidden. '
1040 'rejected or approved is forbidden. '
1033 'Calculated status from all reviewers '
1041 'Calculated status from all reviewers '
1034 'is currently: %s') % calculated_status_lbl,
1042 'is currently: %s') % calculated_status_lbl,
1035 category='warning')
1043 category='warning')
1036
1044
1037 Session().commit()
1045 Session().commit()
1038
1046
1039 if not request.is_xhr:
1047 if not request.is_xhr:
1040 return redirect(h.url('pullrequest_show', repo_name=repo_name,
1048 return redirect(h.url('pullrequest_show', repo_name=repo_name,
1041 pull_request_id=pull_request_id))
1049 pull_request_id=pull_request_id))
1042
1050
1043 data = {
1051 data = {
1044 'target_id': h.safeid(h.safe_unicode(request.POST.get('f_path'))),
1052 'target_id': h.safeid(h.safe_unicode(request.POST.get('f_path'))),
1045 }
1053 }
1046 if comm:
1054 if comm:
1047 c.co = comm
1055 c.co = comm
1048 c.inline_comment = True if comm.line_no else False
1056 c.inline_comment = True if comm.line_no else False
1049 data.update(comm.get_dict())
1057 data.update(comm.get_dict())
1050 data.update({'rendered_text':
1058 data.update({'rendered_text':
1051 render('changeset/changeset_comment_block.mako')})
1059 render('changeset/changeset_comment_block.mako')})
1052
1060
1053 return data
1061 return data
1054
1062
1055 @LoginRequired()
1063 @LoginRequired()
1056 @NotAnonymous()
1064 @NotAnonymous()
1057 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
1065 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
1058 'repository.admin')
1066 'repository.admin')
1059 @auth.CSRFRequired()
1067 @auth.CSRFRequired()
1060 @jsonify
1068 @jsonify
1061 def delete_comment(self, repo_name, comment_id):
1069 def delete_comment(self, repo_name, comment_id):
1062 return self._delete_comment(comment_id)
1070 return self._delete_comment(comment_id)
1063
1071
1064 def _delete_comment(self, comment_id):
1072 def _delete_comment(self, comment_id):
1065 comment_id = safe_int(comment_id)
1073 comment_id = safe_int(comment_id)
1066 co = ChangesetComment.get_or_404(comment_id)
1074 co = ChangesetComment.get_or_404(comment_id)
1067 if co.pull_request.is_closed():
1075 if co.pull_request.is_closed():
1068 # don't allow deleting comments on closed pull request
1076 # don't allow deleting comments on closed pull request
1069 raise HTTPForbidden()
1077 raise HTTPForbidden()
1070
1078
1071 is_owner = co.author.user_id == c.rhodecode_user.user_id
1079 is_owner = co.author.user_id == c.rhodecode_user.user_id
1072 is_repo_admin = h.HasRepoPermissionAny('repository.admin')(c.repo_name)
1080 is_repo_admin = h.HasRepoPermissionAny('repository.admin')(c.repo_name)
1073 if h.HasPermissionAny('hg.admin')() or is_repo_admin or is_owner:
1081 if h.HasPermissionAny('hg.admin')() or is_repo_admin or is_owner:
1074 old_calculated_status = co.pull_request.calculated_review_status()
1082 old_calculated_status = co.pull_request.calculated_review_status()
1075 CommentsModel().delete(comment=co)
1083 CommentsModel().delete(comment=co)
1076 Session().commit()
1084 Session().commit()
1077 calculated_status = co.pull_request.calculated_review_status()
1085 calculated_status = co.pull_request.calculated_review_status()
1078 if old_calculated_status != calculated_status:
1086 if old_calculated_status != calculated_status:
1079 PullRequestModel()._trigger_pull_request_hook(
1087 PullRequestModel()._trigger_pull_request_hook(
1080 co.pull_request, c.rhodecode_user, 'review_status_change')
1088 co.pull_request, c.rhodecode_user, 'review_status_change')
1081 return True
1089 return True
1082 else:
1090 else:
1083 raise HTTPForbidden()
1091 raise HTTPForbidden()
General Comments 0
You need to be logged in to leave comments. Login now