##// END OF EJS Templates
pullrequests: don't pass unicode strings to Mercurial
Mads Kiilerich -
r3719:3534e75b beta
parent child Browse files
Show More
@@ -1,517 +1,521 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.helpers import Page
41 from rhodecode.lib.helpers import Page
42 from rhodecode.lib import helpers as h
42 from rhodecode.lib import helpers as h
43 from rhodecode.lib import diffs
43 from rhodecode.lib import diffs
44 from rhodecode.lib.utils import action_logger, jsonify
44 from rhodecode.lib.utils import action_logger, jsonify
45 from rhodecode.lib.vcs.utils import safe_str
45 from rhodecode.lib.vcs.utils import safe_str
46 from rhodecode.lib.vcs.exceptions import EmptyRepositoryError
46 from rhodecode.lib.vcs.exceptions import EmptyRepositoryError
47 from rhodecode.lib.vcs.backends.base import EmptyChangeset
47 from rhodecode.lib.vcs.backends.base import EmptyChangeset
48 from rhodecode.lib.diffs import LimitedDiffContainer
48 from rhodecode.lib.diffs import LimitedDiffContainer
49 from rhodecode.model.db import User, PullRequest, ChangesetStatus,\
49 from rhodecode.model.db import User, PullRequest, ChangesetStatus,\
50 ChangesetComment
50 ChangesetComment
51 from rhodecode.model.pull_request import PullRequestModel
51 from rhodecode.model.pull_request import PullRequestModel
52 from rhodecode.model.meta import Session
52 from rhodecode.model.meta import Session
53 from rhodecode.model.repo import RepoModel
53 from rhodecode.model.repo import RepoModel
54 from rhodecode.model.comment import ChangesetCommentsModel
54 from rhodecode.model.comment import ChangesetCommentsModel
55 from rhodecode.model.changeset_status import ChangesetStatusModel
55 from rhodecode.model.changeset_status import ChangesetStatusModel
56 from rhodecode.model.forms import PullRequestForm
56 from rhodecode.model.forms import PullRequestForm
57 from mercurial import scmutil
57 from mercurial import scmutil
58 from rhodecode.lib.utils2 import safe_int
58 from rhodecode.lib.utils2 import safe_int
59
59
60 log = logging.getLogger(__name__)
60 log = logging.getLogger(__name__)
61
61
62
62
63 class PullrequestsController(BaseRepoController):
63 class PullrequestsController(BaseRepoController):
64
64
65 @LoginRequired()
65 @LoginRequired()
66 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
66 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
67 'repository.admin')
67 'repository.admin')
68 def __before__(self):
68 def __before__(self):
69 super(PullrequestsController, self).__before__()
69 super(PullrequestsController, self).__before__()
70 repo_model = RepoModel()
70 repo_model = RepoModel()
71 c.users_array = repo_model.get_users_js()
71 c.users_array = repo_model.get_users_js()
72 c.users_groups_array = repo_model.get_users_groups_js()
72 c.users_groups_array = repo_model.get_users_groups_js()
73
73
74 def _get_repo_refs(self, repo, rev=None, branch_rev=None):
74 def _get_repo_refs(self, repo, rev=None, branch_rev=None):
75 """return a structure with repo's interesting changesets, suitable for
75 """return a structure with repo's interesting changesets, suitable for
76 the selectors in pullrequest.html"""
76 the selectors in pullrequest.html"""
77
78 # list named branches that has been merged to this named branch - it should probably merge back
77 # list named branches that has been merged to this named branch - it should probably merge back
79 peers = []
78 peers = []
79
80 if rev:
81 rev = safe_str(rev)
82
80 if branch_rev:
83 if branch_rev:
84 branch_rev = safe_str(branch_rev)
81 # not restricting to merge() would also get branch point and be better
85 # not restricting to merge() would also get branch point and be better
82 # (especially because it would get the branch point) ... but is currently too expensive
86 # (especially because it would get the branch point) ... but is currently too expensive
83 revs = ["sort(parents(branch(id('%s')) and merge()) - branch(id('%s')))" %
87 revs = ["sort(parents(branch(id('%s')) and merge()) - branch(id('%s')))" %
84 (branch_rev, branch_rev)]
88 (branch_rev, branch_rev)]
85 otherbranches = {}
89 otherbranches = {}
86 for i in scmutil.revrange(repo._repo, revs):
90 for i in scmutil.revrange(repo._repo, revs):
87 cs = repo.get_changeset(i)
91 cs = repo.get_changeset(i)
88 otherbranches[cs.branch] = cs.raw_id
92 otherbranches[cs.branch] = cs.raw_id
89 for branch, node in otherbranches.iteritems():
93 for branch, node in otherbranches.iteritems():
90 selected = 'branch:%s:%s' % (branch, node)
94 selected = 'branch:%s:%s' % (branch, node)
91 peers.append((selected, branch))
95 peers.append((selected, branch))
92
96
93 selected = None
97 selected = None
94 branches = []
98 branches = []
95 for branch, branchrev in repo.branches.iteritems():
99 for branch, branchrev in repo.branches.iteritems():
96 n = 'branch:%s:%s' % (branch, branchrev)
100 n = 'branch:%s:%s' % (branch, branchrev)
97 branches.append((n, branch))
101 branches.append((n, branch))
98 if rev == branchrev:
102 if rev == branchrev:
99 selected = n
103 selected = n
100 bookmarks = []
104 bookmarks = []
101 for bookmark, bookmarkrev in repo.bookmarks.iteritems():
105 for bookmark, bookmarkrev in repo.bookmarks.iteritems():
102 n = 'book:%s:%s' % (bookmark, bookmarkrev)
106 n = 'book:%s:%s' % (bookmark, bookmarkrev)
103 bookmarks.append((n, bookmark))
107 bookmarks.append((n, bookmark))
104 if rev == bookmarkrev:
108 if rev == bookmarkrev:
105 selected = n
109 selected = n
106 tags = []
110 tags = []
107 for tag, tagrev in repo.tags.iteritems():
111 for tag, tagrev in repo.tags.iteritems():
108 n = 'tag:%s:%s' % (tag, tagrev)
112 n = 'tag:%s:%s' % (tag, tagrev)
109 tags.append((n, tag))
113 tags.append((n, tag))
110 if rev == tagrev and tag != 'tip': # tip is not a real tag - and its branch is better
114 if rev == tagrev and tag != 'tip': # tip is not a real tag - and its branch is better
111 selected = n
115 selected = n
112
116
113 # prio 1: rev was selected as existing entry above
117 # prio 1: rev was selected as existing entry above
114
118
115 # prio 2: create special entry for rev; rev _must_ be used
119 # prio 2: create special entry for rev; rev _must_ be used
116 specials = []
120 specials = []
117 if rev and selected is None:
121 if rev and selected is None:
118 selected = 'rev:%s:%s' % (rev, rev)
122 selected = 'rev:%s:%s' % (rev, rev)
119 specials = [(selected, '%s: %s' % (_("Changeset"), rev[:12]))]
123 specials = [(selected, '%s: %s' % (_("Changeset"), rev[:12]))]
120
124
121 # prio 3: most recent peer branch
125 # prio 3: most recent peer branch
122 if peers and not selected:
126 if peers and not selected:
123 selected = peers[0][0][0]
127 selected = peers[0][0][0]
124
128
125 # prio 4: tip revision
129 # prio 4: tip revision
126 if not selected:
130 if not selected:
127 selected = 'tag:tip:%s' % repo.tags['tip']
131 selected = 'tag:tip:%s' % repo.tags['tip']
128
132
129 groups = [(specials, _("Special")),
133 groups = [(specials, _("Special")),
130 (peers, _("Peer branches")),
134 (peers, _("Peer branches")),
131 (bookmarks, _("Bookmarks")),
135 (bookmarks, _("Bookmarks")),
132 (branches, _("Branches")),
136 (branches, _("Branches")),
133 (tags, _("Tags")),
137 (tags, _("Tags")),
134 ]
138 ]
135 return [g for g in groups if g[0]], selected
139 return [g for g in groups if g[0]], selected
136
140
137 def _get_is_allowed_change_status(self, pull_request):
141 def _get_is_allowed_change_status(self, pull_request):
138 owner = self.rhodecode_user.user_id == pull_request.user_id
142 owner = self.rhodecode_user.user_id == pull_request.user_id
139 reviewer = self.rhodecode_user.user_id in [x.user_id for x in
143 reviewer = self.rhodecode_user.user_id in [x.user_id for x in
140 pull_request.reviewers]
144 pull_request.reviewers]
141 return (self.rhodecode_user.admin or owner or reviewer)
145 return (self.rhodecode_user.admin or owner or reviewer)
142
146
143 def show_all(self, repo_name):
147 def show_all(self, repo_name):
144 c.pull_requests = PullRequestModel().get_all(repo_name)
148 c.pull_requests = PullRequestModel().get_all(repo_name)
145 c.repo_name = repo_name
149 c.repo_name = repo_name
146 p = safe_int(request.params.get('page', 1), 1)
150 p = safe_int(request.params.get('page', 1), 1)
147
151
148 c.pullrequests_pager = Page(c.pull_requests, page=p, items_per_page=10)
152 c.pullrequests_pager = Page(c.pull_requests, page=p, items_per_page=10)
149
153
150 c.pullrequest_data = render('/pullrequests/pullrequest_data.html')
154 c.pullrequest_data = render('/pullrequests/pullrequest_data.html')
151
155
152 if request.environ.get('HTTP_X_PARTIAL_XHR'):
156 if request.environ.get('HTTP_X_PARTIAL_XHR'):
153 return c.pullrequest_data
157 return c.pullrequest_data
154
158
155 return render('/pullrequests/pullrequest_show_all.html')
159 return render('/pullrequests/pullrequest_show_all.html')
156
160
157 @NotAnonymous()
161 @NotAnonymous()
158 def index(self):
162 def index(self):
159 org_repo = c.rhodecode_db_repo
163 org_repo = c.rhodecode_db_repo
160
164
161 if org_repo.scm_instance.alias != 'hg':
165 if org_repo.scm_instance.alias != 'hg':
162 log.error('Review not available for GIT REPOS')
166 log.error('Review not available for GIT REPOS')
163 raise HTTPNotFound
167 raise HTTPNotFound
164
168
165 try:
169 try:
166 org_repo.scm_instance.get_changeset()
170 org_repo.scm_instance.get_changeset()
167 except EmptyRepositoryError, e:
171 except EmptyRepositoryError, e:
168 h.flash(h.literal(_('There are no changesets yet')),
172 h.flash(h.literal(_('There are no changesets yet')),
169 category='warning')
173 category='warning')
170 redirect(url('summary_home', repo_name=org_repo.repo_name))
174 redirect(url('summary_home', repo_name=org_repo.repo_name))
171
175
172 org_rev = request.GET.get('rev_end')
176 org_rev = request.GET.get('rev_end')
173 # rev_start is not directly useful - its parent could however be used
177 # rev_start is not directly useful - its parent could however be used
174 # as default for other and thus give a simple compare view
178 # as default for other and thus give a simple compare view
175 #other_rev = request.POST.get('rev_start')
179 #other_rev = request.POST.get('rev_start')
176
180
177 c.org_repos = []
181 c.org_repos = []
178 c.org_repos.append((org_repo.repo_name, org_repo.repo_name))
182 c.org_repos.append((org_repo.repo_name, org_repo.repo_name))
179 c.default_org_repo = org_repo.repo_name
183 c.default_org_repo = org_repo.repo_name
180 c.org_refs, c.default_org_ref = self._get_repo_refs(org_repo.scm_instance, org_rev)
184 c.org_refs, c.default_org_ref = self._get_repo_refs(org_repo.scm_instance, org_rev)
181
185
182 c.other_repos = []
186 c.other_repos = []
183 other_repos_info = {}
187 other_repos_info = {}
184
188
185 def add_other_repo(repo, branch_rev=None):
189 def add_other_repo(repo, branch_rev=None):
186 if repo.repo_name in other_repos_info: # shouldn't happen
190 if repo.repo_name in other_repos_info: # shouldn't happen
187 return
191 return
188 c.other_repos.append((repo.repo_name, repo.repo_name))
192 c.other_repos.append((repo.repo_name, repo.repo_name))
189 other_refs, selected_other_ref = self._get_repo_refs(repo.scm_instance, branch_rev=branch_rev)
193 other_refs, selected_other_ref = self._get_repo_refs(repo.scm_instance, branch_rev=branch_rev)
190 other_repos_info[repo.repo_name] = {
194 other_repos_info[repo.repo_name] = {
191 'user': dict(user_id=repo.user.user_id,
195 'user': dict(user_id=repo.user.user_id,
192 username=repo.user.username,
196 username=repo.user.username,
193 firstname=repo.user.firstname,
197 firstname=repo.user.firstname,
194 lastname=repo.user.lastname,
198 lastname=repo.user.lastname,
195 gravatar_link=h.gravatar_url(repo.user.email, 14)),
199 gravatar_link=h.gravatar_url(repo.user.email, 14)),
196 'description': repo.description.split('\n', 1)[0],
200 'description': repo.description.split('\n', 1)[0],
197 'revs': h.select('other_ref', selected_other_ref, other_refs, class_='refs')
201 'revs': h.select('other_ref', selected_other_ref, other_refs, class_='refs')
198 }
202 }
199
203
200 # add org repo to other so we can open pull request against peer branches on itself
204 # add org repo to other so we can open pull request against peer branches on itself
201 add_other_repo(org_repo, branch_rev=org_rev)
205 add_other_repo(org_repo, branch_rev=org_rev)
202 c.default_other_repo = org_repo.repo_name
206 c.default_other_repo = org_repo.repo_name
203
207
204 # gather forks and add to this list ... even though it is rare to
208 # gather forks and add to this list ... even though it is rare to
205 # request forks to pull from their parent
209 # request forks to pull from their parent
206 for fork in org_repo.forks:
210 for fork in org_repo.forks:
207 add_other_repo(fork)
211 add_other_repo(fork)
208
212
209 # add parents of this fork also, but only if it's not empty
213 # add parents of this fork also, but only if it's not empty
210 if org_repo.parent and org_repo.parent.scm_instance.revisions:
214 if org_repo.parent and org_repo.parent.scm_instance.revisions:
211 add_other_repo(org_repo.parent)
215 add_other_repo(org_repo.parent)
212 c.default_other_repo = org_repo.parent.repo_name
216 c.default_other_repo = org_repo.parent.repo_name
213
217
214 c.default_other_repo_info = other_repos_info[c.default_other_repo]
218 c.default_other_repo_info = other_repos_info[c.default_other_repo]
215 c.other_repos_info = json.dumps(other_repos_info)
219 c.other_repos_info = json.dumps(other_repos_info)
216
220
217 return render('/pullrequests/pullrequest.html')
221 return render('/pullrequests/pullrequest.html')
218
222
219 @NotAnonymous()
223 @NotAnonymous()
220 def create(self, repo_name):
224 def create(self, repo_name):
221 repo = RepoModel()._get_repo(repo_name)
225 repo = RepoModel()._get_repo(repo_name)
222 try:
226 try:
223 _form = PullRequestForm(repo.repo_id)().to_python(request.POST)
227 _form = PullRequestForm(repo.repo_id)().to_python(request.POST)
224 except formencode.Invalid, errors:
228 except formencode.Invalid, errors:
225 log.error(traceback.format_exc())
229 log.error(traceback.format_exc())
226 if errors.error_dict.get('revisions'):
230 if errors.error_dict.get('revisions'):
227 msg = 'Revisions: %s' % errors.error_dict['revisions']
231 msg = 'Revisions: %s' % errors.error_dict['revisions']
228 elif errors.error_dict.get('pullrequest_title'):
232 elif errors.error_dict.get('pullrequest_title'):
229 msg = _('Pull request requires a title with min. 3 chars')
233 msg = _('Pull request requires a title with min. 3 chars')
230 else:
234 else:
231 msg = _('Error creating pull request')
235 msg = _('Error creating pull request')
232
236
233 h.flash(msg, 'error')
237 h.flash(msg, 'error')
234 return redirect(url('pullrequest_home', repo_name=repo_name))
238 return redirect(url('pullrequest_home', repo_name=repo_name))
235
239
236 org_repo = _form['org_repo']
240 org_repo = _form['org_repo']
237 org_ref = 'rev:merge:%s' % _form['merge_rev']
241 org_ref = 'rev:merge:%s' % _form['merge_rev']
238 other_repo = _form['other_repo']
242 other_repo = _form['other_repo']
239 other_ref = 'rev:ancestor:%s' % _form['ancestor_rev']
243 other_ref = 'rev:ancestor:%s' % _form['ancestor_rev']
240 revisions = _form['revisions']
244 revisions = _form['revisions']
241 reviewers = _form['review_members']
245 reviewers = _form['review_members']
242
246
243 title = _form['pullrequest_title']
247 title = _form['pullrequest_title']
244 description = _form['pullrequest_desc']
248 description = _form['pullrequest_desc']
245
249
246 try:
250 try:
247 pull_request = PullRequestModel().create(
251 pull_request = PullRequestModel().create(
248 self.rhodecode_user.user_id, org_repo, org_ref, other_repo,
252 self.rhodecode_user.user_id, org_repo, org_ref, other_repo,
249 other_ref, revisions, reviewers, title, description
253 other_ref, revisions, reviewers, title, description
250 )
254 )
251 Session().commit()
255 Session().commit()
252 h.flash(_('Successfully opened new pull request'),
256 h.flash(_('Successfully opened new pull request'),
253 category='success')
257 category='success')
254 except Exception:
258 except Exception:
255 h.flash(_('Error occurred during sending pull request'),
259 h.flash(_('Error occurred during sending pull request'),
256 category='error')
260 category='error')
257 log.error(traceback.format_exc())
261 log.error(traceback.format_exc())
258 return redirect(url('pullrequest_home', repo_name=repo_name))
262 return redirect(url('pullrequest_home', repo_name=repo_name))
259
263
260 return redirect(url('pullrequest_show', repo_name=other_repo,
264 return redirect(url('pullrequest_show', repo_name=other_repo,
261 pull_request_id=pull_request.pull_request_id))
265 pull_request_id=pull_request.pull_request_id))
262
266
263 @NotAnonymous()
267 @NotAnonymous()
264 @jsonify
268 @jsonify
265 def update(self, repo_name, pull_request_id):
269 def update(self, repo_name, pull_request_id):
266 pull_request = PullRequest.get_or_404(pull_request_id)
270 pull_request = PullRequest.get_or_404(pull_request_id)
267 if pull_request.is_closed():
271 if pull_request.is_closed():
268 raise HTTPForbidden()
272 raise HTTPForbidden()
269 #only owner or admin can update it
273 #only owner or admin can update it
270 owner = pull_request.author.user_id == c.rhodecode_user.user_id
274 owner = pull_request.author.user_id == c.rhodecode_user.user_id
271 if h.HasPermissionAny('hg.admin', 'repository.admin')() or owner:
275 if h.HasPermissionAny('hg.admin', 'repository.admin')() or owner:
272 reviewers_ids = map(int, filter(lambda v: v not in [None, ''],
276 reviewers_ids = map(int, filter(lambda v: v not in [None, ''],
273 request.POST.get('reviewers_ids', '').split(',')))
277 request.POST.get('reviewers_ids', '').split(',')))
274
278
275 PullRequestModel().update_reviewers(pull_request_id, reviewers_ids)
279 PullRequestModel().update_reviewers(pull_request_id, reviewers_ids)
276 Session().commit()
280 Session().commit()
277 return True
281 return True
278 raise HTTPForbidden()
282 raise HTTPForbidden()
279
283
280 @NotAnonymous()
284 @NotAnonymous()
281 @jsonify
285 @jsonify
282 def delete(self, repo_name, pull_request_id):
286 def delete(self, repo_name, pull_request_id):
283 pull_request = PullRequest.get_or_404(pull_request_id)
287 pull_request = PullRequest.get_or_404(pull_request_id)
284 #only owner can delete it !
288 #only owner can delete it !
285 if pull_request.author.user_id == c.rhodecode_user.user_id:
289 if pull_request.author.user_id == c.rhodecode_user.user_id:
286 PullRequestModel().delete(pull_request)
290 PullRequestModel().delete(pull_request)
287 Session().commit()
291 Session().commit()
288 h.flash(_('Successfully deleted pull request'),
292 h.flash(_('Successfully deleted pull request'),
289 category='success')
293 category='success')
290 return redirect(url('admin_settings_my_account', anchor='pullrequests'))
294 return redirect(url('admin_settings_my_account', anchor='pullrequests'))
291 raise HTTPForbidden()
295 raise HTTPForbidden()
292
296
293 def _load_compare_data(self, pull_request, enable_comments=True):
297 def _load_compare_data(self, pull_request, enable_comments=True):
294 """
298 """
295 Load context data needed for generating compare diff
299 Load context data needed for generating compare diff
296
300
297 :param pull_request:
301 :param pull_request:
298 :type pull_request:
302 :type pull_request:
299 """
303 """
300 org_repo = pull_request.org_repo
304 org_repo = pull_request.org_repo
301 (org_ref_type,
305 (org_ref_type,
302 org_ref_name,
306 org_ref_name,
303 org_ref_rev) = pull_request.org_ref.split(':')
307 org_ref_rev) = pull_request.org_ref.split(':')
304
308
305 other_repo = org_repo
309 other_repo = org_repo
306 (other_ref_type,
310 (other_ref_type,
307 other_ref_name,
311 other_ref_name,
308 other_ref_rev) = pull_request.other_ref.split(':')
312 other_ref_rev) = pull_request.other_ref.split(':')
309
313
310 # despite opening revisions for bookmarks/branches/tags, we always
314 # despite opening revisions for bookmarks/branches/tags, we always
311 # convert this to rev to prevent changes after bookmark or branch change
315 # convert this to rev to prevent changes after bookmark or branch change
312 org_ref = ('rev', org_ref_rev)
316 org_ref = ('rev', org_ref_rev)
313 other_ref = ('rev', other_ref_rev)
317 other_ref = ('rev', other_ref_rev)
314
318
315 c.org_repo = org_repo
319 c.org_repo = org_repo
316 c.other_repo = other_repo
320 c.other_repo = other_repo
317
321
318 c.fulldiff = fulldiff = request.GET.get('fulldiff')
322 c.fulldiff = fulldiff = request.GET.get('fulldiff')
319
323
320 c.cs_ranges = [org_repo.get_changeset(x) for x in pull_request.revisions]
324 c.cs_ranges = [org_repo.get_changeset(x) for x in pull_request.revisions]
321
325
322 c.statuses = org_repo.statuses([x.raw_id for x in c.cs_ranges])
326 c.statuses = org_repo.statuses([x.raw_id for x in c.cs_ranges])
323
327
324 c.org_ref = org_ref[1]
328 c.org_ref = org_ref[1]
325 c.org_ref_type = org_ref[0]
329 c.org_ref_type = org_ref[0]
326 c.other_ref = other_ref[1]
330 c.other_ref = other_ref[1]
327 c.other_ref_type = other_ref[0]
331 c.other_ref_type = other_ref[0]
328
332
329 diff_limit = self.cut_off_limit if not fulldiff else None
333 diff_limit = self.cut_off_limit if not fulldiff else None
330
334
331 #we swap org/other ref since we run a simple diff on one repo
335 #we swap org/other ref since we run a simple diff on one repo
332 log.debug('running diff between %s@%s and %s@%s'
336 log.debug('running diff between %s@%s and %s@%s'
333 % (org_repo.scm_instance.path, org_ref,
337 % (org_repo.scm_instance.path, org_ref,
334 other_repo.scm_instance.path, other_ref))
338 other_repo.scm_instance.path, other_ref))
335 _diff = org_repo.scm_instance.get_diff(rev1=safe_str(org_ref[1]), rev2=safe_str(other_ref[1]))
339 _diff = org_repo.scm_instance.get_diff(rev1=safe_str(org_ref[1]), rev2=safe_str(other_ref[1]))
336
340
337 diff_processor = diffs.DiffProcessor(_diff or '', format='gitdiff',
341 diff_processor = diffs.DiffProcessor(_diff or '', format='gitdiff',
338 diff_limit=diff_limit)
342 diff_limit=diff_limit)
339 _parsed = diff_processor.prepare()
343 _parsed = diff_processor.prepare()
340
344
341 c.limited_diff = False
345 c.limited_diff = False
342 if isinstance(_parsed, LimitedDiffContainer):
346 if isinstance(_parsed, LimitedDiffContainer):
343 c.limited_diff = True
347 c.limited_diff = True
344
348
345 c.files = []
349 c.files = []
346 c.changes = {}
350 c.changes = {}
347 c.lines_added = 0
351 c.lines_added = 0
348 c.lines_deleted = 0
352 c.lines_deleted = 0
349 for f in _parsed:
353 for f in _parsed:
350 st = f['stats']
354 st = f['stats']
351 if st[0] != 'b':
355 if st[0] != 'b':
352 c.lines_added += st[0]
356 c.lines_added += st[0]
353 c.lines_deleted += st[1]
357 c.lines_deleted += st[1]
354 fid = h.FID('', f['filename'])
358 fid = h.FID('', f['filename'])
355 c.files.append([fid, f['operation'], f['filename'], f['stats']])
359 c.files.append([fid, f['operation'], f['filename'], f['stats']])
356 diff = diff_processor.as_html(enable_comments=enable_comments,
360 diff = diff_processor.as_html(enable_comments=enable_comments,
357 parsed_lines=[f])
361 parsed_lines=[f])
358 c.changes[fid] = [f['operation'], f['filename'], diff]
362 c.changes[fid] = [f['operation'], f['filename'], diff]
359
363
360 def show(self, repo_name, pull_request_id):
364 def show(self, repo_name, pull_request_id):
361 repo_model = RepoModel()
365 repo_model = RepoModel()
362 c.users_array = repo_model.get_users_js()
366 c.users_array = repo_model.get_users_js()
363 c.users_groups_array = repo_model.get_users_groups_js()
367 c.users_groups_array = repo_model.get_users_groups_js()
364 c.pull_request = PullRequest.get_or_404(pull_request_id)
368 c.pull_request = PullRequest.get_or_404(pull_request_id)
365 c.allowed_to_change_status = self._get_is_allowed_change_status(c.pull_request)
369 c.allowed_to_change_status = self._get_is_allowed_change_status(c.pull_request)
366 cc_model = ChangesetCommentsModel()
370 cc_model = ChangesetCommentsModel()
367 cs_model = ChangesetStatusModel()
371 cs_model = ChangesetStatusModel()
368 _cs_statuses = cs_model.get_statuses(c.pull_request.org_repo,
372 _cs_statuses = cs_model.get_statuses(c.pull_request.org_repo,
369 pull_request=c.pull_request,
373 pull_request=c.pull_request,
370 with_revisions=True)
374 with_revisions=True)
371
375
372 cs_statuses = defaultdict(list)
376 cs_statuses = defaultdict(list)
373 for st in _cs_statuses:
377 for st in _cs_statuses:
374 cs_statuses[st.author.username] += [st]
378 cs_statuses[st.author.username] += [st]
375
379
376 c.pull_request_reviewers = []
380 c.pull_request_reviewers = []
377 c.pull_request_pending_reviewers = []
381 c.pull_request_pending_reviewers = []
378 for o in c.pull_request.reviewers:
382 for o in c.pull_request.reviewers:
379 st = cs_statuses.get(o.user.username, None)
383 st = cs_statuses.get(o.user.username, None)
380 if st:
384 if st:
381 sorter = lambda k: k.version
385 sorter = lambda k: k.version
382 st = [(x, list(y)[0])
386 st = [(x, list(y)[0])
383 for x, y in (groupby(sorted(st, key=sorter), sorter))]
387 for x, y in (groupby(sorted(st, key=sorter), sorter))]
384 else:
388 else:
385 c.pull_request_pending_reviewers.append(o.user)
389 c.pull_request_pending_reviewers.append(o.user)
386 c.pull_request_reviewers.append([o.user, st])
390 c.pull_request_reviewers.append([o.user, st])
387
391
388 # pull_requests repo_name we opened it against
392 # pull_requests repo_name we opened it against
389 # ie. other_repo must match
393 # ie. other_repo must match
390 if repo_name != c.pull_request.other_repo.repo_name:
394 if repo_name != c.pull_request.other_repo.repo_name:
391 raise HTTPNotFound
395 raise HTTPNotFound
392
396
393 # load compare data into template context
397 # load compare data into template context
394 enable_comments = not c.pull_request.is_closed()
398 enable_comments = not c.pull_request.is_closed()
395 self._load_compare_data(c.pull_request, enable_comments=enable_comments)
399 self._load_compare_data(c.pull_request, enable_comments=enable_comments)
396
400
397 # inline comments
401 # inline comments
398 c.inline_cnt = 0
402 c.inline_cnt = 0
399 c.inline_comments = cc_model.get_inline_comments(
403 c.inline_comments = cc_model.get_inline_comments(
400 c.rhodecode_db_repo.repo_id,
404 c.rhodecode_db_repo.repo_id,
401 pull_request=pull_request_id)
405 pull_request=pull_request_id)
402 # count inline comments
406 # count inline comments
403 for __, lines in c.inline_comments:
407 for __, lines in c.inline_comments:
404 for comments in lines.values():
408 for comments in lines.values():
405 c.inline_cnt += len(comments)
409 c.inline_cnt += len(comments)
406 # comments
410 # comments
407 c.comments = cc_model.get_comments(c.rhodecode_db_repo.repo_id,
411 c.comments = cc_model.get_comments(c.rhodecode_db_repo.repo_id,
408 pull_request=pull_request_id)
412 pull_request=pull_request_id)
409
413
410 try:
414 try:
411 cur_status = c.statuses[c.pull_request.revisions[0]][0]
415 cur_status = c.statuses[c.pull_request.revisions[0]][0]
412 except Exception:
416 except Exception:
413 log.error(traceback.format_exc())
417 log.error(traceback.format_exc())
414 cur_status = 'undefined'
418 cur_status = 'undefined'
415 if c.pull_request.is_closed() and 0:
419 if c.pull_request.is_closed() and 0:
416 c.current_changeset_status = cur_status
420 c.current_changeset_status = cur_status
417 else:
421 else:
418 # changeset(pull-request) status calulation based on reviewers
422 # changeset(pull-request) status calulation based on reviewers
419 c.current_changeset_status = cs_model.calculate_status(
423 c.current_changeset_status = cs_model.calculate_status(
420 c.pull_request_reviewers,
424 c.pull_request_reviewers,
421 )
425 )
422 c.changeset_statuses = ChangesetStatus.STATUSES
426 c.changeset_statuses = ChangesetStatus.STATUSES
423
427
424 c.as_form = False
428 c.as_form = False
425 c.ancestor = None # there is one - but right here we don't know which
429 c.ancestor = None # there is one - but right here we don't know which
426 return render('/pullrequests/pullrequest_show.html')
430 return render('/pullrequests/pullrequest_show.html')
427
431
428 @NotAnonymous()
432 @NotAnonymous()
429 @jsonify
433 @jsonify
430 def comment(self, repo_name, pull_request_id):
434 def comment(self, repo_name, pull_request_id):
431 pull_request = PullRequest.get_or_404(pull_request_id)
435 pull_request = PullRequest.get_or_404(pull_request_id)
432 if pull_request.is_closed():
436 if pull_request.is_closed():
433 raise HTTPForbidden()
437 raise HTTPForbidden()
434
438
435 status = request.POST.get('changeset_status')
439 status = request.POST.get('changeset_status')
436 change_status = request.POST.get('change_changeset_status')
440 change_status = request.POST.get('change_changeset_status')
437 text = request.POST.get('text')
441 text = request.POST.get('text')
438 close_pr = request.POST.get('save_close')
442 close_pr = request.POST.get('save_close')
439
443
440 allowed_to_change_status = self._get_is_allowed_change_status(pull_request)
444 allowed_to_change_status = self._get_is_allowed_change_status(pull_request)
441 if status and change_status and allowed_to_change_status:
445 if status and change_status and allowed_to_change_status:
442 _def = (_('Status change -> %s')
446 _def = (_('Status change -> %s')
443 % ChangesetStatus.get_status_lbl(status))
447 % ChangesetStatus.get_status_lbl(status))
444 if close_pr:
448 if close_pr:
445 _def = _('Closing with') + ' ' + _def
449 _def = _('Closing with') + ' ' + _def
446 text = text or _def
450 text = text or _def
447 comm = ChangesetCommentsModel().create(
451 comm = ChangesetCommentsModel().create(
448 text=text,
452 text=text,
449 repo=c.rhodecode_db_repo.repo_id,
453 repo=c.rhodecode_db_repo.repo_id,
450 user=c.rhodecode_user.user_id,
454 user=c.rhodecode_user.user_id,
451 pull_request=pull_request_id,
455 pull_request=pull_request_id,
452 f_path=request.POST.get('f_path'),
456 f_path=request.POST.get('f_path'),
453 line_no=request.POST.get('line'),
457 line_no=request.POST.get('line'),
454 status_change=(ChangesetStatus.get_status_lbl(status)
458 status_change=(ChangesetStatus.get_status_lbl(status)
455 if status and change_status
459 if status and change_status
456 and allowed_to_change_status else None),
460 and allowed_to_change_status else None),
457 closing_pr=close_pr
461 closing_pr=close_pr
458 )
462 )
459
463
460 action_logger(self.rhodecode_user,
464 action_logger(self.rhodecode_user,
461 'user_commented_pull_request:%s' % pull_request_id,
465 'user_commented_pull_request:%s' % pull_request_id,
462 c.rhodecode_db_repo, self.ip_addr, self.sa)
466 c.rhodecode_db_repo, self.ip_addr, self.sa)
463
467
464 if allowed_to_change_status:
468 if allowed_to_change_status:
465 # get status if set !
469 # get status if set !
466 if status and change_status:
470 if status and change_status:
467 ChangesetStatusModel().set_status(
471 ChangesetStatusModel().set_status(
468 c.rhodecode_db_repo.repo_id,
472 c.rhodecode_db_repo.repo_id,
469 status,
473 status,
470 c.rhodecode_user.user_id,
474 c.rhodecode_user.user_id,
471 comm,
475 comm,
472 pull_request=pull_request_id
476 pull_request=pull_request_id
473 )
477 )
474
478
475 if close_pr:
479 if close_pr:
476 if status in ['rejected', 'approved']:
480 if status in ['rejected', 'approved']:
477 PullRequestModel().close_pull_request(pull_request_id)
481 PullRequestModel().close_pull_request(pull_request_id)
478 action_logger(self.rhodecode_user,
482 action_logger(self.rhodecode_user,
479 'user_closed_pull_request:%s' % pull_request_id,
483 'user_closed_pull_request:%s' % pull_request_id,
480 c.rhodecode_db_repo, self.ip_addr, self.sa)
484 c.rhodecode_db_repo, self.ip_addr, self.sa)
481 else:
485 else:
482 h.flash(_('Closing pull request on other statuses than '
486 h.flash(_('Closing pull request on other statuses than '
483 'rejected or approved forbidden'),
487 'rejected or approved forbidden'),
484 category='warning')
488 category='warning')
485
489
486 Session().commit()
490 Session().commit()
487
491
488 if not request.environ.get('HTTP_X_PARTIAL_XHR'):
492 if not request.environ.get('HTTP_X_PARTIAL_XHR'):
489 return redirect(h.url('pullrequest_show', repo_name=repo_name,
493 return redirect(h.url('pullrequest_show', repo_name=repo_name,
490 pull_request_id=pull_request_id))
494 pull_request_id=pull_request_id))
491
495
492 data = {
496 data = {
493 'target_id': h.safeid(h.safe_unicode(request.POST.get('f_path'))),
497 'target_id': h.safeid(h.safe_unicode(request.POST.get('f_path'))),
494 }
498 }
495 if comm:
499 if comm:
496 c.co = comm
500 c.co = comm
497 data.update(comm.get_dict())
501 data.update(comm.get_dict())
498 data.update({'rendered_text':
502 data.update({'rendered_text':
499 render('changeset/changeset_comment_block.html')})
503 render('changeset/changeset_comment_block.html')})
500
504
501 return data
505 return data
502
506
503 @NotAnonymous()
507 @NotAnonymous()
504 @jsonify
508 @jsonify
505 def delete_comment(self, repo_name, comment_id):
509 def delete_comment(self, repo_name, comment_id):
506 co = ChangesetComment.get(comment_id)
510 co = ChangesetComment.get(comment_id)
507 if co.pull_request.is_closed():
511 if co.pull_request.is_closed():
508 #don't allow deleting comments on closed pull request
512 #don't allow deleting comments on closed pull request
509 raise HTTPForbidden()
513 raise HTTPForbidden()
510
514
511 owner = co.author.user_id == c.rhodecode_user.user_id
515 owner = co.author.user_id == c.rhodecode_user.user_id
512 if h.HasPermissionAny('hg.admin', 'repository.admin')() or owner:
516 if h.HasPermissionAny('hg.admin', 'repository.admin')() or owner:
513 ChangesetCommentsModel().delete(comment=co)
517 ChangesetCommentsModel().delete(comment=co)
514 Session().commit()
518 Session().commit()
515 return True
519 return True
516 else:
520 else:
517 raise HTTPForbidden()
521 raise HTTPForbidden()
General Comments 0
You need to be logged in to leave comments. Login now