##// END OF EJS Templates
fixes #693 Opening changeset from pull request fails
marcink -
r3123:324ed41c beta
parent child Browse files
Show More
@@ -1,487 +1,486 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.controllers.pullrequests
3 rhodecode.controllers.pullrequests
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 pull requests controller for rhodecode for initializing pull requests
6 pull requests controller for rhodecode for initializing pull requests
7
7
8 :created_on: May 7, 2012
8 :created_on: May 7, 2012
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
16 # (at your option) any later version.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 import logging
25 import logging
26 import traceback
26 import traceback
27 import formencode
27 import formencode
28
28
29 from webob.exc import HTTPNotFound, HTTPForbidden
29 from webob.exc import HTTPNotFound, HTTPForbidden
30 from collections import defaultdict
30 from collections import defaultdict
31 from itertools import groupby
31 from itertools import groupby
32
32
33 from pylons import request, response, session, tmpl_context as c, url
33 from pylons import request, response, session, tmpl_context as c, url
34 from pylons.controllers.util import abort, redirect
34 from pylons.controllers.util import abort, redirect
35 from pylons.i18n.translation import _
35 from pylons.i18n.translation import _
36
36
37 from rhodecode.lib.compat import json
37 from rhodecode.lib.compat import json
38 from rhodecode.lib.base import BaseRepoController, render
38 from rhodecode.lib.base import BaseRepoController, render
39 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator,\
39 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator,\
40 NotAnonymous
40 NotAnonymous
41 from rhodecode.lib import helpers as h
41 from rhodecode.lib import helpers as h
42 from rhodecode.lib import diffs
42 from rhodecode.lib import diffs
43 from rhodecode.lib.utils import action_logger, jsonify
43 from rhodecode.lib.utils import action_logger, jsonify
44 from rhodecode.lib.vcs.exceptions import EmptyRepositoryError
44 from rhodecode.lib.vcs.exceptions import EmptyRepositoryError
45 from rhodecode.lib.vcs.backends.base import EmptyChangeset
45 from rhodecode.lib.vcs.backends.base import EmptyChangeset
46 from rhodecode.lib.diffs import LimitedDiffContainer
46 from rhodecode.lib.diffs import LimitedDiffContainer
47 from rhodecode.model.db import User, PullRequest, ChangesetStatus,\
47 from rhodecode.model.db import User, PullRequest, ChangesetStatus,\
48 ChangesetComment
48 ChangesetComment
49 from rhodecode.model.pull_request import PullRequestModel
49 from rhodecode.model.pull_request import PullRequestModel
50 from rhodecode.model.meta import Session
50 from rhodecode.model.meta import Session
51 from rhodecode.model.repo import RepoModel
51 from rhodecode.model.repo import RepoModel
52 from rhodecode.model.comment import ChangesetCommentsModel
52 from rhodecode.model.comment import ChangesetCommentsModel
53 from rhodecode.model.changeset_status import ChangesetStatusModel
53 from rhodecode.model.changeset_status import ChangesetStatusModel
54 from rhodecode.model.forms import PullRequestForm
54 from rhodecode.model.forms import PullRequestForm
55
55
56 log = logging.getLogger(__name__)
56 log = logging.getLogger(__name__)
57
57
58
58
59 class PullrequestsController(BaseRepoController):
59 class PullrequestsController(BaseRepoController):
60
60
61 @LoginRequired()
61 @LoginRequired()
62 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
62 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
63 'repository.admin')
63 'repository.admin')
64 def __before__(self):
64 def __before__(self):
65 super(PullrequestsController, self).__before__()
65 super(PullrequestsController, self).__before__()
66 repo_model = RepoModel()
66 repo_model = RepoModel()
67 c.users_array = repo_model.get_users_js()
67 c.users_array = repo_model.get_users_js()
68 c.users_groups_array = repo_model.get_users_groups_js()
68 c.users_groups_array = repo_model.get_users_groups_js()
69
69
70 def _get_repo_refs(self, repo):
70 def _get_repo_refs(self, repo):
71 hist_l = []
71 hist_l = []
72
72
73 branches_group = ([('branch:%s:%s' % (k, v), k) for
73 branches_group = ([('branch:%s:%s' % (k, v), k) for
74 k, v in repo.branches.iteritems()], _("Branches"))
74 k, v in repo.branches.iteritems()], _("Branches"))
75 bookmarks_group = ([('book:%s:%s' % (k, v), k) for
75 bookmarks_group = ([('book:%s:%s' % (k, v), k) for
76 k, v in repo.bookmarks.iteritems()], _("Bookmarks"))
76 k, v in repo.bookmarks.iteritems()], _("Bookmarks"))
77 tags_group = ([('tag:%s:%s' % (k, v), k) for
77 tags_group = ([('tag:%s:%s' % (k, v), k) for
78 k, v in repo.tags.iteritems()], _("Tags"))
78 k, v in repo.tags.iteritems()], _("Tags"))
79
79
80 hist_l.append(bookmarks_group)
80 hist_l.append(bookmarks_group)
81 hist_l.append(branches_group)
81 hist_l.append(branches_group)
82 hist_l.append(tags_group)
82 hist_l.append(tags_group)
83
83
84 return hist_l
84 return hist_l
85
85
86 def _get_default_rev(self, repo):
86 def _get_default_rev(self, repo):
87 """
87 """
88 Get's default revision to do compare on pull request
88 Get's default revision to do compare on pull request
89
89
90 :param repo:
90 :param repo:
91 """
91 """
92 repo = repo.scm_instance
92 repo = repo.scm_instance
93 if 'default' in repo.branches:
93 if 'default' in repo.branches:
94 return 'default'
94 return 'default'
95 else:
95 else:
96 #if repo doesn't have default branch return first found
96 #if repo doesn't have default branch return first found
97 return repo.branches.keys()[0]
97 return repo.branches.keys()[0]
98
98
99 def _get_is_allowed_change_status(self, pull_request):
99 def _get_is_allowed_change_status(self, pull_request):
100 owner = self.rhodecode_user.user_id == pull_request.user_id
100 owner = self.rhodecode_user.user_id == pull_request.user_id
101 reviewer = self.rhodecode_user.user_id in [x.user_id for x in
101 reviewer = self.rhodecode_user.user_id in [x.user_id for x in
102 pull_request.reviewers]
102 pull_request.reviewers]
103 return (self.rhodecode_user.admin or owner or reviewer)
103 return (self.rhodecode_user.admin or owner or reviewer)
104
104
105 def show_all(self, repo_name):
105 def show_all(self, repo_name):
106 c.pull_requests = PullRequestModel().get_all(repo_name)
106 c.pull_requests = PullRequestModel().get_all(repo_name)
107 c.repo_name = repo_name
107 c.repo_name = repo_name
108 return render('/pullrequests/pullrequest_show_all.html')
108 return render('/pullrequests/pullrequest_show_all.html')
109
109
110 @NotAnonymous()
110 @NotAnonymous()
111 def index(self):
111 def index(self):
112 org_repo = c.rhodecode_db_repo
112 org_repo = c.rhodecode_db_repo
113
113
114 if org_repo.scm_instance.alias != 'hg':
114 if org_repo.scm_instance.alias != 'hg':
115 log.error('Review not available for GIT REPOS')
115 log.error('Review not available for GIT REPOS')
116 raise HTTPNotFound
116 raise HTTPNotFound
117
117
118 try:
118 try:
119 org_repo.scm_instance.get_changeset()
119 org_repo.scm_instance.get_changeset()
120 except EmptyRepositoryError, e:
120 except EmptyRepositoryError, e:
121 h.flash(h.literal(_('There are no changesets yet')),
121 h.flash(h.literal(_('There are no changesets yet')),
122 category='warning')
122 category='warning')
123 redirect(url('summary_home', repo_name=org_repo.repo_name))
123 redirect(url('summary_home', repo_name=org_repo.repo_name))
124
124
125 other_repos_info = {}
125 other_repos_info = {}
126
126
127 c.org_refs = self._get_repo_refs(c.rhodecode_repo)
127 c.org_refs = self._get_repo_refs(c.rhodecode_repo)
128 c.org_repos = []
128 c.org_repos = []
129 c.other_repos = []
129 c.other_repos = []
130 c.org_repos.append((org_repo.repo_name, '%s/%s' % (
130 c.org_repos.append((org_repo.repo_name, '%s/%s' % (
131 org_repo.user.username, c.repo_name))
131 org_repo.user.username, c.repo_name))
132 )
132 )
133
133
134 # add org repo to other so we can open pull request agains itself
134 # add org repo to other so we can open pull request agains itself
135 c.other_repos.extend(c.org_repos)
135 c.other_repos.extend(c.org_repos)
136
136
137 c.default_pull_request = org_repo.repo_name # repo name pre-selected
137 c.default_pull_request = org_repo.repo_name # repo name pre-selected
138 c.default_pull_request_rev = self._get_default_rev(org_repo) # revision pre-selected
138 c.default_pull_request_rev = self._get_default_rev(org_repo) # revision pre-selected
139 c.default_revs = self._get_repo_refs(org_repo.scm_instance)
139 c.default_revs = self._get_repo_refs(org_repo.scm_instance)
140 #add orginal repo
140 #add orginal repo
141 other_repos_info[org_repo.repo_name] = {
141 other_repos_info[org_repo.repo_name] = {
142 'gravatar': h.gravatar_url(org_repo.user.email, 24),
142 'gravatar': h.gravatar_url(org_repo.user.email, 24),
143 'description': org_repo.description,
143 'description': org_repo.description,
144 'revs': h.select('other_ref', '', c.default_revs, class_='refs')
144 'revs': h.select('other_ref', '', c.default_revs, class_='refs')
145 }
145 }
146
146
147 #gather forks and add to this list
147 #gather forks and add to this list
148 for fork in org_repo.forks:
148 for fork in org_repo.forks:
149 c.other_repos.append((fork.repo_name, '%s/%s' % (
149 c.other_repos.append((fork.repo_name, '%s/%s' % (
150 fork.user.username, fork.repo_name))
150 fork.user.username, fork.repo_name))
151 )
151 )
152 other_repos_info[fork.repo_name] = {
152 other_repos_info[fork.repo_name] = {
153 'gravatar': h.gravatar_url(fork.user.email, 24),
153 'gravatar': h.gravatar_url(fork.user.email, 24),
154 'description': fork.description,
154 'description': fork.description,
155 'revs': h.select('other_ref', '',
155 'revs': h.select('other_ref', '',
156 self._get_repo_refs(fork.scm_instance),
156 self._get_repo_refs(fork.scm_instance),
157 class_='refs')
157 class_='refs')
158 }
158 }
159 #add parents of this fork also, but only if it's not empty
159 #add parents of this fork also, but only if it's not empty
160 if org_repo.parent and org_repo.parent.scm_instance.revisions:
160 if org_repo.parent and org_repo.parent.scm_instance.revisions:
161 c.default_pull_request = org_repo.parent.repo_name
161 c.default_pull_request = org_repo.parent.repo_name
162 c.default_pull_request_rev = self._get_default_rev(org_repo.parent)
162 c.default_pull_request_rev = self._get_default_rev(org_repo.parent)
163 c.default_revs = self._get_repo_refs(org_repo.parent.scm_instance)
163 c.default_revs = self._get_repo_refs(org_repo.parent.scm_instance)
164 c.other_repos.append((org_repo.parent.repo_name, '%s/%s' % (
164 c.other_repos.append((org_repo.parent.repo_name, '%s/%s' % (
165 org_repo.parent.user.username,
165 org_repo.parent.user.username,
166 org_repo.parent.repo_name))
166 org_repo.parent.repo_name))
167 )
167 )
168 other_repos_info[org_repo.parent.repo_name] = {
168 other_repos_info[org_repo.parent.repo_name] = {
169 'gravatar': h.gravatar_url(org_repo.parent.user.email, 24),
169 'gravatar': h.gravatar_url(org_repo.parent.user.email, 24),
170 'description': org_repo.parent.description,
170 'description': org_repo.parent.description,
171 'revs': h.select('other_ref', '',
171 'revs': h.select('other_ref', '',
172 self._get_repo_refs(org_repo.parent.scm_instance),
172 self._get_repo_refs(org_repo.parent.scm_instance),
173 class_='refs')
173 class_='refs')
174 }
174 }
175
175
176 c.other_repos_info = json.dumps(other_repos_info)
176 c.other_repos_info = json.dumps(other_repos_info)
177 c.review_members = [org_repo.user]
177 c.review_members = [org_repo.user]
178 return render('/pullrequests/pullrequest.html')
178 return render('/pullrequests/pullrequest.html')
179
179
180 @NotAnonymous()
180 @NotAnonymous()
181 def create(self, repo_name):
181 def create(self, repo_name):
182 repo = RepoModel()._get_repo(repo_name)
182 repo = RepoModel()._get_repo(repo_name)
183 try:
183 try:
184 _form = PullRequestForm(repo.repo_id)().to_python(request.POST)
184 _form = PullRequestForm(repo.repo_id)().to_python(request.POST)
185 except formencode.Invalid, errors:
185 except formencode.Invalid, errors:
186 log.error(traceback.format_exc())
186 log.error(traceback.format_exc())
187 if errors.error_dict.get('revisions'):
187 if errors.error_dict.get('revisions'):
188 msg = 'Revisions: %s' % errors.error_dict['revisions']
188 msg = 'Revisions: %s' % errors.error_dict['revisions']
189 elif errors.error_dict.get('pullrequest_title'):
189 elif errors.error_dict.get('pullrequest_title'):
190 msg = _('Pull request requires a title with min. 3 chars')
190 msg = _('Pull request requires a title with min. 3 chars')
191 else:
191 else:
192 msg = _('error during creation of pull request')
192 msg = _('error during creation of pull request')
193
193
194 h.flash(msg, 'error')
194 h.flash(msg, 'error')
195 return redirect(url('pullrequest_home', repo_name=repo_name))
195 return redirect(url('pullrequest_home', repo_name=repo_name))
196
196
197 org_repo = _form['org_repo']
197 org_repo = _form['org_repo']
198 org_ref = _form['org_ref']
198 org_ref = _form['org_ref']
199 other_repo = _form['other_repo']
199 other_repo = _form['other_repo']
200 other_ref = _form['other_ref']
200 other_ref = _form['other_ref']
201 revisions = _form['revisions']
201 revisions = _form['revisions']
202 reviewers = _form['review_members']
202 reviewers = _form['review_members']
203
203
204 # if we have cherry picked pull request we don't care what is in
204 # if we have cherry picked pull request we don't care what is in
205 # org_ref/other_ref
205 # org_ref/other_ref
206 rev_start = request.POST.get('rev_start')
206 rev_start = request.POST.get('rev_start')
207 rev_end = request.POST.get('rev_end')
207 rev_end = request.POST.get('rev_end')
208
208
209 if rev_start and rev_end:
209 if rev_start and rev_end:
210 # this is swapped to simulate that rev_end is a revision from
210 # this is swapped to simulate that rev_end is a revision from
211 # parent of the fork
211 # parent of the fork
212 org_ref = 'rev:%s:%s' % (rev_end, rev_end)
212 org_ref = 'rev:%s:%s' % (rev_end, rev_end)
213 other_ref = 'rev:%s:%s' % (rev_start, rev_start)
213 other_ref = 'rev:%s:%s' % (rev_start, rev_start)
214
214
215 title = _form['pullrequest_title']
215 title = _form['pullrequest_title']
216 description = _form['pullrequest_desc']
216 description = _form['pullrequest_desc']
217
217
218 try:
218 try:
219 pull_request = PullRequestModel().create(
219 pull_request = PullRequestModel().create(
220 self.rhodecode_user.user_id, org_repo, org_ref, other_repo,
220 self.rhodecode_user.user_id, org_repo, org_ref, other_repo,
221 other_ref, revisions, reviewers, title, description
221 other_ref, revisions, reviewers, title, description
222 )
222 )
223 Session().commit()
223 Session().commit()
224 h.flash(_('Successfully opened new pull request'),
224 h.flash(_('Successfully opened new pull request'),
225 category='success')
225 category='success')
226 except Exception:
226 except Exception:
227 h.flash(_('Error occurred during sending pull request'),
227 h.flash(_('Error occurred during sending pull request'),
228 category='error')
228 category='error')
229 log.error(traceback.format_exc())
229 log.error(traceback.format_exc())
230 return redirect(url('pullrequest_home', repo_name=repo_name))
230 return redirect(url('pullrequest_home', repo_name=repo_name))
231
231
232 return redirect(url('pullrequest_show', repo_name=other_repo,
232 return redirect(url('pullrequest_show', repo_name=other_repo,
233 pull_request_id=pull_request.pull_request_id))
233 pull_request_id=pull_request.pull_request_id))
234
234
235 @NotAnonymous()
235 @NotAnonymous()
236 @jsonify
236 @jsonify
237 def update(self, repo_name, pull_request_id):
237 def update(self, repo_name, pull_request_id):
238 pull_request = PullRequest.get_or_404(pull_request_id)
238 pull_request = PullRequest.get_or_404(pull_request_id)
239 if pull_request.is_closed():
239 if pull_request.is_closed():
240 raise HTTPForbidden()
240 raise HTTPForbidden()
241 #only owner or admin can update it
241 #only owner or admin can update it
242 owner = pull_request.author.user_id == c.rhodecode_user.user_id
242 owner = pull_request.author.user_id == c.rhodecode_user.user_id
243 if h.HasPermissionAny('hg.admin', 'repository.admin')() or owner:
243 if h.HasPermissionAny('hg.admin', 'repository.admin')() or owner:
244 reviewers_ids = map(int, filter(lambda v: v not in [None, ''],
244 reviewers_ids = map(int, filter(lambda v: v not in [None, ''],
245 request.POST.get('reviewers_ids', '').split(',')))
245 request.POST.get('reviewers_ids', '').split(',')))
246
246
247 PullRequestModel().update_reviewers(pull_request_id, reviewers_ids)
247 PullRequestModel().update_reviewers(pull_request_id, reviewers_ids)
248 Session().commit()
248 Session().commit()
249 return True
249 return True
250 raise HTTPForbidden()
250 raise HTTPForbidden()
251
251
252 @NotAnonymous()
252 @NotAnonymous()
253 @jsonify
253 @jsonify
254 def delete(self, repo_name, pull_request_id):
254 def delete(self, repo_name, pull_request_id):
255 pull_request = PullRequest.get_or_404(pull_request_id)
255 pull_request = PullRequest.get_or_404(pull_request_id)
256 #only owner can delete it !
256 #only owner can delete it !
257 if pull_request.author.user_id == c.rhodecode_user.user_id:
257 if pull_request.author.user_id == c.rhodecode_user.user_id:
258 PullRequestModel().delete(pull_request)
258 PullRequestModel().delete(pull_request)
259 Session().commit()
259 Session().commit()
260 h.flash(_('Successfully deleted pull request'),
260 h.flash(_('Successfully deleted pull request'),
261 category='success')
261 category='success')
262 return redirect(url('admin_settings_my_account', anchor='pullrequests'))
262 return redirect(url('admin_settings_my_account', anchor='pullrequests'))
263 raise HTTPForbidden()
263 raise HTTPForbidden()
264
264
265 def _load_compare_data(self, pull_request, enable_comments=True):
265 def _load_compare_data(self, pull_request, enable_comments=True):
266 """
266 """
267 Load context data needed for generating compare diff
267 Load context data needed for generating compare diff
268
268
269 :param pull_request:
269 :param pull_request:
270 :type pull_request:
270 :type pull_request:
271 """
271 """
272 rev_start = request.GET.get('rev_start')
272 rev_start = request.GET.get('rev_start')
273 rev_end = request.GET.get('rev_end')
273 rev_end = request.GET.get('rev_end')
274
274
275 org_repo = pull_request.org_repo
275 org_repo = pull_request.org_repo
276 (org_ref_type,
276 (org_ref_type,
277 org_ref_name,
277 org_ref_name,
278 org_ref_rev) = pull_request.org_ref.split(':')
278 org_ref_rev) = pull_request.org_ref.split(':')
279
279
280 other_repo = org_repo
280 other_repo = org_repo
281 (other_ref_type,
281 (other_ref_type,
282 other_ref_name,
282 other_ref_name,
283 other_ref_rev) = pull_request.other_ref.split(':')
283 other_ref_rev) = pull_request.other_ref.split(':')
284
284
285 # despite opening revisions for bookmarks/branches/tags, we always
285 # despite opening revisions for bookmarks/branches/tags, we always
286 # convert this to rev to prevent changes after book or branch change
286 # convert this to rev to prevent changes after book or branch change
287 org_ref = ('rev', org_ref_rev)
287 org_ref = ('rev', org_ref_rev)
288 other_ref = ('rev', other_ref_rev)
288 other_ref = ('rev', other_ref_rev)
289
289
290 c.org_repo = org_repo
290 c.org_repo = org_repo
291 c.other_repo = other_repo
291 c.other_repo = other_repo
292
292
293 c.fulldiff = fulldiff = request.GET.get('fulldiff')
293 c.fulldiff = fulldiff = request.GET.get('fulldiff')
294
294
295 c.cs_ranges = [org_repo.get_changeset(x) for x in pull_request.revisions]
295 c.cs_ranges = [org_repo.get_changeset(x) for x in pull_request.revisions]
296
296
297 other_ref = ('rev', getattr(c.cs_ranges[0].parents[0]
297 other_ref = ('rev', getattr(c.cs_ranges[0].parents[0]
298 if c.cs_ranges[0].parents
298 if c.cs_ranges[0].parents
299 else EmptyChangeset(), 'raw_id'))
299 else EmptyChangeset(), 'raw_id'))
300
300
301 c.statuses = org_repo.statuses([x.raw_id for x in c.cs_ranges])
301 c.statuses = org_repo.statuses([x.raw_id for x in c.cs_ranges])
302 c.target_repo = c.repo_name
302 c.target_repo = other_repo.repo_name
303 # defines that we need hidden inputs with changesets
303 # defines that we need hidden inputs with changesets
304 c.as_form = request.GET.get('as_form', False)
304 c.as_form = request.GET.get('as_form', False)
305
305
306 c.org_ref = org_ref[1]
306 c.org_ref = org_ref[1]
307 c.other_ref = other_ref[1]
307 c.other_ref = other_ref[1]
308
308
309 diff_limit = self.cut_off_limit if not fulldiff else None
309 diff_limit = self.cut_off_limit if not fulldiff else None
310
310
311 #we swap org/other ref since we run a simple diff on one repo
311 #we swap org/other ref since we run a simple diff on one repo
312 _diff = diffs.differ(org_repo, other_ref, other_repo, org_ref)
312 _diff = diffs.differ(org_repo, other_ref, other_repo, org_ref)
313
313
314 diff_processor = diffs.DiffProcessor(_diff or '', format='gitdiff',
314 diff_processor = diffs.DiffProcessor(_diff or '', format='gitdiff',
315 diff_limit=diff_limit)
315 diff_limit=diff_limit)
316 _parsed = diff_processor.prepare()
316 _parsed = diff_processor.prepare()
317
317
318 c.limited_diff = False
318 c.limited_diff = False
319 if isinstance(_parsed, LimitedDiffContainer):
319 if isinstance(_parsed, LimitedDiffContainer):
320 c.limited_diff = True
320 c.limited_diff = True
321
321
322 c.files = []
322 c.files = []
323 c.changes = {}
323 c.changes = {}
324 c.lines_added = 0
324 c.lines_added = 0
325 c.lines_deleted = 0
325 c.lines_deleted = 0
326 for f in _parsed:
326 for f in _parsed:
327 st = f['stats']
327 st = f['stats']
328 if st[0] != 'b':
328 if st[0] != 'b':
329 c.lines_added += st[0]
329 c.lines_added += st[0]
330 c.lines_deleted += st[1]
330 c.lines_deleted += st[1]
331 fid = h.FID('', f['filename'])
331 fid = h.FID('', f['filename'])
332 c.files.append([fid, f['operation'], f['filename'], f['stats']])
332 c.files.append([fid, f['operation'], f['filename'], f['stats']])
333 diff = diff_processor.as_html(enable_comments=enable_comments,
333 diff = diff_processor.as_html(enable_comments=enable_comments,
334 parsed_lines=[f])
334 parsed_lines=[f])
335 c.changes[fid] = [f['operation'], f['filename'], diff]
335 c.changes[fid] = [f['operation'], f['filename'], diff]
336
336
337 def show(self, repo_name, pull_request_id):
337 def show(self, repo_name, pull_request_id):
338 repo_model = RepoModel()
338 repo_model = RepoModel()
339 c.users_array = repo_model.get_users_js()
339 c.users_array = repo_model.get_users_js()
340 c.users_groups_array = repo_model.get_users_groups_js()
340 c.users_groups_array = repo_model.get_users_groups_js()
341 c.pull_request = PullRequest.get_or_404(pull_request_id)
341 c.pull_request = PullRequest.get_or_404(pull_request_id)
342 c.target_repo = c.pull_request.org_repo.repo_name
343 c.allowed_to_change_status = self._get_is_allowed_change_status(c.pull_request)
342 c.allowed_to_change_status = self._get_is_allowed_change_status(c.pull_request)
344 cc_model = ChangesetCommentsModel()
343 cc_model = ChangesetCommentsModel()
345 cs_model = ChangesetStatusModel()
344 cs_model = ChangesetStatusModel()
346 _cs_statuses = cs_model.get_statuses(c.pull_request.org_repo,
345 _cs_statuses = cs_model.get_statuses(c.pull_request.org_repo,
347 pull_request=c.pull_request,
346 pull_request=c.pull_request,
348 with_revisions=True)
347 with_revisions=True)
349
348
350 cs_statuses = defaultdict(list)
349 cs_statuses = defaultdict(list)
351 for st in _cs_statuses:
350 for st in _cs_statuses:
352 cs_statuses[st.author.username] += [st]
351 cs_statuses[st.author.username] += [st]
353
352
354 c.pull_request_reviewers = []
353 c.pull_request_reviewers = []
355 c.pull_request_pending_reviewers = []
354 c.pull_request_pending_reviewers = []
356 for o in c.pull_request.reviewers:
355 for o in c.pull_request.reviewers:
357 st = cs_statuses.get(o.user.username, None)
356 st = cs_statuses.get(o.user.username, None)
358 if st:
357 if st:
359 sorter = lambda k: k.version
358 sorter = lambda k: k.version
360 st = [(x, list(y)[0])
359 st = [(x, list(y)[0])
361 for x, y in (groupby(sorted(st, key=sorter), sorter))]
360 for x, y in (groupby(sorted(st, key=sorter), sorter))]
362 else:
361 else:
363 c.pull_request_pending_reviewers.append(o.user)
362 c.pull_request_pending_reviewers.append(o.user)
364 c.pull_request_reviewers.append([o.user, st])
363 c.pull_request_reviewers.append([o.user, st])
365
364
366 # pull_requests repo_name we opened it against
365 # pull_requests repo_name we opened it against
367 # ie. other_repo must match
366 # ie. other_repo must match
368 if repo_name != c.pull_request.other_repo.repo_name:
367 if repo_name != c.pull_request.other_repo.repo_name:
369 raise HTTPNotFound
368 raise HTTPNotFound
370
369
371 # load compare data into template context
370 # load compare data into template context
372 enable_comments = not c.pull_request.is_closed()
371 enable_comments = not c.pull_request.is_closed()
373 self._load_compare_data(c.pull_request, enable_comments=enable_comments)
372 self._load_compare_data(c.pull_request, enable_comments=enable_comments)
374
373
375 # inline comments
374 # inline comments
376 c.inline_cnt = 0
375 c.inline_cnt = 0
377 c.inline_comments = cc_model.get_inline_comments(
376 c.inline_comments = cc_model.get_inline_comments(
378 c.rhodecode_db_repo.repo_id,
377 c.rhodecode_db_repo.repo_id,
379 pull_request=pull_request_id)
378 pull_request=pull_request_id)
380 # count inline comments
379 # count inline comments
381 for __, lines in c.inline_comments:
380 for __, lines in c.inline_comments:
382 for comments in lines.values():
381 for comments in lines.values():
383 c.inline_cnt += len(comments)
382 c.inline_cnt += len(comments)
384 # comments
383 # comments
385 c.comments = cc_model.get_comments(c.rhodecode_db_repo.repo_id,
384 c.comments = cc_model.get_comments(c.rhodecode_db_repo.repo_id,
386 pull_request=pull_request_id)
385 pull_request=pull_request_id)
387
386
388 try:
387 try:
389 cur_status = c.statuses[c.pull_request.revisions[0]][0]
388 cur_status = c.statuses[c.pull_request.revisions[0]][0]
390 except:
389 except:
391 log.error(traceback.format_exc())
390 log.error(traceback.format_exc())
392 cur_status = 'undefined'
391 cur_status = 'undefined'
393 if c.pull_request.is_closed() and 0:
392 if c.pull_request.is_closed() and 0:
394 c.current_changeset_status = cur_status
393 c.current_changeset_status = cur_status
395 else:
394 else:
396 # changeset(pull-request) status calulation based on reviewers
395 # changeset(pull-request) status calulation based on reviewers
397 c.current_changeset_status = cs_model.calculate_status(
396 c.current_changeset_status = cs_model.calculate_status(
398 c.pull_request_reviewers,
397 c.pull_request_reviewers,
399 )
398 )
400 c.changeset_statuses = ChangesetStatus.STATUSES
399 c.changeset_statuses = ChangesetStatus.STATUSES
401
400
402 return render('/pullrequests/pullrequest_show.html')
401 return render('/pullrequests/pullrequest_show.html')
403
402
404 @NotAnonymous()
403 @NotAnonymous()
405 @jsonify
404 @jsonify
406 def comment(self, repo_name, pull_request_id):
405 def comment(self, repo_name, pull_request_id):
407 pull_request = PullRequest.get_or_404(pull_request_id)
406 pull_request = PullRequest.get_or_404(pull_request_id)
408 if pull_request.is_closed():
407 if pull_request.is_closed():
409 raise HTTPForbidden()
408 raise HTTPForbidden()
410
409
411 status = request.POST.get('changeset_status')
410 status = request.POST.get('changeset_status')
412 change_status = request.POST.get('change_changeset_status')
411 change_status = request.POST.get('change_changeset_status')
413 text = request.POST.get('text')
412 text = request.POST.get('text')
414
413
415 allowed_to_change_status = self._get_is_allowed_change_status(pull_request)
414 allowed_to_change_status = self._get_is_allowed_change_status(pull_request)
416 if status and change_status and allowed_to_change_status:
415 if status and change_status and allowed_to_change_status:
417 text = text or (_('Status change -> %s')
416 text = text or (_('Status change -> %s')
418 % ChangesetStatus.get_status_lbl(status))
417 % ChangesetStatus.get_status_lbl(status))
419 comm = ChangesetCommentsModel().create(
418 comm = ChangesetCommentsModel().create(
420 text=text,
419 text=text,
421 repo=c.rhodecode_db_repo.repo_id,
420 repo=c.rhodecode_db_repo.repo_id,
422 user=c.rhodecode_user.user_id,
421 user=c.rhodecode_user.user_id,
423 pull_request=pull_request_id,
422 pull_request=pull_request_id,
424 f_path=request.POST.get('f_path'),
423 f_path=request.POST.get('f_path'),
425 line_no=request.POST.get('line'),
424 line_no=request.POST.get('line'),
426 status_change=(ChangesetStatus.get_status_lbl(status)
425 status_change=(ChangesetStatus.get_status_lbl(status)
427 if status and change_status and allowed_to_change_status else None)
426 if status and change_status and allowed_to_change_status else None)
428 )
427 )
429
428
430 action_logger(self.rhodecode_user,
429 action_logger(self.rhodecode_user,
431 'user_commented_pull_request:%s' % pull_request_id,
430 'user_commented_pull_request:%s' % pull_request_id,
432 c.rhodecode_db_repo, self.ip_addr, self.sa)
431 c.rhodecode_db_repo, self.ip_addr, self.sa)
433
432
434 if allowed_to_change_status:
433 if allowed_to_change_status:
435 # get status if set !
434 # get status if set !
436 if status and change_status:
435 if status and change_status:
437 ChangesetStatusModel().set_status(
436 ChangesetStatusModel().set_status(
438 c.rhodecode_db_repo.repo_id,
437 c.rhodecode_db_repo.repo_id,
439 status,
438 status,
440 c.rhodecode_user.user_id,
439 c.rhodecode_user.user_id,
441 comm,
440 comm,
442 pull_request=pull_request_id
441 pull_request=pull_request_id
443 )
442 )
444
443
445 if request.POST.get('save_close'):
444 if request.POST.get('save_close'):
446 if status in ['rejected', 'approved']:
445 if status in ['rejected', 'approved']:
447 PullRequestModel().close_pull_request(pull_request_id)
446 PullRequestModel().close_pull_request(pull_request_id)
448 action_logger(self.rhodecode_user,
447 action_logger(self.rhodecode_user,
449 'user_closed_pull_request:%s' % pull_request_id,
448 'user_closed_pull_request:%s' % pull_request_id,
450 c.rhodecode_db_repo, self.ip_addr, self.sa)
449 c.rhodecode_db_repo, self.ip_addr, self.sa)
451 else:
450 else:
452 h.flash(_('Closing pull request on other statuses than '
451 h.flash(_('Closing pull request on other statuses than '
453 'rejected or approved forbidden'),
452 'rejected or approved forbidden'),
454 category='warning')
453 category='warning')
455
454
456 Session().commit()
455 Session().commit()
457
456
458 if not request.environ.get('HTTP_X_PARTIAL_XHR'):
457 if not request.environ.get('HTTP_X_PARTIAL_XHR'):
459 return redirect(h.url('pullrequest_show', repo_name=repo_name,
458 return redirect(h.url('pullrequest_show', repo_name=repo_name,
460 pull_request_id=pull_request_id))
459 pull_request_id=pull_request_id))
461
460
462 data = {
461 data = {
463 'target_id': h.safeid(h.safe_unicode(request.POST.get('f_path'))),
462 'target_id': h.safeid(h.safe_unicode(request.POST.get('f_path'))),
464 }
463 }
465 if comm:
464 if comm:
466 c.co = comm
465 c.co = comm
467 data.update(comm.get_dict())
466 data.update(comm.get_dict())
468 data.update({'rendered_text':
467 data.update({'rendered_text':
469 render('changeset/changeset_comment_block.html')})
468 render('changeset/changeset_comment_block.html')})
470
469
471 return data
470 return data
472
471
473 @NotAnonymous()
472 @NotAnonymous()
474 @jsonify
473 @jsonify
475 def delete_comment(self, repo_name, comment_id):
474 def delete_comment(self, repo_name, comment_id):
476 co = ChangesetComment.get(comment_id)
475 co = ChangesetComment.get(comment_id)
477 if co.pull_request.is_closed():
476 if co.pull_request.is_closed():
478 #don't allow deleting comments on closed pull request
477 #don't allow deleting comments on closed pull request
479 raise HTTPForbidden()
478 raise HTTPForbidden()
480
479
481 owner = lambda: co.author.user_id == c.rhodecode_user.user_id
480 owner = lambda: co.author.user_id == c.rhodecode_user.user_id
482 if h.HasPermissionAny('hg.admin', 'repository.admin')() or owner:
481 if h.HasPermissionAny('hg.admin', 'repository.admin')() or owner:
483 ChangesetCommentsModel().delete(comment=co)
482 ChangesetCommentsModel().delete(comment=co)
484 Session().commit()
483 Session().commit()
485 return True
484 return True
486 else:
485 else:
487 raise HTTPForbidden()
486 raise HTTPForbidden()
General Comments 0
You need to be logged in to leave comments. Login now