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