##// END OF EJS Templates
pullrequests: also limit to stop displaying merged changes...
Mads Kiilerich -
r8757:86b9eed9 stable
parent child Browse files
Show More
@@ -1,646 +1,649 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 # This program is free software: you can redistribute it and/or modify
2 # This program is free software: you can redistribute it and/or modify
3 # it under the terms of the GNU General Public License as published by
3 # it under the terms of the GNU General Public License as published by
4 # the Free Software Foundation, either version 3 of the License, or
4 # the Free Software Foundation, either version 3 of the License, or
5 # (at your option) any later version.
5 # (at your option) any later version.
6 #
6 #
7 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # GNU General Public License for more details.
10 # GNU General Public License for more details.
11 #
11 #
12 # You should have received a copy of the GNU General Public License
12 # You should have received a copy of the GNU General Public License
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 """
14 """
15 kallithea.controllers.pullrequests
15 kallithea.controllers.pullrequests
16 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
16 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
17
17
18 pull requests controller for Kallithea for initializing pull requests
18 pull requests controller for Kallithea for initializing pull requests
19
19
20 This file was forked by the Kallithea project in July 2014.
20 This file was forked by the Kallithea project in July 2014.
21 Original author and date, and relevant copyright and licensing information is below:
21 Original author and date, and relevant copyright and licensing information is below:
22 :created_on: May 7, 2012
22 :created_on: May 7, 2012
23 :author: marcink
23 :author: marcink
24 :copyright: (c) 2013 RhodeCode GmbH, and others.
24 :copyright: (c) 2013 RhodeCode GmbH, and others.
25 :license: GPLv3, see LICENSE.md for more details.
25 :license: GPLv3, see LICENSE.md for more details.
26 """
26 """
27
27
28 import logging
28 import logging
29 import traceback
29 import traceback
30
30
31 import formencode
31 import formencode
32 import mercurial.unionrepo
32 import mercurial.unionrepo
33 from tg import request
33 from tg import request
34 from tg import tmpl_context as c
34 from tg import tmpl_context as c
35 from tg.i18n import ugettext as _
35 from tg.i18n import ugettext as _
36 from webob.exc import HTTPBadRequest, HTTPForbidden, HTTPFound, HTTPNotFound
36 from webob.exc import HTTPBadRequest, HTTPForbidden, HTTPFound, HTTPNotFound
37
37
38 import kallithea
38 import kallithea
39 import kallithea.lib.helpers as h
39 import kallithea.lib.helpers as h
40 from kallithea.controllers import base
40 from kallithea.controllers import base
41 from kallithea.controllers.changeset import create_cs_pr_comment, delete_cs_pr_comment
41 from kallithea.controllers.changeset import create_cs_pr_comment, delete_cs_pr_comment
42 from kallithea.lib import auth, diffs, webutils
42 from kallithea.lib import auth, diffs, webutils
43 from kallithea.lib.auth import HasRepoPermissionLevelDecorator, LoginRequired
43 from kallithea.lib.auth import HasRepoPermissionLevelDecorator, LoginRequired
44 from kallithea.lib.graphmod import graph_data
44 from kallithea.lib.graphmod import graph_data
45 from kallithea.lib.page import Page
45 from kallithea.lib.page import Page
46 from kallithea.lib.utils2 import ascii_bytes, safe_bytes, safe_int
46 from kallithea.lib.utils2 import ascii_bytes, safe_bytes, safe_int
47 from kallithea.lib.vcs.exceptions import ChangesetDoesNotExistError, EmptyRepositoryError
47 from kallithea.lib.vcs.exceptions import ChangesetDoesNotExistError, EmptyRepositoryError
48 from kallithea.lib.webutils import url
48 from kallithea.lib.webutils import url
49 from kallithea.model import db, meta
49 from kallithea.model import db, meta
50 from kallithea.model.changeset_status import ChangesetStatusModel
50 from kallithea.model.changeset_status import ChangesetStatusModel
51 from kallithea.model.comment import ChangesetCommentsModel
51 from kallithea.model.comment import ChangesetCommentsModel
52 from kallithea.model.forms import PullRequestForm, PullRequestPostForm
52 from kallithea.model.forms import PullRequestForm, PullRequestPostForm
53 from kallithea.model.pull_request import CreatePullRequestAction, CreatePullRequestIterationAction, PullRequestModel
53 from kallithea.model.pull_request import CreatePullRequestAction, CreatePullRequestIterationAction, PullRequestModel
54
54
55
55
56 log = logging.getLogger(__name__)
56 log = logging.getLogger(__name__)
57
57
58
58
59 def _get_reviewer(user_id):
59 def _get_reviewer(user_id):
60 """Look up user by ID and validate it as a potential reviewer."""
60 """Look up user by ID and validate it as a potential reviewer."""
61 try:
61 try:
62 user = db.User.get(int(user_id))
62 user = db.User.get(int(user_id))
63 except ValueError:
63 except ValueError:
64 user = None
64 user = None
65
65
66 if user is None or user.is_default_user:
66 if user is None or user.is_default_user:
67 webutils.flash(_('Invalid reviewer "%s" specified') % user_id, category='error')
67 webutils.flash(_('Invalid reviewer "%s" specified') % user_id, category='error')
68 raise HTTPBadRequest()
68 raise HTTPBadRequest()
69
69
70 return user
70 return user
71
71
72
72
73 class PullrequestsController(base.BaseRepoController):
73 class PullrequestsController(base.BaseRepoController):
74
74
75 def _get_repo_refs(self, repo, rev=None, branch=None, branch_rev=None):
75 def _get_repo_refs(self, repo, rev=None, branch=None, branch_rev=None):
76 """return a structure with scm repo's interesting changesets, suitable for
76 """return a structure with scm repo's interesting changesets, suitable for
77 the selectors in pullrequest.html
77 the selectors in pullrequest.html
78
78
79 rev: a revision that must be in the list somehow and selected by default
79 rev: a revision that must be in the list somehow and selected by default
80 branch: a branch that must be in the list and selected by default - even if closed
80 branch: a branch that must be in the list and selected by default - even if closed
81 branch_rev: a revision of which peers should be preferred and available."""
81 branch_rev: a revision of which peers should be preferred and available."""
82 # list named branches that has been merged to this named branch - it should probably merge back
82 # list named branches that has been merged to this named branch - it should probably merge back
83 peers = []
83 peers = []
84
84
85 if branch_rev:
85 if branch_rev:
86 # a revset not restricting to merge() would be better
86 # a revset not restricting to merge() would be better
87 # (especially because it would get the branch point)
87 # (especially because it would get the branch point)
88 # ... but is currently too expensive
88 # ... but is currently too expensive
89 # including branches of children could be nice too
89 # including branches of children could be nice too
90 peerbranches = set()
90 peerbranches = set()
91 for i in repo._repo.revs(
91 for i in repo._repo.revs(
92 b"sort(parents(branch(id(%s)) and merge()) - branch(id(%s)), -rev)",
92 b"sort(parents(branch(id(%s)) and merge()) - branch(id(%s)), -rev)",
93 ascii_bytes(branch_rev), ascii_bytes(branch_rev),
93 ascii_bytes(branch_rev), ascii_bytes(branch_rev),
94 ):
94 ):
95 for abranch in repo.get_changeset(i).branches:
95 for abranch in repo.get_changeset(i).branches:
96 if abranch not in peerbranches:
96 if abranch not in peerbranches:
97 n = 'branch:%s:%s' % (abranch, repo.get_changeset(abranch).raw_id)
97 n = 'branch:%s:%s' % (abranch, repo.get_changeset(abranch).raw_id)
98 peers.append((n, abranch))
98 peers.append((n, abranch))
99 peerbranches.add(abranch)
99 peerbranches.add(abranch)
100
100
101 selected = None
101 selected = None
102 tiprev = repo.tags.get('tip')
102 tiprev = repo.tags.get('tip')
103 tipbranch = None
103 tipbranch = None
104
104
105 branches = []
105 branches = []
106 for abranch, branchrev in repo.branches.items():
106 for abranch, branchrev in repo.branches.items():
107 n = 'branch:%s:%s' % (abranch, branchrev)
107 n = 'branch:%s:%s' % (abranch, branchrev)
108 desc = abranch
108 desc = abranch
109 if branchrev == tiprev:
109 if branchrev == tiprev:
110 tipbranch = abranch
110 tipbranch = abranch
111 desc = '%s (current tip)' % desc
111 desc = '%s (current tip)' % desc
112 branches.append((n, desc))
112 branches.append((n, desc))
113 if rev == branchrev:
113 if rev == branchrev:
114 selected = n
114 selected = n
115 if branch == abranch:
115 if branch == abranch:
116 if not rev:
116 if not rev:
117 selected = n
117 selected = n
118 branch = None
118 branch = None
119 if branch: # branch not in list - it is probably closed
119 if branch: # branch not in list - it is probably closed
120 branchrev = repo.closed_branches.get(branch)
120 branchrev = repo.closed_branches.get(branch)
121 if branchrev:
121 if branchrev:
122 n = 'branch:%s:%s' % (branch, branchrev)
122 n = 'branch:%s:%s' % (branch, branchrev)
123 branches.append((n, _('%s (closed)') % branch))
123 branches.append((n, _('%s (closed)') % branch))
124 selected = n
124 selected = n
125 branch = None
125 branch = None
126 if branch:
126 if branch:
127 log.debug('branch %r not found in %s', branch, repo)
127 log.debug('branch %r not found in %s', branch, repo)
128
128
129 bookmarks = []
129 bookmarks = []
130 for bookmark, bookmarkrev in repo.bookmarks.items():
130 for bookmark, bookmarkrev in repo.bookmarks.items():
131 n = 'book:%s:%s' % (bookmark, bookmarkrev)
131 n = 'book:%s:%s' % (bookmark, bookmarkrev)
132 bookmarks.append((n, bookmark))
132 bookmarks.append((n, bookmark))
133 if rev == bookmarkrev:
133 if rev == bookmarkrev:
134 selected = n
134 selected = n
135
135
136 tags = []
136 tags = []
137 for tag, tagrev in repo.tags.items():
137 for tag, tagrev in repo.tags.items():
138 if tag == 'tip':
138 if tag == 'tip':
139 continue
139 continue
140 n = 'tag:%s:%s' % (tag, tagrev)
140 n = 'tag:%s:%s' % (tag, tagrev)
141 tags.append((n, tag))
141 tags.append((n, tag))
142 # note: even if rev == tagrev, don't select the static tag - it must be chosen explicitly
142 # note: even if rev == tagrev, don't select the static tag - it must be chosen explicitly
143
143
144 # prio 1: rev was selected as existing entry above
144 # prio 1: rev was selected as existing entry above
145
145
146 # prio 2: create special entry for rev; rev _must_ be used
146 # prio 2: create special entry for rev; rev _must_ be used
147 specials = []
147 specials = []
148 if rev and selected is None:
148 if rev and selected is None:
149 selected = 'rev:%s:%s' % (rev, rev)
149 selected = 'rev:%s:%s' % (rev, rev)
150 specials = [(selected, '%s: %s' % (_("Changeset"), rev[:12]))]
150 specials = [(selected, '%s: %s' % (_("Changeset"), rev[:12]))]
151
151
152 # prio 3: most recent peer branch
152 # prio 3: most recent peer branch
153 if peers and not selected:
153 if peers and not selected:
154 selected = peers[0][0]
154 selected = peers[0][0]
155
155
156 # prio 4: tip revision
156 # prio 4: tip revision
157 if not selected:
157 if not selected:
158 if repo.alias == 'hg':
158 if repo.alias == 'hg':
159 if tipbranch:
159 if tipbranch:
160 selected = 'branch:%s:%s' % (tipbranch, tiprev)
160 selected = 'branch:%s:%s' % (tipbranch, tiprev)
161 else:
161 else:
162 selected = 'tag:null:' + repo.EMPTY_CHANGESET
162 selected = 'tag:null:' + repo.EMPTY_CHANGESET
163 tags.append((selected, 'null'))
163 tags.append((selected, 'null'))
164 else: # Git
164 else: # Git
165 assert repo.alias == 'git'
165 assert repo.alias == 'git'
166 if not repo.branches:
166 if not repo.branches:
167 selected = '' # doesn't make sense, but better than nothing
167 selected = '' # doesn't make sense, but better than nothing
168 elif 'master' in repo.branches:
168 elif 'master' in repo.branches:
169 selected = 'branch:master:%s' % repo.branches['master']
169 selected = 'branch:master:%s' % repo.branches['master']
170 else:
170 else:
171 k, v = list(repo.branches.items())[0]
171 k, v = list(repo.branches.items())[0]
172 selected = 'branch:%s:%s' % (k, v)
172 selected = 'branch:%s:%s' % (k, v)
173
173
174 groups = [(specials, _("Special")),
174 groups = [(specials, _("Special")),
175 (peers, _("Peer branches")),
175 (peers, _("Peer branches")),
176 (bookmarks, _("Bookmarks")),
176 (bookmarks, _("Bookmarks")),
177 (branches, _("Branches")),
177 (branches, _("Branches")),
178 (tags, _("Tags")),
178 (tags, _("Tags")),
179 ]
179 ]
180 return [g for g in groups if g[0]], selected
180 return [g for g in groups if g[0]], selected
181
181
182 def _is_allowed_to_change_status(self, pull_request):
182 def _is_allowed_to_change_status(self, pull_request):
183 if pull_request.is_closed():
183 if pull_request.is_closed():
184 return False
184 return False
185
185
186 owner = request.authuser.user_id == pull_request.owner_id
186 owner = request.authuser.user_id == pull_request.owner_id
187 reviewer = db.PullRequestReviewer.query() \
187 reviewer = db.PullRequestReviewer.query() \
188 .filter(db.PullRequestReviewer.pull_request == pull_request) \
188 .filter(db.PullRequestReviewer.pull_request == pull_request) \
189 .filter(db.PullRequestReviewer.user_id == request.authuser.user_id) \
189 .filter(db.PullRequestReviewer.user_id == request.authuser.user_id) \
190 .count() != 0
190 .count() != 0
191
191
192 return request.authuser.admin or owner or reviewer
192 return request.authuser.admin or owner or reviewer
193
193
194 @LoginRequired(allow_default_user=True)
194 @LoginRequired(allow_default_user=True)
195 @HasRepoPermissionLevelDecorator('read')
195 @HasRepoPermissionLevelDecorator('read')
196 def show_all(self, repo_name):
196 def show_all(self, repo_name):
197 c.from_ = request.GET.get('from_') or ''
197 c.from_ = request.GET.get('from_') or ''
198 c.closed = request.GET.get('closed') or ''
198 c.closed = request.GET.get('closed') or ''
199 url_params = {}
199 url_params = {}
200 if c.from_:
200 if c.from_:
201 url_params['from_'] = 1
201 url_params['from_'] = 1
202 if c.closed:
202 if c.closed:
203 url_params['closed'] = 1
203 url_params['closed'] = 1
204 p = safe_int(request.GET.get('page'), 1)
204 p = safe_int(request.GET.get('page'), 1)
205
205
206 q = db.PullRequest.query(include_closed=c.closed, sorted=True)
206 q = db.PullRequest.query(include_closed=c.closed, sorted=True)
207 if c.from_:
207 if c.from_:
208 q = q.filter_by(org_repo=c.db_repo)
208 q = q.filter_by(org_repo=c.db_repo)
209 else:
209 else:
210 q = q.filter_by(other_repo=c.db_repo)
210 q = q.filter_by(other_repo=c.db_repo)
211 c.pull_requests = q.all()
211 c.pull_requests = q.all()
212
212
213 c.pullrequests_pager = Page(c.pull_requests, page=p, items_per_page=100, **url_params)
213 c.pullrequests_pager = Page(c.pull_requests, page=p, items_per_page=100, **url_params)
214
214
215 return base.render('/pullrequests/pullrequest_show_all.html')
215 return base.render('/pullrequests/pullrequest_show_all.html')
216
216
217 @LoginRequired()
217 @LoginRequired()
218 def show_my(self):
218 def show_my(self):
219 c.closed = request.GET.get('closed') or ''
219 c.closed = request.GET.get('closed') or ''
220
220
221 c.my_pull_requests = db.PullRequest.query(
221 c.my_pull_requests = db.PullRequest.query(
222 include_closed=c.closed,
222 include_closed=c.closed,
223 sorted=True,
223 sorted=True,
224 ).filter_by(owner_id=request.authuser.user_id).all()
224 ).filter_by(owner_id=request.authuser.user_id).all()
225
225
226 c.participate_in_pull_requests = []
226 c.participate_in_pull_requests = []
227 c.participate_in_pull_requests_todo = []
227 c.participate_in_pull_requests_todo = []
228 done_status = set([db.ChangesetStatus.STATUS_APPROVED, db.ChangesetStatus.STATUS_REJECTED])
228 done_status = set([db.ChangesetStatus.STATUS_APPROVED, db.ChangesetStatus.STATUS_REJECTED])
229 for pr in db.PullRequest.query(
229 for pr in db.PullRequest.query(
230 include_closed=c.closed,
230 include_closed=c.closed,
231 reviewer_id=request.authuser.user_id,
231 reviewer_id=request.authuser.user_id,
232 sorted=True,
232 sorted=True,
233 ):
233 ):
234 status = pr.user_review_status(request.authuser.user_id) # very inefficient!!!
234 status = pr.user_review_status(request.authuser.user_id) # very inefficient!!!
235 if status in done_status:
235 if status in done_status:
236 c.participate_in_pull_requests.append(pr)
236 c.participate_in_pull_requests.append(pr)
237 else:
237 else:
238 c.participate_in_pull_requests_todo.append(pr)
238 c.participate_in_pull_requests_todo.append(pr)
239
239
240 return base.render('/pullrequests/pullrequest_show_my.html')
240 return base.render('/pullrequests/pullrequest_show_my.html')
241
241
242 @LoginRequired()
242 @LoginRequired()
243 @HasRepoPermissionLevelDecorator('read')
243 @HasRepoPermissionLevelDecorator('read')
244 def index(self):
244 def index(self):
245 org_repo = c.db_repo
245 org_repo = c.db_repo
246 org_scm_instance = org_repo.scm_instance
246 org_scm_instance = org_repo.scm_instance
247 try:
247 try:
248 org_scm_instance.get_changeset()
248 org_scm_instance.get_changeset()
249 except EmptyRepositoryError as e:
249 except EmptyRepositoryError as e:
250 webutils.flash(_('There are no changesets yet'),
250 webutils.flash(_('There are no changesets yet'),
251 category='warning')
251 category='warning')
252 raise HTTPFound(location=url('summary_home', repo_name=org_repo.repo_name))
252 raise HTTPFound(location=url('summary_home', repo_name=org_repo.repo_name))
253
253
254 org_rev = request.GET.get('rev_end')
254 org_rev = request.GET.get('rev_end')
255 # rev_start is not directly useful - its parent could however be used
255 # rev_start is not directly useful - its parent could however be used
256 # as default for other and thus give a simple compare view
256 # as default for other and thus give a simple compare view
257 rev_start = request.GET.get('rev_start')
257 rev_start = request.GET.get('rev_start')
258 other_rev = None
258 other_rev = None
259 if rev_start:
259 if rev_start:
260 starters = org_repo.get_changeset(rev_start).parents
260 starters = org_repo.get_changeset(rev_start).parents
261 if starters:
261 if starters:
262 other_rev = starters[0].raw_id
262 other_rev = starters[0].raw_id
263 else:
263 else:
264 other_rev = org_repo.scm_instance.EMPTY_CHANGESET
264 other_rev = org_repo.scm_instance.EMPTY_CHANGESET
265 branch = request.GET.get('branch')
265 branch = request.GET.get('branch')
266
266
267 c.cs_repos = [(org_repo.repo_name, org_repo.repo_name)]
267 c.cs_repos = [(org_repo.repo_name, org_repo.repo_name)]
268 c.default_cs_repo = org_repo.repo_name
268 c.default_cs_repo = org_repo.repo_name
269 c.cs_refs, c.default_cs_ref = self._get_repo_refs(org_scm_instance, rev=org_rev, branch=branch)
269 c.cs_refs, c.default_cs_ref = self._get_repo_refs(org_scm_instance, rev=org_rev, branch=branch)
270
270
271 default_cs_ref_type, default_cs_branch, default_cs_rev = c.default_cs_ref.split(':')
271 default_cs_ref_type, default_cs_branch, default_cs_rev = c.default_cs_ref.split(':')
272 if default_cs_ref_type != 'branch':
272 if default_cs_ref_type != 'branch':
273 default_cs_branch = org_repo.get_changeset(default_cs_rev).branch
273 default_cs_branch = org_repo.get_changeset(default_cs_rev).branch
274
274
275 # add org repo to other so we can open pull request against peer branches on itself
275 # add org repo to other so we can open pull request against peer branches on itself
276 c.a_repos = [(org_repo.repo_name, '%s (self)' % org_repo.repo_name)]
276 c.a_repos = [(org_repo.repo_name, '%s (self)' % org_repo.repo_name)]
277
277
278 if org_repo.parent:
278 if org_repo.parent:
279 # add parent of this fork also and select it.
279 # add parent of this fork also and select it.
280 # use the same branch on destination as on source, if available.
280 # use the same branch on destination as on source, if available.
281 c.a_repos.append((org_repo.parent.repo_name, '%s (parent)' % org_repo.parent.repo_name))
281 c.a_repos.append((org_repo.parent.repo_name, '%s (parent)' % org_repo.parent.repo_name))
282 c.a_repo = org_repo.parent
282 c.a_repo = org_repo.parent
283 c.a_refs, c.default_a_ref = self._get_repo_refs(
283 c.a_refs, c.default_a_ref = self._get_repo_refs(
284 org_repo.parent.scm_instance, branch=default_cs_branch, rev=other_rev)
284 org_repo.parent.scm_instance, branch=default_cs_branch, rev=other_rev)
285
285
286 else:
286 else:
287 c.a_repo = org_repo
287 c.a_repo = org_repo
288 c.a_refs, c.default_a_ref = self._get_repo_refs(org_scm_instance, rev=other_rev)
288 c.a_refs, c.default_a_ref = self._get_repo_refs(org_scm_instance, rev=other_rev)
289
289
290 # gather forks and add to this list ... even though it is rare to
290 # gather forks and add to this list ... even though it is rare to
291 # request forks to pull from their parent
291 # request forks to pull from their parent
292 for fork in org_repo.forks:
292 for fork in org_repo.forks:
293 c.a_repos.append((fork.repo_name, fork.repo_name))
293 c.a_repos.append((fork.repo_name, fork.repo_name))
294
294
295 return base.render('/pullrequests/pullrequest.html')
295 return base.render('/pullrequests/pullrequest.html')
296
296
297 @LoginRequired()
297 @LoginRequired()
298 @HasRepoPermissionLevelDecorator('read')
298 @HasRepoPermissionLevelDecorator('read')
299 @base.jsonify
299 @base.jsonify
300 def repo_info(self, repo_name):
300 def repo_info(self, repo_name):
301 repo = c.db_repo
301 repo = c.db_repo
302 refs, selected_ref = self._get_repo_refs(repo.scm_instance)
302 refs, selected_ref = self._get_repo_refs(repo.scm_instance)
303 return {
303 return {
304 'description': repo.description.split('\n', 1)[0],
304 'description': repo.description.split('\n', 1)[0],
305 'selected_ref': selected_ref,
305 'selected_ref': selected_ref,
306 'refs': refs,
306 'refs': refs,
307 }
307 }
308
308
309 @LoginRequired()
309 @LoginRequired()
310 @HasRepoPermissionLevelDecorator('read')
310 @HasRepoPermissionLevelDecorator('read')
311 def create(self, repo_name):
311 def create(self, repo_name):
312 repo = c.db_repo
312 repo = c.db_repo
313 try:
313 try:
314 _form = PullRequestForm(repo.repo_id)().to_python(request.POST)
314 _form = PullRequestForm(repo.repo_id)().to_python(request.POST)
315 except formencode.Invalid as errors:
315 except formencode.Invalid as errors:
316 log.error(traceback.format_exc())
316 log.error(traceback.format_exc())
317 log.error(str(errors))
317 log.error(str(errors))
318 msg = _('Error creating pull request: %s') % errors.msg
318 msg = _('Error creating pull request: %s') % errors.msg
319 webutils.flash(msg, 'error')
319 webutils.flash(msg, 'error')
320 raise HTTPBadRequest
320 raise HTTPBadRequest
321
321
322 # heads up: org and other might seem backward here ...
322 # heads up: org and other might seem backward here ...
323 org_ref = _form['org_ref'] # will have merge_rev as rev but symbolic name
323 org_ref = _form['org_ref'] # will have merge_rev as rev but symbolic name
324 org_repo = db.Repository.guess_instance(_form['org_repo'])
324 org_repo = db.Repository.guess_instance(_form['org_repo'])
325
325
326 other_ref = _form['other_ref'] # will have symbolic name and head revision
326 other_ref = _form['other_ref'] # will have symbolic name and head revision
327 other_repo = db.Repository.guess_instance(_form['other_repo'])
327 other_repo = db.Repository.guess_instance(_form['other_repo'])
328
328
329 reviewers = []
329 reviewers = []
330
330
331 title = _form['pullrequest_title']
331 title = _form['pullrequest_title']
332 description = _form['pullrequest_desc'].strip()
332 description = _form['pullrequest_desc'].strip()
333 owner = db.User.get(request.authuser.user_id)
333 owner = db.User.get(request.authuser.user_id)
334
334
335 try:
335 try:
336 cmd = CreatePullRequestAction(org_repo, other_repo, org_ref, other_ref, title, description, owner, reviewers)
336 cmd = CreatePullRequestAction(org_repo, other_repo, org_ref, other_ref, title, description, owner, reviewers)
337 except CreatePullRequestAction.ValidationError as e:
337 except CreatePullRequestAction.ValidationError as e:
338 webutils.flash(e, category='error', logf=log.error)
338 webutils.flash(e, category='error', logf=log.error)
339 raise HTTPNotFound
339 raise HTTPNotFound
340
340
341 try:
341 try:
342 pull_request = cmd.execute()
342 pull_request = cmd.execute()
343 meta.Session().commit()
343 meta.Session().commit()
344 except Exception:
344 except Exception:
345 webutils.flash(_('Error occurred while creating pull request'),
345 webutils.flash(_('Error occurred while creating pull request'),
346 category='error')
346 category='error')
347 log.error(traceback.format_exc())
347 log.error(traceback.format_exc())
348 raise HTTPFound(location=url('pullrequest_home', repo_name=repo_name))
348 raise HTTPFound(location=url('pullrequest_home', repo_name=repo_name))
349
349
350 webutils.flash(_('Successfully opened new pull request'),
350 webutils.flash(_('Successfully opened new pull request'),
351 category='success')
351 category='success')
352 raise HTTPFound(location=pull_request.url())
352 raise HTTPFound(location=pull_request.url())
353
353
354 def create_new_iteration(self, old_pull_request, new_rev, title, description, reviewers):
354 def create_new_iteration(self, old_pull_request, new_rev, title, description, reviewers):
355 owner = db.User.get(request.authuser.user_id)
355 owner = db.User.get(request.authuser.user_id)
356 new_org_rev = self._get_ref_rev(old_pull_request.org_repo, 'rev', new_rev)
356 new_org_rev = self._get_ref_rev(old_pull_request.org_repo, 'rev', new_rev)
357 new_other_rev = self._get_ref_rev(old_pull_request.other_repo, old_pull_request.other_ref_parts[0], old_pull_request.other_ref_parts[1])
357 new_other_rev = self._get_ref_rev(old_pull_request.other_repo, old_pull_request.other_ref_parts[0], old_pull_request.other_ref_parts[1])
358 try:
358 try:
359 cmd = CreatePullRequestIterationAction(old_pull_request, new_org_rev, new_other_rev, title, description, owner, reviewers)
359 cmd = CreatePullRequestIterationAction(old_pull_request, new_org_rev, new_other_rev, title, description, owner, reviewers)
360 except CreatePullRequestAction.ValidationError as e:
360 except CreatePullRequestAction.ValidationError as e:
361 webutils.flash(e, category='error', logf=log.error)
361 webutils.flash(e, category='error', logf=log.error)
362 raise HTTPNotFound
362 raise HTTPNotFound
363
363
364 try:
364 try:
365 pull_request = cmd.execute()
365 pull_request = cmd.execute()
366 meta.Session().commit()
366 meta.Session().commit()
367 except Exception:
367 except Exception:
368 webutils.flash(_('Error occurred while creating pull request'),
368 webutils.flash(_('Error occurred while creating pull request'),
369 category='error')
369 category='error')
370 log.error(traceback.format_exc())
370 log.error(traceback.format_exc())
371 raise HTTPFound(location=old_pull_request.url())
371 raise HTTPFound(location=old_pull_request.url())
372
372
373 webutils.flash(_('New pull request iteration created'),
373 webutils.flash(_('New pull request iteration created'),
374 category='success')
374 category='success')
375 raise HTTPFound(location=pull_request.url())
375 raise HTTPFound(location=pull_request.url())
376
376
377 # pullrequest_post for PR editing
377 # pullrequest_post for PR editing
378 @LoginRequired()
378 @LoginRequired()
379 @HasRepoPermissionLevelDecorator('read')
379 @HasRepoPermissionLevelDecorator('read')
380 def post(self, repo_name, pull_request_id):
380 def post(self, repo_name, pull_request_id):
381 pull_request = db.PullRequest.get_or_404(pull_request_id)
381 pull_request = db.PullRequest.get_or_404(pull_request_id)
382 if pull_request.is_closed():
382 if pull_request.is_closed():
383 raise HTTPForbidden()
383 raise HTTPForbidden()
384 assert pull_request.other_repo.repo_name == repo_name
384 assert pull_request.other_repo.repo_name == repo_name
385 # only owner or admin can update it
385 # only owner or admin can update it
386 owner = pull_request.owner_id == request.authuser.user_id
386 owner = pull_request.owner_id == request.authuser.user_id
387 repo_admin = auth.HasRepoPermissionLevel('admin')(c.repo_name)
387 repo_admin = auth.HasRepoPermissionLevel('admin')(c.repo_name)
388 if not (auth.HasPermissionAny('hg.admin')() or repo_admin or owner):
388 if not (auth.HasPermissionAny('hg.admin')() or repo_admin or owner):
389 raise HTTPForbidden()
389 raise HTTPForbidden()
390
390
391 _form = PullRequestPostForm()().to_python(request.POST)
391 _form = PullRequestPostForm()().to_python(request.POST)
392
392
393 cur_reviewers = set(pull_request.get_reviewer_users())
393 cur_reviewers = set(pull_request.get_reviewer_users())
394 new_reviewers = set(_get_reviewer(s) for s in _form['review_members'])
394 new_reviewers = set(_get_reviewer(s) for s in _form['review_members'])
395 old_reviewers = set(_get_reviewer(s) for s in _form['org_review_members'])
395 old_reviewers = set(_get_reviewer(s) for s in _form['org_review_members'])
396
396
397 other_added = cur_reviewers - old_reviewers
397 other_added = cur_reviewers - old_reviewers
398 other_removed = old_reviewers - cur_reviewers
398 other_removed = old_reviewers - cur_reviewers
399
399
400 if other_added:
400 if other_added:
401 webutils.flash(_('Meanwhile, the following reviewers have been added: %s') %
401 webutils.flash(_('Meanwhile, the following reviewers have been added: %s') %
402 (', '.join(u.username for u in other_added)),
402 (', '.join(u.username for u in other_added)),
403 category='warning')
403 category='warning')
404 if other_removed:
404 if other_removed:
405 webutils.flash(_('Meanwhile, the following reviewers have been removed: %s') %
405 webutils.flash(_('Meanwhile, the following reviewers have been removed: %s') %
406 (', '.join(u.username for u in other_removed)),
406 (', '.join(u.username for u in other_removed)),
407 category='warning')
407 category='warning')
408
408
409 if _form['updaterev']:
409 if _form['updaterev']:
410 return self.create_new_iteration(pull_request,
410 return self.create_new_iteration(pull_request,
411 _form['updaterev'],
411 _form['updaterev'],
412 _form['pullrequest_title'],
412 _form['pullrequest_title'],
413 _form['pullrequest_desc'],
413 _form['pullrequest_desc'],
414 new_reviewers)
414 new_reviewers)
415
415
416 added_reviewers = new_reviewers - old_reviewers - cur_reviewers
416 added_reviewers = new_reviewers - old_reviewers - cur_reviewers
417 removed_reviewers = (old_reviewers - new_reviewers) & cur_reviewers
417 removed_reviewers = (old_reviewers - new_reviewers) & cur_reviewers
418
418
419 old_description = pull_request.description
419 old_description = pull_request.description
420 pull_request.title = _form['pullrequest_title']
420 pull_request.title = _form['pullrequest_title']
421 pull_request.description = _form['pullrequest_desc'].strip() or _('No description')
421 pull_request.description = _form['pullrequest_desc'].strip() or _('No description')
422 pull_request.owner = db.User.get_by_username(_form['owner'])
422 pull_request.owner = db.User.get_by_username(_form['owner'])
423 user = db.User.get(request.authuser.user_id)
423 user = db.User.get(request.authuser.user_id)
424
424
425 PullRequestModel().mention_from_description(user, pull_request, old_description)
425 PullRequestModel().mention_from_description(user, pull_request, old_description)
426 PullRequestModel().add_reviewers(user, pull_request, added_reviewers)
426 PullRequestModel().add_reviewers(user, pull_request, added_reviewers)
427 PullRequestModel().remove_reviewers(user, pull_request, removed_reviewers)
427 PullRequestModel().remove_reviewers(user, pull_request, removed_reviewers)
428
428
429 meta.Session().commit()
429 meta.Session().commit()
430 webutils.flash(_('Pull request updated'), category='success')
430 webutils.flash(_('Pull request updated'), category='success')
431
431
432 raise HTTPFound(location=pull_request.url())
432 raise HTTPFound(location=pull_request.url())
433
433
434 @LoginRequired()
434 @LoginRequired()
435 @HasRepoPermissionLevelDecorator('read')
435 @HasRepoPermissionLevelDecorator('read')
436 @base.jsonify
436 @base.jsonify
437 def delete(self, repo_name, pull_request_id):
437 def delete(self, repo_name, pull_request_id):
438 pull_request = db.PullRequest.get_or_404(pull_request_id)
438 pull_request = db.PullRequest.get_or_404(pull_request_id)
439 # only owner can delete it !
439 # only owner can delete it !
440 if pull_request.owner_id == request.authuser.user_id:
440 if pull_request.owner_id == request.authuser.user_id:
441 PullRequestModel().delete(pull_request)
441 PullRequestModel().delete(pull_request)
442 meta.Session().commit()
442 meta.Session().commit()
443 webutils.flash(_('Successfully deleted pull request'),
443 webutils.flash(_('Successfully deleted pull request'),
444 category='success')
444 category='success')
445 raise HTTPFound(location=url('my_pullrequests'))
445 raise HTTPFound(location=url('my_pullrequests'))
446 raise HTTPForbidden()
446 raise HTTPForbidden()
447
447
448 @LoginRequired(allow_default_user=True)
448 @LoginRequired(allow_default_user=True)
449 @HasRepoPermissionLevelDecorator('read')
449 @HasRepoPermissionLevelDecorator('read')
450 def show(self, repo_name, pull_request_id, extra=None):
450 def show(self, repo_name, pull_request_id, extra=None):
451 c.pull_request = db.PullRequest.get_or_404(pull_request_id)
451 c.pull_request = db.PullRequest.get_or_404(pull_request_id)
452 c.allowed_to_change_status = self._is_allowed_to_change_status(c.pull_request)
452 c.allowed_to_change_status = self._is_allowed_to_change_status(c.pull_request)
453 cc_model = ChangesetCommentsModel()
453 cc_model = ChangesetCommentsModel()
454 cs_model = ChangesetStatusModel()
454 cs_model = ChangesetStatusModel()
455
455
456 # pull_requests repo_name we opened it against
456 # pull_requests repo_name we opened it against
457 # ie. other_repo must match
457 # ie. other_repo must match
458 if repo_name != c.pull_request.other_repo.repo_name:
458 if repo_name != c.pull_request.other_repo.repo_name:
459 raise HTTPNotFound
459 raise HTTPNotFound
460
460
461 # load compare data into template context
461 # load compare data into template context
462 c.cs_repo = c.pull_request.org_repo
462 c.cs_repo = c.pull_request.org_repo
463 (c.cs_ref_type,
463 (c.cs_ref_type,
464 c.cs_ref_name,
464 c.cs_ref_name,
465 c.cs_rev) = c.pull_request.org_ref.split(':')
465 c.cs_rev) = c.pull_request.org_ref.split(':')
466
466
467 c.a_repo = c.pull_request.other_repo
467 c.a_repo = c.pull_request.other_repo
468 (c.a_ref_type,
468 (c.a_ref_type,
469 c.a_ref_name,
469 c.a_ref_name,
470 c.a_rev) = c.pull_request.other_ref.split(':') # a_rev is ancestor
470 c.a_rev) = c.pull_request.other_ref.split(':') # a_rev is ancestor
471
471
472 org_scm_instance = c.cs_repo.scm_instance # property with expensive cache invalidation check!!!
472 org_scm_instance = c.cs_repo.scm_instance # property with expensive cache invalidation check!!!
473 c.cs_ranges = []
473 c.cs_ranges = []
474 for x in c.pull_request.revisions:
474 for x in c.pull_request.revisions:
475 try:
475 try:
476 c.cs_ranges.append(org_scm_instance.get_changeset(x))
476 c.cs_ranges.append(org_scm_instance.get_changeset(x))
477 except ChangesetDoesNotExistError:
477 except ChangesetDoesNotExistError:
478 c.cs_ranges = []
478 c.cs_ranges = []
479 webutils.flash(_('Revision %s not found in %s') % (x, c.cs_repo.repo_name),
479 webutils.flash(_('Revision %s not found in %s') % (x, c.cs_repo.repo_name),
480 'error')
480 'error')
481 break
481 break
482 c.cs_ranges_org = None # not stored and not important and moving target - could be calculated ...
482 c.cs_ranges_org = None # not stored and not important and moving target - could be calculated ...
483 revs = [ctx.revision for ctx in reversed(c.cs_ranges)]
483 revs = [ctx.revision for ctx in reversed(c.cs_ranges)]
484 c.jsdata = graph_data(org_scm_instance, revs)
484 c.jsdata = graph_data(org_scm_instance, revs)
485
485
486 c.is_range = False
486 c.is_range = False
487 try:
487 try:
488 if c.a_ref_type == 'rev': # this looks like a free range where target is ancestor
488 if c.a_ref_type == 'rev': # this looks like a free range where target is ancestor
489 cs_a = org_scm_instance.get_changeset(c.a_rev)
489 cs_a = org_scm_instance.get_changeset(c.a_rev)
490 root_parents = c.cs_ranges[0].parents
490 root_parents = c.cs_ranges[0].parents
491 c.is_range = cs_a in root_parents
491 c.is_range = cs_a in root_parents
492 #c.merge_root = len(root_parents) > 1 # a range starting with a merge might deserve a warning
492 #c.merge_root = len(root_parents) > 1 # a range starting with a merge might deserve a warning
493 except ChangesetDoesNotExistError: # probably because c.a_rev not found
493 except ChangesetDoesNotExistError: # probably because c.a_rev not found
494 pass
494 pass
495 except IndexError: # probably because c.cs_ranges is empty, probably because revisions are missing
495 except IndexError: # probably because c.cs_ranges is empty, probably because revisions are missing
496 pass
496 pass
497
497
498 rev_limit = safe_int(kallithea.CONFIG.get('next_iteration_rev_limit'), 0)
498 rev_limit = safe_int(kallithea.CONFIG.get('next_iteration_rev_limit'), 0)
499
499
500 avail_revs = set()
500 avail_revs = set()
501 avail_show = []
501 avail_show = []
502 c.cs_branch_name = c.cs_ref_name
502 c.cs_branch_name = c.cs_ref_name
503 c.a_branch_name = None
503 c.a_branch_name = None
504 other_scm_instance = c.a_repo.scm_instance
504 other_scm_instance = c.a_repo.scm_instance
505 c.update_msg = ""
505 c.update_msg = ""
506 c.update_msg_other = ""
506 c.update_msg_other = ""
507 try:
507 try:
508 if not c.cs_ranges:
508 if not c.cs_ranges:
509 c.update_msg = _('Error: changesets not found when displaying pull request from %s.') % c.cs_rev
509 c.update_msg = _('Error: changesets not found when displaying pull request from %s.') % c.cs_rev
510 elif org_scm_instance.alias == 'hg' and c.a_ref_name != 'ancestor':
510 elif org_scm_instance.alias == 'hg' and c.a_ref_name != 'ancestor':
511 if c.cs_ref_type != 'branch':
511 if c.cs_ref_type != 'branch':
512 c.cs_branch_name = org_scm_instance.get_changeset(c.cs_ref_name).branch # use ref_type ?
512 c.cs_branch_name = org_scm_instance.get_changeset(c.cs_ref_name).branch # use ref_type ?
513 c.a_branch_name = c.a_ref_name
513 c.a_branch_name = c.a_ref_name
514 if c.a_ref_type != 'branch':
514 if c.a_ref_type != 'branch':
515 try:
515 try:
516 c.a_branch_name = other_scm_instance.get_changeset(c.a_ref_name).branch # use ref_type ?
516 c.a_branch_name = other_scm_instance.get_changeset(c.a_ref_name).branch # use ref_type ?
517 except EmptyRepositoryError:
517 except EmptyRepositoryError:
518 c.a_branch_name = 'null' # not a branch name ... but close enough
518 c.a_branch_name = 'null' # not a branch name ... but close enough
519 # candidates: descendants of old head that are on the right branch
519 # candidates: descendants of old head that are on the right branch
520 # and not are the old head itself ...
520 # and not are the old head itself ...
521 # and nothing at all if old head is a descendant of target ref name
521 # and nothing at all if old head is a descendant of target ref name
522 if not c.is_range and other_scm_instance._repo.revs('present(%s)::&%s', c.cs_ranges[-1].raw_id, c.a_branch_name):
522 if not c.is_range and other_scm_instance._repo.revs('present(%s)::&%s', c.cs_ranges[-1].raw_id, c.a_branch_name):
523 c.update_msg = _('This pull request has already been merged to %s.') % c.a_branch_name
523 c.update_msg = _('This pull request has already been merged to %s.') % c.a_branch_name
524 elif c.pull_request.is_closed():
524 elif c.pull_request.is_closed():
525 c.update_msg = _('This pull request has been closed and can not be updated.')
525 c.update_msg = _('This pull request has been closed and can not be updated.')
526 else: # look for descendants of PR head on source branch in org repo
526 else: # look for descendants of PR head on source branch in org repo
527 avail_revs = org_scm_instance._repo.revs('%s:: & branch(%s)',
527 avail_revs = org_scm_instance._repo.revs('%s:: & branch(%s)',
528 revs[0], c.cs_branch_name)
528 revs[0], c.cs_branch_name)
529 if len(avail_revs) > 1: # more than just revs[0]
529 if len(avail_revs) > 1: # more than just revs[0]
530 # also show changesets that not are descendants but would be merged in
530 # also show changesets that not are descendants but would be merged in
531 targethead = other_scm_instance.get_changeset(c.a_branch_name).raw_id
531 targethead = other_scm_instance.get_changeset(c.a_branch_name).raw_id
532 if org_scm_instance.path != other_scm_instance.path:
532 if org_scm_instance.path != other_scm_instance.path:
533 # Note: org_scm_instance.path must come first so all
533 # Note: org_scm_instance.path must come first so all
534 # valid revision numbers are 100% org_scm compatible
534 # valid revision numbers are 100% org_scm compatible
535 # - both for avail_revs and for revset results
535 # - both for avail_revs and for revset results
536 hgrepo = mercurial.unionrepo.makeunionrepository(org_scm_instance.baseui,
536 hgrepo = mercurial.unionrepo.makeunionrepository(org_scm_instance.baseui,
537 safe_bytes(org_scm_instance.path),
537 safe_bytes(org_scm_instance.path),
538 safe_bytes(other_scm_instance.path))
538 safe_bytes(other_scm_instance.path))
539 else:
539 else:
540 hgrepo = org_scm_instance._repo
540 hgrepo = org_scm_instance._repo
541 show = set(hgrepo.revs('::%ld & !::parents(%s) & !::%s',
541 show = set(hgrepo.revs('::%ld & !::parents(%s) & !::%s',
542 avail_revs, revs[0], targethead))
542 avail_revs, revs[0], targethead))
543 if show:
543 if show:
544 c.update_msg = _('The following additional changes are available on %s:') % c.cs_branch_name
544 c.update_msg = _('The following additional changes are available on %s:') % c.cs_branch_name
545 else:
545 else:
546 c.update_msg = _('No additional changesets found for iterating on this pull request.')
546 c.update_msg = _('No additional changesets found for iterating on this pull request.')
547 else:
547 else:
548 show = set()
548 show = set()
549 avail_revs = set() # drop revs[0]
549 avail_revs = set() # drop revs[0]
550 c.update_msg = _('No additional changesets found for iterating on this pull request.')
550 c.update_msg = _('No additional changesets found for iterating on this pull request.')
551
551
552 # TODO: handle branch heads that not are tip-most
552 # TODO: handle branch heads that not are tip-most
553 brevs = org_scm_instance._repo.revs('%s - %ld - %s', c.cs_branch_name, avail_revs, revs[0])
553 brevs = org_scm_instance._repo.revs('%s - %ld - %s', c.cs_branch_name, avail_revs, revs[0])
554 if brevs:
554 if brevs:
555 # also show changesets that are on branch but neither ancestors nor descendants
555 # also show changesets that are on branch but neither ancestors nor descendants
556 show.update(org_scm_instance._repo.revs('::%ld - ::%ld - ::%s', brevs, avail_revs, c.a_branch_name))
556 show.update(org_scm_instance._repo.revs('::%ld - ::%ld - ::%s', brevs, avail_revs, c.a_branch_name))
557 show.add(revs[0]) # make sure graph shows this so we can see how they relate
557 show.add(revs[0]) # make sure graph shows this so we can see how they relate
558 c.update_msg_other = _('Note: Branch %s has another head: %s.') % (c.cs_branch_name,
558 c.update_msg_other = _('Note: Branch %s has another head: %s.') % (c.cs_branch_name,
559 org_scm_instance.get_changeset(max(brevs)).short_id)
559 org_scm_instance.get_changeset(max(brevs)).short_id)
560
560
561 avail_show = sorted(show, reverse=True)
561 avail_show = sorted(show, reverse=True)
562
562
563 elif org_scm_instance.alias == 'git':
563 elif org_scm_instance.alias == 'git':
564 c.cs_repo.scm_instance.get_changeset(c.cs_rev) # check it exists - raise ChangesetDoesNotExistError if not
564 c.cs_repo.scm_instance.get_changeset(c.cs_rev) # check it exists - raise ChangesetDoesNotExistError if not
565 c.update_msg = _("Git pull requests don't support iterating yet.")
565 c.update_msg = _("Git pull requests don't support iterating yet.")
566 except ChangesetDoesNotExistError:
566 except ChangesetDoesNotExistError:
567 c.update_msg = _('Error: some changesets not found when displaying pull request from %s.') % c.cs_rev
567 c.update_msg = _('Error: some changesets not found when displaying pull request from %s.') % c.cs_rev
568
568
569 if rev_limit:
569 if rev_limit:
570 if len(avail_revs) - 1 > rev_limit:
570 if len(avail_revs) - 1 > rev_limit:
571 c.update_msg = _('%d additional changesets are not shown.') % (len(avail_revs) - 1)
571 c.update_msg = _('%d additional changesets are not shown.') % (len(avail_revs) - 1)
572 avail_show = []
572 avail_show = []
573 elif len(avail_show) - 1 > rev_limit:
574 c.update_msg = _('%d changesets available for merging are not shown.') % (len(avail_show) - len(avail_revs))
575 avail_show = sorted(avail_revs, reverse=True)
573
576
574 c.avail_revs = avail_revs
577 c.avail_revs = avail_revs
575 c.avail_cs = [org_scm_instance.get_changeset(r) for r in avail_show]
578 c.avail_cs = [org_scm_instance.get_changeset(r) for r in avail_show]
576 c.avail_jsdata = graph_data(org_scm_instance, avail_show)
579 c.avail_jsdata = graph_data(org_scm_instance, avail_show)
577
580
578 raw_ids = [x.raw_id for x in c.cs_ranges]
581 raw_ids = [x.raw_id for x in c.cs_ranges]
579 c.cs_comments = c.cs_repo.get_comments(raw_ids)
582 c.cs_comments = c.cs_repo.get_comments(raw_ids)
580 c.cs_statuses = c.cs_repo.statuses(raw_ids)
583 c.cs_statuses = c.cs_repo.statuses(raw_ids)
581
584
582 ignore_whitespace_diff = h.get_ignore_whitespace_diff(request.GET)
585 ignore_whitespace_diff = h.get_ignore_whitespace_diff(request.GET)
583 diff_context_size = h.get_diff_context_size(request.GET)
586 diff_context_size = h.get_diff_context_size(request.GET)
584 fulldiff = request.GET.get('fulldiff')
587 fulldiff = request.GET.get('fulldiff')
585 diff_limit = None if fulldiff else self.cut_off_limit
588 diff_limit = None if fulldiff else self.cut_off_limit
586
589
587 # we swap org/other ref since we run a simple diff on one repo
590 # we swap org/other ref since we run a simple diff on one repo
588 log.debug('running diff between %s and %s in %s',
591 log.debug('running diff between %s and %s in %s',
589 c.a_rev, c.cs_rev, org_scm_instance.path)
592 c.a_rev, c.cs_rev, org_scm_instance.path)
590 try:
593 try:
591 raw_diff = diffs.get_diff(org_scm_instance, rev1=c.a_rev, rev2=c.cs_rev,
594 raw_diff = diffs.get_diff(org_scm_instance, rev1=c.a_rev, rev2=c.cs_rev,
592 ignore_whitespace=ignore_whitespace_diff, context=diff_context_size)
595 ignore_whitespace=ignore_whitespace_diff, context=diff_context_size)
593 except ChangesetDoesNotExistError:
596 except ChangesetDoesNotExistError:
594 raw_diff = safe_bytes(_("The diff can't be shown - the PR revisions could not be found."))
597 raw_diff = safe_bytes(_("The diff can't be shown - the PR revisions could not be found."))
595 diff_processor = diffs.DiffProcessor(raw_diff, diff_limit=diff_limit)
598 diff_processor = diffs.DiffProcessor(raw_diff, diff_limit=diff_limit)
596 c.limited_diff = diff_processor.limited_diff
599 c.limited_diff = diff_processor.limited_diff
597 c.file_diff_data = []
600 c.file_diff_data = []
598 c.lines_added = 0
601 c.lines_added = 0
599 c.lines_deleted = 0
602 c.lines_deleted = 0
600
603
601 for f in diff_processor.parsed:
604 for f in diff_processor.parsed:
602 st = f['stats']
605 st = f['stats']
603 c.lines_added += st['added']
606 c.lines_added += st['added']
604 c.lines_deleted += st['deleted']
607 c.lines_deleted += st['deleted']
605 filename = f['filename']
608 filename = f['filename']
606 fid = h.FID('', filename)
609 fid = h.FID('', filename)
607 html_diff = diffs.as_html(parsed_lines=[f])
610 html_diff = diffs.as_html(parsed_lines=[f])
608 c.file_diff_data.append((fid, None, f['operation'], f['old_filename'], filename, html_diff, st))
611 c.file_diff_data.append((fid, None, f['operation'], f['old_filename'], filename, html_diff, st))
609
612
610 # inline comments
613 # inline comments
611 c.inline_cnt = 0
614 c.inline_cnt = 0
612 c.inline_comments = cc_model.get_inline_comments(
615 c.inline_comments = cc_model.get_inline_comments(
613 c.db_repo.repo_id,
616 c.db_repo.repo_id,
614 pull_request=pull_request_id)
617 pull_request=pull_request_id)
615 # count inline comments
618 # count inline comments
616 for __, lines in c.inline_comments:
619 for __, lines in c.inline_comments:
617 for comments in lines.values():
620 for comments in lines.values():
618 c.inline_cnt += len(comments)
621 c.inline_cnt += len(comments)
619 # comments
622 # comments
620 c.comments = cc_model.get_comments(c.db_repo.repo_id, pull_request=pull_request_id)
623 c.comments = cc_model.get_comments(c.db_repo.repo_id, pull_request=pull_request_id)
621
624
622 # (badly named) pull-request status calculation based on reviewer votes
625 # (badly named) pull-request status calculation based on reviewer votes
623 (c.pull_request_reviewers,
626 (c.pull_request_reviewers,
624 c.pull_request_pending_reviewers,
627 c.pull_request_pending_reviewers,
625 c.current_voting_result,
628 c.current_voting_result,
626 ) = cs_model.calculate_pull_request_result(c.pull_request)
629 ) = cs_model.calculate_pull_request_result(c.pull_request)
627 c.changeset_statuses = db.ChangesetStatus.STATUSES
630 c.changeset_statuses = db.ChangesetStatus.STATUSES
628
631
629 c.is_ajax_preview = False
632 c.is_ajax_preview = False
630 c.ancestors = None # [c.a_rev] ... but that is shown in an other way
633 c.ancestors = None # [c.a_rev] ... but that is shown in an other way
631 return base.render('/pullrequests/pullrequest_show.html')
634 return base.render('/pullrequests/pullrequest_show.html')
632
635
633 @LoginRequired()
636 @LoginRequired()
634 @HasRepoPermissionLevelDecorator('read')
637 @HasRepoPermissionLevelDecorator('read')
635 @base.jsonify
638 @base.jsonify
636 def comment(self, repo_name, pull_request_id):
639 def comment(self, repo_name, pull_request_id):
637 pull_request = db.PullRequest.get_or_404(pull_request_id)
640 pull_request = db.PullRequest.get_or_404(pull_request_id)
638 allowed_to_change_status = self._is_allowed_to_change_status(pull_request)
641 allowed_to_change_status = self._is_allowed_to_change_status(pull_request)
639 return create_cs_pr_comment(repo_name, pull_request=pull_request,
642 return create_cs_pr_comment(repo_name, pull_request=pull_request,
640 allowed_to_change_status=allowed_to_change_status)
643 allowed_to_change_status=allowed_to_change_status)
641
644
642 @LoginRequired()
645 @LoginRequired()
643 @HasRepoPermissionLevelDecorator('read')
646 @HasRepoPermissionLevelDecorator('read')
644 @base.jsonify
647 @base.jsonify
645 def delete_comment(self, repo_name, comment_id):
648 def delete_comment(self, repo_name, comment_id):
646 return delete_cs_pr_comment(repo_name, comment_id)
649 return delete_cs_pr_comment(repo_name, comment_id)
General Comments 0
You need to be logged in to leave comments. Login now