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