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