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