##// END OF EJS Templates
new summary for opened pull requests...
marcink -
r2712:7224882c beta
parent child Browse files
Show More
@@ -1,378 +1,382 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 from pylons.decorators import jsonify
36 from pylons.decorators import jsonify
37
37
38 from rhodecode.lib.compat import json
38 from rhodecode.lib.compat import json
39 from rhodecode.lib.base import BaseRepoController, render
39 from rhodecode.lib.base import BaseRepoController, render
40 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator,\
40 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator,\
41 NotAnonymous
41 NotAnonymous
42 from rhodecode.lib import helpers as h
42 from rhodecode.lib import helpers as h
43 from rhodecode.lib import diffs
43 from rhodecode.lib import diffs
44 from rhodecode.lib.utils import action_logger
44 from rhodecode.lib.utils import action_logger
45 from rhodecode.model.db import User, PullRequest, ChangesetStatus,\
45 from rhodecode.model.db import User, PullRequest, ChangesetStatus,\
46 ChangesetComment
46 ChangesetComment
47 from rhodecode.model.pull_request import PullRequestModel
47 from rhodecode.model.pull_request import PullRequestModel
48 from rhodecode.model.meta import Session
48 from rhodecode.model.meta import Session
49 from rhodecode.model.repo import RepoModel
49 from rhodecode.model.repo import RepoModel
50 from rhodecode.model.comment import ChangesetCommentsModel
50 from rhodecode.model.comment import ChangesetCommentsModel
51 from rhodecode.model.changeset_status import ChangesetStatusModel
51 from rhodecode.model.changeset_status import ChangesetStatusModel
52 from rhodecode.model.forms import PullRequestForm
52 from rhodecode.model.forms import PullRequestForm
53
53
54 log = logging.getLogger(__name__)
54 log = logging.getLogger(__name__)
55
55
56
56
57 class PullrequestsController(BaseRepoController):
57 class PullrequestsController(BaseRepoController):
58
58
59 @LoginRequired()
59 @LoginRequired()
60 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
60 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
61 'repository.admin')
61 'repository.admin')
62 def __before__(self):
62 def __before__(self):
63 super(PullrequestsController, self).__before__()
63 super(PullrequestsController, self).__before__()
64 repo_model = RepoModel()
64 repo_model = RepoModel()
65 c.users_array = repo_model.get_users_js()
65 c.users_array = repo_model.get_users_js()
66 c.users_groups_array = repo_model.get_users_groups_js()
66 c.users_groups_array = repo_model.get_users_groups_js()
67
67
68 def _get_repo_refs(self, repo):
68 def _get_repo_refs(self, repo):
69 hist_l = []
69 hist_l = []
70
70
71 branches_group = ([('branch:%s:%s' % (k, v), k) for
71 branches_group = ([('branch:%s:%s' % (k, v), k) for
72 k, v in repo.branches.iteritems()], _("Branches"))
72 k, v in repo.branches.iteritems()], _("Branches"))
73 bookmarks_group = ([('book:%s:%s' % (k, v), k) for
73 bookmarks_group = ([('book:%s:%s' % (k, v), k) for
74 k, v in repo.bookmarks.iteritems()], _("Bookmarks"))
74 k, v in repo.bookmarks.iteritems()], _("Bookmarks"))
75 tags_group = ([('tag:%s:%s' % (k, v), k) for
75 tags_group = ([('tag:%s:%s' % (k, v), k) for
76 k, v in repo.tags.iteritems()], _("Tags"))
76 k, v in repo.tags.iteritems()], _("Tags"))
77
77
78 hist_l.append(bookmarks_group)
78 hist_l.append(bookmarks_group)
79 hist_l.append(branches_group)
79 hist_l.append(branches_group)
80 hist_l.append(tags_group)
80 hist_l.append(tags_group)
81
81
82 return hist_l
82 return hist_l
83
83
84 def show_all(self, repo_name):
84 def show_all(self, repo_name):
85 c.pull_requests = PullRequestModel().get_all(repo_name)
85 c.pull_requests = PullRequestModel().get_all(repo_name)
86 c.repo_name = repo_name
86 c.repo_name = repo_name
87 return render('/pullrequests/pullrequest_show_all.html')
87 return render('/pullrequests/pullrequest_show_all.html')
88
88
89 @NotAnonymous()
89 @NotAnonymous()
90 def index(self):
90 def index(self):
91 org_repo = c.rhodecode_db_repo
91 org_repo = c.rhodecode_db_repo
92
92
93 if org_repo.scm_instance.alias != 'hg':
93 if org_repo.scm_instance.alias != 'hg':
94 log.error('Review not available for GIT REPOS')
94 log.error('Review not available for GIT REPOS')
95 raise HTTPNotFound
95 raise HTTPNotFound
96
96
97 other_repos_info = {}
97 other_repos_info = {}
98
98
99 c.org_refs = self._get_repo_refs(c.rhodecode_repo)
99 c.org_refs = self._get_repo_refs(c.rhodecode_repo)
100 c.org_repos = []
100 c.org_repos = []
101 c.other_repos = []
101 c.other_repos = []
102 c.org_repos.append((org_repo.repo_name, '%s/%s' % (
102 c.org_repos.append((org_repo.repo_name, '%s/%s' % (
103 org_repo.user.username, c.repo_name))
103 org_repo.user.username, c.repo_name))
104 )
104 )
105
105
106 c.other_refs = c.org_refs
106 c.other_refs = c.org_refs
107 c.other_repos.extend(c.org_repos)
107 c.other_repos.extend(c.org_repos)
108
108
109 #add orginal repo
109 #add orginal repo
110 other_repos_info[org_repo.repo_name] = {
110 other_repos_info[org_repo.repo_name] = {
111 'gravatar': h.gravatar_url(org_repo.user.email, 24),
111 'gravatar': h.gravatar_url(org_repo.user.email, 24),
112 'description': org_repo.description
112 'description': org_repo.description
113 }
113 }
114
114
115 c.default_pull_request = org_repo.repo_name
115 c.default_pull_request = org_repo.repo_name
116 #gather forks and add to this list
116 #gather forks and add to this list
117 for fork in org_repo.forks:
117 for fork in org_repo.forks:
118 c.other_repos.append((fork.repo_name, '%s/%s' % (
118 c.other_repos.append((fork.repo_name, '%s/%s' % (
119 fork.user.username, fork.repo_name))
119 fork.user.username, fork.repo_name))
120 )
120 )
121 other_repos_info[fork.repo_name] = {
121 other_repos_info[fork.repo_name] = {
122 'gravatar': h.gravatar_url(fork.user.email, 24),
122 'gravatar': h.gravatar_url(fork.user.email, 24),
123 'description': fork.description
123 'description': fork.description
124 }
124 }
125 #add parents of this fork also
125 #add parents of this fork also
126 if org_repo.parent:
126 if org_repo.parent:
127 c.default_pull_request = org_repo.parent.repo_name
127 c.default_pull_request = org_repo.parent.repo_name
128 c.other_repos.append((org_repo.parent.repo_name, '%s/%s' % (
128 c.other_repos.append((org_repo.parent.repo_name, '%s/%s' % (
129 org_repo.parent.user.username,
129 org_repo.parent.user.username,
130 org_repo.parent.repo_name))
130 org_repo.parent.repo_name))
131 )
131 )
132 other_repos_info[org_repo.parent.repo_name] = {
132 other_repos_info[org_repo.parent.repo_name] = {
133 'gravatar': h.gravatar_url(org_repo.parent.user.email, 24),
133 'gravatar': h.gravatar_url(org_repo.parent.user.email, 24),
134 'description': org_repo.parent.description
134 'description': org_repo.parent.description
135 }
135 }
136
136
137 c.other_repos_info = json.dumps(other_repos_info)
137 c.other_repos_info = json.dumps(other_repos_info)
138 c.review_members = [org_repo.user]
138 c.review_members = [org_repo.user]
139 return render('/pullrequests/pullrequest.html')
139 return render('/pullrequests/pullrequest.html')
140
140
141 @NotAnonymous()
141 @NotAnonymous()
142 def create(self, repo_name):
142 def create(self, repo_name):
143
143
144 try:
144 try:
145 _form = PullRequestForm()().to_python(request.POST)
145 _form = PullRequestForm()().to_python(request.POST)
146 except formencode.Invalid, errors:
146 except formencode.Invalid, errors:
147 log.error(traceback.format_exc())
147 log.error(traceback.format_exc())
148 if errors.error_dict.get('revisions'):
148 if errors.error_dict.get('revisions'):
149 msg = _('Cannot open a pull request with '
149 msg = _('Cannot open a pull request with '
150 'empty list of changesets')
150 'empty list of changesets')
151 elif errors.error_dict.get('pullrequest_title'):
151 elif errors.error_dict.get('pullrequest_title'):
152 msg = _('Pull request requires a title with min. 3 chars')
152 msg = _('Pull request requires a title with min. 3 chars')
153 else:
153 else:
154 msg = _('error during creation of pull request')
154 msg = _('error during creation of pull request')
155
155
156 h.flash(msg, 'error')
156 h.flash(msg, 'error')
157 return redirect(url('pullrequest_home', repo_name=repo_name))
157 return redirect(url('pullrequest_home', repo_name=repo_name))
158
158
159 org_repo = _form['org_repo']
159 org_repo = _form['org_repo']
160 org_ref = _form['org_ref']
160 org_ref = _form['org_ref']
161 other_repo = _form['other_repo']
161 other_repo = _form['other_repo']
162 other_ref = _form['other_ref']
162 other_ref = _form['other_ref']
163 revisions = _form['revisions']
163 revisions = _form['revisions']
164 reviewers = _form['review_members']
164 reviewers = _form['review_members']
165
165
166 title = _form['pullrequest_title']
166 title = _form['pullrequest_title']
167 description = _form['pullrequest_desc']
167 description = _form['pullrequest_desc']
168
168
169 try:
169 try:
170 pull_request = PullRequestModel().create(
170 pull_request = PullRequestModel().create(
171 self.rhodecode_user.user_id, org_repo, org_ref, other_repo,
171 self.rhodecode_user.user_id, org_repo, org_ref, other_repo,
172 other_ref, revisions, reviewers, title, description
172 other_ref, revisions, reviewers, title, description
173 )
173 )
174 Session().commit()
174 Session().commit()
175 h.flash(_('Successfully opened new pull request'),
175 h.flash(_('Successfully opened new pull request'),
176 category='success')
176 category='success')
177 except Exception:
177 except Exception:
178 h.flash(_('Error occurred during sending pull request'),
178 h.flash(_('Error occurred during sending pull request'),
179 category='error')
179 category='error')
180 log.error(traceback.format_exc())
180 log.error(traceback.format_exc())
181 return redirect(url('pullrequest_home', repo_name=repo_name))
181 return redirect(url('pullrequest_home', repo_name=repo_name))
182
182
183 return redirect(url('pullrequest_show', repo_name=other_repo,
183 return redirect(url('pullrequest_show', repo_name=other_repo,
184 pull_request_id=pull_request.pull_request_id))
184 pull_request_id=pull_request.pull_request_id))
185
185
186 @NotAnonymous()
186 @NotAnonymous()
187 @jsonify
187 @jsonify
188 def update(self, repo_name, pull_request_id):
188 def update(self, repo_name, pull_request_id):
189 pull_request = PullRequest.get_or_404(pull_request_id)
189 pull_request = PullRequest.get_or_404(pull_request_id)
190 if pull_request.is_closed():
190 if pull_request.is_closed():
191 raise HTTPForbidden()
191 raise HTTPForbidden()
192
192
193 reviewers_ids = map(int, filter(lambda v: v not in [None, ''],
193 reviewers_ids = map(int, filter(lambda v: v not in [None, ''],
194 request.POST.get('reviewers_ids', '').split(',')))
194 request.POST.get('reviewers_ids', '').split(',')))
195
195 PullRequestModel().update_reviewers(pull_request_id, reviewers_ids)
196 PullRequestModel().update_reviewers(pull_request_id, reviewers_ids)
196 Session.commit()
197 Session.commit()
197 return True
198 return True
198
199
199 def _load_compare_data(self, pull_request, enable_comments=True):
200 def _load_compare_data(self, pull_request, enable_comments=True):
200 """
201 """
201 Load context data needed for generating compare diff
202 Load context data needed for generating compare diff
202
203
203 :param pull_request:
204 :param pull_request:
204 :type pull_request:
205 :type pull_request:
205 """
206 """
206
207
207 org_repo = pull_request.org_repo
208 org_repo = pull_request.org_repo
208 (org_ref_type,
209 (org_ref_type,
209 org_ref_name,
210 org_ref_name,
210 org_ref_rev) = pull_request.org_ref.split(':')
211 org_ref_rev) = pull_request.org_ref.split(':')
211
212
212 other_repo = pull_request.other_repo
213 other_repo = pull_request.other_repo
213 (other_ref_type,
214 (other_ref_type,
214 other_ref_name,
215 other_ref_name,
215 other_ref_rev) = pull_request.other_ref.split(':')
216 other_ref_rev) = pull_request.other_ref.split(':')
216
217
217 # dispite opening revisions for bookmarks/branches/tags, we always
218 # dispite opening revisions for bookmarks/branches/tags, we always
218 # convert this to rev to prevent changes after book or branch change
219 # convert this to rev to prevent changes after book or branch change
219 org_ref = ('rev', org_ref_rev)
220 org_ref = ('rev', org_ref_rev)
220 other_ref = ('rev', other_ref_rev)
221 other_ref = ('rev', other_ref_rev)
221
222
222 c.org_repo = org_repo
223 c.org_repo = org_repo
223 c.other_repo = other_repo
224 c.other_repo = other_repo
224
225
225 c.cs_ranges, discovery_data = PullRequestModel().get_compare_data(
226 c.cs_ranges, discovery_data = PullRequestModel().get_compare_data(
226 org_repo, org_ref, other_repo, other_ref
227 org_repo, org_ref, other_repo, other_ref
227 )
228 )
228
229
229 c.statuses = c.rhodecode_db_repo.statuses([x.raw_id for x in
230 c.statuses = c.rhodecode_db_repo.statuses([x.raw_id for x in
230 c.cs_ranges])
231 c.cs_ranges])
231 # defines that we need hidden inputs with changesets
232 # defines that we need hidden inputs with changesets
232 c.as_form = request.GET.get('as_form', False)
233 c.as_form = request.GET.get('as_form', False)
233
234
234 c.org_ref = org_ref[1]
235 c.org_ref = org_ref[1]
235 c.other_ref = other_ref[1]
236 c.other_ref = other_ref[1]
236 # diff needs to have swapped org with other to generate proper diff
237 # diff needs to have swapped org with other to generate proper diff
237 _diff = diffs.differ(other_repo, other_ref, org_repo, org_ref,
238 _diff = diffs.differ(other_repo, other_ref, org_repo, org_ref,
238 discovery_data)
239 discovery_data)
239 diff_processor = diffs.DiffProcessor(_diff, format='gitdiff')
240 diff_processor = diffs.DiffProcessor(_diff, format='gitdiff')
240 _parsed = diff_processor.prepare()
241 _parsed = diff_processor.prepare()
241
242
242 c.files = []
243 c.files = []
243 c.changes = {}
244 c.changes = {}
244
245
245 for f in _parsed:
246 for f in _parsed:
246 fid = h.FID('', f['filename'])
247 fid = h.FID('', f['filename'])
247 c.files.append([fid, f['operation'], f['filename'], f['stats']])
248 c.files.append([fid, f['operation'], f['filename'], f['stats']])
248 diff = diff_processor.as_html(enable_comments=enable_comments,
249 diff = diff_processor.as_html(enable_comments=enable_comments,
249 diff_lines=[f])
250 diff_lines=[f])
250 c.changes[fid] = [f['operation'], f['filename'], diff]
251 c.changes[fid] = [f['operation'], f['filename'], diff]
251
252
252 def show(self, repo_name, pull_request_id):
253 def show(self, repo_name, pull_request_id):
253 repo_model = RepoModel()
254 repo_model = RepoModel()
254 c.users_array = repo_model.get_users_js()
255 c.users_array = repo_model.get_users_js()
255 c.users_groups_array = repo_model.get_users_groups_js()
256 c.users_groups_array = repo_model.get_users_groups_js()
256 c.pull_request = PullRequest.get_or_404(pull_request_id)
257 c.pull_request = PullRequest.get_or_404(pull_request_id)
257
258
258 cc_model = ChangesetCommentsModel()
259 cc_model = ChangesetCommentsModel()
259 cs_model = ChangesetStatusModel()
260 cs_model = ChangesetStatusModel()
260 _cs_statuses = cs_model.get_statuses(c.pull_request.org_repo,
261 _cs_statuses = cs_model.get_statuses(c.pull_request.org_repo,
261 pull_request=c.pull_request,
262 pull_request=c.pull_request,
262 with_revisions=True)
263 with_revisions=True)
263
264
264 cs_statuses = defaultdict(list)
265 cs_statuses = defaultdict(list)
265 for st in _cs_statuses:
266 for st in _cs_statuses:
266 cs_statuses[st.author.username] += [st]
267 cs_statuses[st.author.username] += [st]
267
268
268 c.pull_request_reviewers = []
269 c.pull_request_reviewers = []
270 c.pull_request_pending_reviewers = []
269 for o in c.pull_request.reviewers:
271 for o in c.pull_request.reviewers:
270 st = cs_statuses.get(o.user.username, None)
272 st = cs_statuses.get(o.user.username, None)
271 if st:
273 if st:
272 sorter = lambda k: k.version
274 sorter = lambda k: k.version
273 st = [(x, list(y)[0])
275 st = [(x, list(y)[0])
274 for x, y in (groupby(sorted(st, key=sorter), sorter))]
276 for x, y in (groupby(sorted(st, key=sorter), sorter))]
277 else:
278 c.pull_request_pending_reviewers.append(o.user)
275 c.pull_request_reviewers.append([o.user, st])
279 c.pull_request_reviewers.append([o.user, st])
276
280
277 # pull_requests repo_name we opened it against
281 # pull_requests repo_name we opened it against
278 # ie. other_repo must match
282 # ie. other_repo must match
279 if repo_name != c.pull_request.other_repo.repo_name:
283 if repo_name != c.pull_request.other_repo.repo_name:
280 raise HTTPNotFound
284 raise HTTPNotFound
281
285
282 # load compare data into template context
286 # load compare data into template context
283 enable_comments = not c.pull_request.is_closed()
287 enable_comments = not c.pull_request.is_closed()
284 self._load_compare_data(c.pull_request, enable_comments=enable_comments)
288 self._load_compare_data(c.pull_request, enable_comments=enable_comments)
285
289
286 # inline comments
290 # inline comments
287 c.inline_cnt = 0
291 c.inline_cnt = 0
288 c.inline_comments = cc_model.get_inline_comments(
292 c.inline_comments = cc_model.get_inline_comments(
289 c.rhodecode_db_repo.repo_id,
293 c.rhodecode_db_repo.repo_id,
290 pull_request=pull_request_id)
294 pull_request=pull_request_id)
291 # count inline comments
295 # count inline comments
292 for __, lines in c.inline_comments:
296 for __, lines in c.inline_comments:
293 for comments in lines.values():
297 for comments in lines.values():
294 c.inline_cnt += len(comments)
298 c.inline_cnt += len(comments)
295 # comments
299 # comments
296 c.comments = cc_model.get_comments(c.rhodecode_db_repo.repo_id,
300 c.comments = cc_model.get_comments(c.rhodecode_db_repo.repo_id,
297 pull_request=pull_request_id)
301 pull_request=pull_request_id)
298
302
299 # changeset(pull-request) status
303 # changeset(pull-request) status
300 c.current_changeset_status = cs_model.calculate_status(
304 c.current_changeset_status = cs_model.calculate_status(
301 c.pull_request_reviewers
305 c.pull_request_reviewers
302 )
306 )
303 c.changeset_statuses = ChangesetStatus.STATUSES
307 c.changeset_statuses = ChangesetStatus.STATUSES
304 c.target_repo = c.pull_request.org_repo.repo_name
308 c.target_repo = c.pull_request.org_repo.repo_name
305 return render('/pullrequests/pullrequest_show.html')
309 return render('/pullrequests/pullrequest_show.html')
306
310
307 @NotAnonymous()
311 @NotAnonymous()
308 @jsonify
312 @jsonify
309 def comment(self, repo_name, pull_request_id):
313 def comment(self, repo_name, pull_request_id):
310 pull_request = PullRequest.get_or_404(pull_request_id)
314 pull_request = PullRequest.get_or_404(pull_request_id)
311 if pull_request.is_closed():
315 if pull_request.is_closed():
312 raise HTTPForbidden()
316 raise HTTPForbidden()
313
317
314 status = request.POST.get('changeset_status')
318 status = request.POST.get('changeset_status')
315 change_status = request.POST.get('change_changeset_status')
319 change_status = request.POST.get('change_changeset_status')
316
320
317 comm = ChangesetCommentsModel().create(
321 comm = ChangesetCommentsModel().create(
318 text=request.POST.get('text'),
322 text=request.POST.get('text'),
319 repo=c.rhodecode_db_repo.repo_id,
323 repo=c.rhodecode_db_repo.repo_id,
320 user=c.rhodecode_user.user_id,
324 user=c.rhodecode_user.user_id,
321 pull_request=pull_request_id,
325 pull_request=pull_request_id,
322 f_path=request.POST.get('f_path'),
326 f_path=request.POST.get('f_path'),
323 line_no=request.POST.get('line'),
327 line_no=request.POST.get('line'),
324 status_change=(ChangesetStatus.get_status_lbl(status)
328 status_change=(ChangesetStatus.get_status_lbl(status)
325 if status and change_status else None)
329 if status and change_status else None)
326 )
330 )
327
331
328 # get status if set !
332 # get status if set !
329 if status and change_status:
333 if status and change_status:
330 ChangesetStatusModel().set_status(
334 ChangesetStatusModel().set_status(
331 c.rhodecode_db_repo.repo_id,
335 c.rhodecode_db_repo.repo_id,
332 status,
336 status,
333 c.rhodecode_user.user_id,
337 c.rhodecode_user.user_id,
334 comm,
338 comm,
335 pull_request=pull_request_id
339 pull_request=pull_request_id
336 )
340 )
337 action_logger(self.rhodecode_user,
341 action_logger(self.rhodecode_user,
338 'user_commented_pull_request:%s' % pull_request_id,
342 'user_commented_pull_request:%s' % pull_request_id,
339 c.rhodecode_db_repo, self.ip_addr, self.sa)
343 c.rhodecode_db_repo, self.ip_addr, self.sa)
340
344
341 if request.POST.get('save_close'):
345 if request.POST.get('save_close'):
342 PullRequestModel().close_pull_request(pull_request_id)
346 PullRequestModel().close_pull_request(pull_request_id)
343 action_logger(self.rhodecode_user,
347 action_logger(self.rhodecode_user,
344 'user_closed_pull_request:%s' % pull_request_id,
348 'user_closed_pull_request:%s' % pull_request_id,
345 c.rhodecode_db_repo, self.ip_addr, self.sa)
349 c.rhodecode_db_repo, self.ip_addr, self.sa)
346
350
347 Session().commit()
351 Session().commit()
348
352
349 if not request.environ.get('HTTP_X_PARTIAL_XHR'):
353 if not request.environ.get('HTTP_X_PARTIAL_XHR'):
350 return redirect(h.url('pullrequest_show', repo_name=repo_name,
354 return redirect(h.url('pullrequest_show', repo_name=repo_name,
351 pull_request_id=pull_request_id))
355 pull_request_id=pull_request_id))
352
356
353 data = {
357 data = {
354 'target_id': h.safeid(h.safe_unicode(request.POST.get('f_path'))),
358 'target_id': h.safeid(h.safe_unicode(request.POST.get('f_path'))),
355 }
359 }
356 if comm:
360 if comm:
357 c.co = comm
361 c.co = comm
358 data.update(comm.get_dict())
362 data.update(comm.get_dict())
359 data.update({'rendered_text':
363 data.update({'rendered_text':
360 render('changeset/changeset_comment_block.html')})
364 render('changeset/changeset_comment_block.html')})
361
365
362 return data
366 return data
363
367
364 @NotAnonymous()
368 @NotAnonymous()
365 @jsonify
369 @jsonify
366 def delete_comment(self, repo_name, comment_id):
370 def delete_comment(self, repo_name, comment_id):
367 co = ChangesetComment.get(comment_id)
371 co = ChangesetComment.get(comment_id)
368 if co.pull_request.is_closed():
372 if co.pull_request.is_closed():
369 #don't allow deleting comments on closed pull request
373 #don't allow deleting comments on closed pull request
370 raise HTTPForbidden()
374 raise HTTPForbidden()
371
375
372 owner = lambda: co.author.user_id == c.rhodecode_user.user_id
376 owner = lambda: co.author.user_id == c.rhodecode_user.user_id
373 if h.HasPermissionAny('hg.admin', 'repository.admin')() or owner:
377 if h.HasPermissionAny('hg.admin', 'repository.admin')() or owner:
374 ChangesetCommentsModel().delete(comment=co)
378 ChangesetCommentsModel().delete(comment=co)
375 Session().commit()
379 Session().commit()
376 return True
380 return True
377 else:
381 else:
378 raise HTTPForbidden()
382 raise HTTPForbidden()
@@ -1,174 +1,193 b''
1 <%inherit file="/base/base.html"/>
1 <%inherit file="/base/base.html"/>
2
2
3 <%def name="title()">
3 <%def name="title()">
4 ${c.repo_name} ${_('Pull request #%s') % c.pull_request.pull_request_id}
4 ${c.repo_name} ${_('Pull request #%s') % c.pull_request.pull_request_id}
5 </%def>
5 </%def>
6
6
7 <%def name="breadcrumbs_links()">
7 <%def name="breadcrumbs_links()">
8 ${h.link_to(_(u'Home'),h.url('/'))}
8 ${h.link_to(_(u'Home'),h.url('/'))}
9 &raquo;
9 &raquo;
10 ${h.link_to(c.repo_name,h.url('changelog_home',repo_name=c.repo_name))}
10 ${h.link_to(c.repo_name,h.url('changelog_home',repo_name=c.repo_name))}
11 &raquo;
11 &raquo;
12 ${_('Pull request #%s') % c.pull_request.pull_request_id}
12 ${_('Pull request #%s') % c.pull_request.pull_request_id}
13 </%def>
13 </%def>
14
14
15 <%def name="main()">
15 <%def name="main()">
16
16
17 <div class="box">
17 <div class="box">
18 <!-- box / title -->
18 <!-- box / title -->
19 <div class="title">
19 <div class="title">
20 ${self.breadcrumbs()}
20 ${self.breadcrumbs()}
21 </div>
21 </div>
22 %if c.pull_request.is_closed():
22 %if c.pull_request.is_closed():
23 <div style="padding:10px; font-size:22px;width:100%;text-align: center; color:#88D882">${_('Closed %s') % (h.age(c.pull_request.updated_on))}</div>
23 <div style="padding:10px; font-size:22px;width:100%;text-align: center; color:#88D882">${_('Closed %s') % (h.age(c.pull_request.updated_on))}</div>
24 %endif
24 %endif
25 <h3>${_('Title')}: ${c.pull_request.title}
25 <h3>${_('Title')}: ${c.pull_request.title}</h3>
26 <div class="changeset-status-container" style="float:none">
26
27 %if c.current_changeset_status:
27 <div class="form">
28 <div title="${_('Pull request status')}" class="changeset-status-lbl">[${h.changeset_status_lbl(c.current_changeset_status)}]</div>
28 <div id="summary" class="fields">
29 <div class="changeset-status-ico" style="padding:4px"><img src="${h.url('/images/icons/flag_status_%s.png' % c.current_changeset_status)}" /></div>
29 <div class="field">
30 %endif
30 <div class="label-summary">
31 </div>
31 <label>${_('Status')}:</label>
32 </h3>
32 </div>
33 <div class="input">
34 <div class="changeset-status-container" style="float:none;clear:both">
35 %if c.current_changeset_status:
36 <div title="${_('Pull request status')}" class="changeset-status-lbl">[${h.changeset_status_lbl(c.current_changeset_status)}]</div>
37 <div class="changeset-status-ico" style="padding:1px 4px"><img src="${h.url('/images/icons/flag_status_%s.png' % c.current_changeset_status)}" /></div>
38 %endif
39 </div>
40 </div>
41 </div>
42 <div class="field">
43 <div class="label-summary">
44 <label>${_('Still not reviewed by')}:</label>
45 </div>
46 <div class="input">
47 <div>${ungettext('%d reviewer', '%d reviewers',len(c.pull_request_pending_reviewers)) % len(c.pull_request_pending_reviewers)}</div>
48 </div>
49 </div>
50 </div>
51 </div>
33 <div style="white-space:pre-wrap;padding:3px 3px 5px 20px">${h.literal(c.pull_request.description)}</div>
52 <div style="white-space:pre-wrap;padding:3px 3px 5px 20px">${h.literal(c.pull_request.description)}</div>
34 <div style="padding:4px 4px 10px 20px">
53 <div style="padding:4px 4px 10px 20px">
35 <div>${_('Created on')}: ${h.fmt_date(c.pull_request.created_on)}</div>
54 <div>${_('Created on')}: ${h.fmt_date(c.pull_request.created_on)}</div>
36 </div>
55 </div>
37
56
38 <div style="min-height:160px">
57 <div style="min-height:160px">
39 ##DIFF
58 ##DIFF
40 <div class="table" style="float:left;clear:none">
59 <div class="table" style="float:left;clear:none">
41 <div id="body" class="diffblock">
60 <div id="body" class="diffblock">
42 <div style="white-space:pre-wrap;padding:5px">${_('Compare view')}</div>
61 <div style="white-space:pre-wrap;padding:5px">${_('Compare view')}</div>
43 </div>
62 </div>
44 <div id="changeset_compare_view_content">
63 <div id="changeset_compare_view_content">
45 ##CS
64 ##CS
46 <div style="font-size:1.1em;font-weight: bold;clear:both;padding-top:10px">${_('Incoming changesets')}</div>
65 <div style="font-size:1.1em;font-weight: bold;clear:both;padding-top:10px">${_('Incoming changesets')}</div>
47 <%include file="/compare/compare_cs.html" />
66 <%include file="/compare/compare_cs.html" />
48
67
49 ## FILES
68 ## FILES
50 <div style="font-size:1.1em;font-weight: bold;clear:both;padding-top:10px">${_('Files affected')}</div>
69 <div style="font-size:1.1em;font-weight: bold;clear:both;padding-top:10px">${_('Files affected')}</div>
51 <div class="cs_files">
70 <div class="cs_files">
52 %for fid, change, f, stat in c.files:
71 %for fid, change, f, stat in c.files:
53 <div class="cs_${change}">
72 <div class="cs_${change}">
54 <div class="node">${h.link_to(h.safe_unicode(f),h.url.current(anchor=fid))}</div>
73 <div class="node">${h.link_to(h.safe_unicode(f),h.url.current(anchor=fid))}</div>
55 <div class="changes">${h.fancy_file_stats(stat)}</div>
74 <div class="changes">${h.fancy_file_stats(stat)}</div>
56 </div>
75 </div>
57 %endfor
76 %endfor
58 </div>
77 </div>
59 </div>
78 </div>
60 </div>
79 </div>
61 ## REVIEWERS
80 ## REVIEWERS
62 <div style="float:left; border-left:1px dashed #eee">
81 <div style="float:left; border-left:1px dashed #eee">
63 <h4>${_('Pull request reviewers')}</h4>
82 <h4>${_('Pull request reviewers')}</h4>
64 <div id="reviewers" style="padding:0px 0px 0px 15px">
83 <div id="reviewers" style="padding:0px 0px 0px 15px">
65 ## members goes here !
84 ## members goes here !
66 <div class="group_members_wrap">
85 <div class="group_members_wrap">
67 <ul id="review_members" class="group_members">
86 <ul id="review_members" class="group_members">
68 %for member,status in c.pull_request_reviewers:
87 %for member,status in c.pull_request_reviewers:
69 <li id="reviewer_${member.user_id}">
88 <li id="reviewer_${member.user_id}">
70 <div class="reviewers_member">
89 <div class="reviewers_member">
71 <div style="float:left;padding:0px 3px 0px 0px" class="tooltip" title="${h.tooltip(h.changeset_status_lbl(status[0][1].status if status else 'not_reviewed'))}">
90 <div style="float:left;padding:0px 3px 0px 0px" class="tooltip" title="${h.tooltip(h.changeset_status_lbl(status[0][1].status if status else 'not_reviewed'))}">
72 <img src="${h.url(str('/images/icons/flag_status_%s.png' % (status[0][1].status if status else 'not_reviewed')))}"/>
91 <img src="${h.url(str('/images/icons/flag_status_%s.png' % (status[0][1].status if status else 'not_reviewed')))}"/>
73 </div>
92 </div>
74 <div class="gravatar"><img alt="gravatar" src="${h.gravatar_url(member.email,14)}"/> </div>
93 <div class="gravatar"><img alt="gravatar" src="${h.gravatar_url(member.email,14)}"/> </div>
75 <div style="float:left">${member.full_name} (${_('owner')})</div>
94 <div style="float:left">${member.full_name} (${_('owner')})</div>
76 <input type="hidden" value="${member.user_id}" name="review_members" />
95 <input type="hidden" value="${member.user_id}" name="review_members" />
77 %if not c.pull_request.is_closed():
96 %if not c.pull_request.is_closed():
78 <span class="delete_icon action_button" onclick="removeReviewer(${member.user_id})"></span>
97 <span class="delete_icon action_button" onclick="removeReviewer(${member.user_id})"></span>
79 %endif
98 %endif
80 </div>
99 </div>
81 </li>
100 </li>
82 %endfor
101 %endfor
83 </ul>
102 </ul>
84 </div>
103 </div>
85 %if not c.pull_request.is_closed():
104 %if not c.pull_request.is_closed():
86 <div class='ac'>
105 <div class='ac'>
87 <div class="reviewer_ac">
106 <div class="reviewer_ac">
88 ${h.text('user', class_='yui-ac-input')}
107 ${h.text('user', class_='yui-ac-input')}
89 <span class="help-block">${_('Add reviewer to this pull request.')}</span>
108 <span class="help-block">${_('Add reviewer to this pull request.')}</span>
90 <div id="reviewers_container"></div>
109 <div id="reviewers_container"></div>
91 </div>
110 </div>
92 <div style="padding:0px 10px">
111 <div style="padding:0px 10px">
93 <span id="update_pull_request" class="ui-btn xsmall">${_('save')}</span>
112 <span id="update_pull_request" class="ui-btn xsmall">${_('save')}</span>
94 </div>
113 </div>
95 </div>
114 </div>
96 %endif
115 %endif
97 </div>
116 </div>
98 </div>
117 </div>
99 </div>
118 </div>
100 <script>
119 <script>
101 var _USERS_AC_DATA = ${c.users_array|n};
120 var _USERS_AC_DATA = ${c.users_array|n};
102 var _GROUPS_AC_DATA = ${c.users_groups_array|n};
121 var _GROUPS_AC_DATA = ${c.users_groups_array|n};
103 AJAX_COMMENT_URL = "${url('pullrequest_comment',repo_name=c.repo_name,pull_request_id=c.pull_request.pull_request_id)}";
122 AJAX_COMMENT_URL = "${url('pullrequest_comment',repo_name=c.repo_name,pull_request_id=c.pull_request.pull_request_id)}";
104 AJAX_COMMENT_DELETE_URL = "${url('pullrequest_comment_delete',repo_name=c.repo_name,comment_id='__COMMENT_ID__')}";
123 AJAX_COMMENT_DELETE_URL = "${url('pullrequest_comment_delete',repo_name=c.repo_name,comment_id='__COMMENT_ID__')}";
105 AJAX_UPDATE_PULLREQUEST = "${url('pullrequest_update',repo_name=c.repo_name,pull_request_id=c.pull_request.pull_request_id)}"
124 AJAX_UPDATE_PULLREQUEST = "${url('pullrequest_update',repo_name=c.repo_name,pull_request_id=c.pull_request.pull_request_id)}"
106 </script>
125 </script>
107
126
108 ## diff block
127 ## diff block
109 <%namespace name="diff_block" file="/changeset/diff_block.html"/>
128 <%namespace name="diff_block" file="/changeset/diff_block.html"/>
110 %for fid, change, f, stat in c.files:
129 %for fid, change, f, stat in c.files:
111 ${diff_block.diff_block_simple([c.changes[fid]])}
130 ${diff_block.diff_block_simple([c.changes[fid]])}
112 %endfor
131 %endfor
113
132
114 ## template for inline comment form
133 ## template for inline comment form
115 <%namespace name="comment" file="/changeset/changeset_file_comment.html"/>
134 <%namespace name="comment" file="/changeset/changeset_file_comment.html"/>
116 ${comment.comment_inline_form()}
135 ${comment.comment_inline_form()}
117
136
118 ## render comments and inlines
137 ## render comments and inlines
119 ${comment.generate_comments()}
138 ${comment.generate_comments()}
120
139
121 % if not c.pull_request.is_closed():
140 % if not c.pull_request.is_closed():
122 ## main comment form and it status
141 ## main comment form and it status
123 ${comment.comments(h.url('pullrequest_comment', repo_name=c.repo_name,
142 ${comment.comments(h.url('pullrequest_comment', repo_name=c.repo_name,
124 pull_request_id=c.pull_request.pull_request_id),
143 pull_request_id=c.pull_request.pull_request_id),
125 c.current_changeset_status,
144 c.current_changeset_status,
126 close_btn=True)}
145 close_btn=True)}
127 %endif
146 %endif
128
147
129 <script type="text/javascript">
148 <script type="text/javascript">
130 YUE.onDOMReady(function(){
149 YUE.onDOMReady(function(){
131 PullRequestAutoComplete('user', 'reviewers_container', _USERS_AC_DATA, _GROUPS_AC_DATA);
150 PullRequestAutoComplete('user', 'reviewers_container', _USERS_AC_DATA, _GROUPS_AC_DATA);
132
151
133 YUE.on(YUQ('.show-inline-comments'),'change',function(e){
152 YUE.on(YUQ('.show-inline-comments'),'change',function(e){
134 var show = 'none';
153 var show = 'none';
135 var target = e.currentTarget;
154 var target = e.currentTarget;
136 if(target.checked){
155 if(target.checked){
137 var show = ''
156 var show = ''
138 }
157 }
139 var boxid = YUD.getAttribute(target,'id_for');
158 var boxid = YUD.getAttribute(target,'id_for');
140 var comments = YUQ('#{0} .inline-comments'.format(boxid));
159 var comments = YUQ('#{0} .inline-comments'.format(boxid));
141 for(c in comments){
160 for(c in comments){
142 YUD.setStyle(comments[c],'display',show);
161 YUD.setStyle(comments[c],'display',show);
143 }
162 }
144 var btns = YUQ('#{0} .inline-comments-button'.format(boxid));
163 var btns = YUQ('#{0} .inline-comments-button'.format(boxid));
145 for(c in btns){
164 for(c in btns){
146 YUD.setStyle(btns[c],'display',show);
165 YUD.setStyle(btns[c],'display',show);
147 }
166 }
148 })
167 })
149
168
150 YUE.on(YUQ('.line'),'click',function(e){
169 YUE.on(YUQ('.line'),'click',function(e){
151 var tr = e.currentTarget;
170 var tr = e.currentTarget;
152 injectInlineForm(tr);
171 injectInlineForm(tr);
153 });
172 });
154
173
155 // inject comments into they proper positions
174 // inject comments into they proper positions
156 var file_comments = YUQ('.inline-comment-placeholder');
175 var file_comments = YUQ('.inline-comment-placeholder');
157 renderInlineComments(file_comments);
176 renderInlineComments(file_comments);
158
177
159 YUE.on(YUD.get('update_pull_request'),'click',function(e){
178 YUE.on(YUD.get('update_pull_request'),'click',function(e){
160
179
161 var reviewers_ids = [];
180 var reviewers_ids = [];
162 var ids = YUQ('#review_members input');
181 var ids = YUQ('#review_members input');
163 for(var i=0; i<ids.length;i++){
182 for(var i=0; i<ids.length;i++){
164 var id = ids[i].value
183 var id = ids[i].value
165 reviewers_ids.push(id);
184 reviewers_ids.push(id);
166 }
185 }
167 updateReviewers(reviewers_ids);
186 updateReviewers(reviewers_ids);
168 })
187 })
169 })
188 })
170 </script>
189 </script>
171
190
172 </div>
191 </div>
173
192
174 </%def>
193 </%def>
General Comments 0
You need to be logged in to leave comments. Login now