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