##// END OF EJS Templates
pull-requests: show pr version in the my-account and repo pr listing grids.
milka -
r4557:5a275c9f default
parent child Browse files
Show More
@@ -1,822 +1,823 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2020 RhodeCode GmbH
3 # Copyright (C) 2016-2020 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import logging
21 import logging
22 import datetime
22 import datetime
23 import string
23 import string
24
24
25 import formencode
25 import formencode
26 import formencode.htmlfill
26 import formencode.htmlfill
27 import peppercorn
27 import peppercorn
28 from pyramid.httpexceptions import HTTPFound, HTTPNotFound
28 from pyramid.httpexceptions import HTTPFound, HTTPNotFound
29 from pyramid.view import view_config
29 from pyramid.view import view_config
30
30
31 from rhodecode.apps._base import BaseAppView, DataGridAppView
31 from rhodecode.apps._base import BaseAppView, DataGridAppView
32 from rhodecode import forms
32 from rhodecode import forms
33 from rhodecode.lib import helpers as h
33 from rhodecode.lib import helpers as h
34 from rhodecode.lib import audit_logger
34 from rhodecode.lib import audit_logger
35 from rhodecode.lib.ext_json import json
35 from rhodecode.lib.ext_json import json
36 from rhodecode.lib.auth import (
36 from rhodecode.lib.auth import (
37 LoginRequired, NotAnonymous, CSRFRequired,
37 LoginRequired, NotAnonymous, CSRFRequired,
38 HasRepoPermissionAny, HasRepoGroupPermissionAny, AuthUser)
38 HasRepoPermissionAny, HasRepoGroupPermissionAny, AuthUser)
39 from rhodecode.lib.channelstream import (
39 from rhodecode.lib.channelstream import (
40 channelstream_request, ChannelstreamException)
40 channelstream_request, ChannelstreamException)
41 from rhodecode.lib.utils2 import safe_int, md5, str2bool
41 from rhodecode.lib.utils2 import safe_int, md5, str2bool
42 from rhodecode.model.auth_token import AuthTokenModel
42 from rhodecode.model.auth_token import AuthTokenModel
43 from rhodecode.model.comment import CommentsModel
43 from rhodecode.model.comment import CommentsModel
44 from rhodecode.model.db import (
44 from rhodecode.model.db import (
45 IntegrityError, or_, in_filter_generator,
45 IntegrityError, or_, in_filter_generator,
46 Repository, UserEmailMap, UserApiKeys, UserFollowing,
46 Repository, UserEmailMap, UserApiKeys, UserFollowing,
47 PullRequest, UserBookmark, RepoGroup)
47 PullRequest, UserBookmark, RepoGroup)
48 from rhodecode.model.meta import Session
48 from rhodecode.model.meta import Session
49 from rhodecode.model.pull_request import PullRequestModel
49 from rhodecode.model.pull_request import PullRequestModel
50 from rhodecode.model.user import UserModel
50 from rhodecode.model.user import UserModel
51 from rhodecode.model.user_group import UserGroupModel
51 from rhodecode.model.user_group import UserGroupModel
52 from rhodecode.model.validation_schema.schemas import user_schema
52 from rhodecode.model.validation_schema.schemas import user_schema
53
53
54 log = logging.getLogger(__name__)
54 log = logging.getLogger(__name__)
55
55
56
56
57 class MyAccountView(BaseAppView, DataGridAppView):
57 class MyAccountView(BaseAppView, DataGridAppView):
58 ALLOW_SCOPED_TOKENS = False
58 ALLOW_SCOPED_TOKENS = False
59 """
59 """
60 This view has alternative version inside EE, if modified please take a look
60 This view has alternative version inside EE, if modified please take a look
61 in there as well.
61 in there as well.
62 """
62 """
63
63
64 def load_default_context(self):
64 def load_default_context(self):
65 c = self._get_local_tmpl_context()
65 c = self._get_local_tmpl_context()
66 c.user = c.auth_user.get_instance()
66 c.user = c.auth_user.get_instance()
67 c.allow_scoped_tokens = self.ALLOW_SCOPED_TOKENS
67 c.allow_scoped_tokens = self.ALLOW_SCOPED_TOKENS
68
68
69 return c
69 return c
70
70
71 @LoginRequired()
71 @LoginRequired()
72 @NotAnonymous()
72 @NotAnonymous()
73 @view_config(
73 @view_config(
74 route_name='my_account_profile', request_method='GET',
74 route_name='my_account_profile', request_method='GET',
75 renderer='rhodecode:templates/admin/my_account/my_account.mako')
75 renderer='rhodecode:templates/admin/my_account/my_account.mako')
76 def my_account_profile(self):
76 def my_account_profile(self):
77 c = self.load_default_context()
77 c = self.load_default_context()
78 c.active = 'profile'
78 c.active = 'profile'
79 c.extern_type = c.user.extern_type
79 c.extern_type = c.user.extern_type
80 return self._get_template_context(c)
80 return self._get_template_context(c)
81
81
82 @LoginRequired()
82 @LoginRequired()
83 @NotAnonymous()
83 @NotAnonymous()
84 @view_config(
84 @view_config(
85 route_name='my_account_password', request_method='GET',
85 route_name='my_account_password', request_method='GET',
86 renderer='rhodecode:templates/admin/my_account/my_account.mako')
86 renderer='rhodecode:templates/admin/my_account/my_account.mako')
87 def my_account_password(self):
87 def my_account_password(self):
88 c = self.load_default_context()
88 c = self.load_default_context()
89 c.active = 'password'
89 c.active = 'password'
90 c.extern_type = c.user.extern_type
90 c.extern_type = c.user.extern_type
91
91
92 schema = user_schema.ChangePasswordSchema().bind(
92 schema = user_schema.ChangePasswordSchema().bind(
93 username=c.user.username)
93 username=c.user.username)
94
94
95 form = forms.Form(
95 form = forms.Form(
96 schema,
96 schema,
97 action=h.route_path('my_account_password_update'),
97 action=h.route_path('my_account_password_update'),
98 buttons=(forms.buttons.save, forms.buttons.reset))
98 buttons=(forms.buttons.save, forms.buttons.reset))
99
99
100 c.form = form
100 c.form = form
101 return self._get_template_context(c)
101 return self._get_template_context(c)
102
102
103 @LoginRequired()
103 @LoginRequired()
104 @NotAnonymous()
104 @NotAnonymous()
105 @CSRFRequired()
105 @CSRFRequired()
106 @view_config(
106 @view_config(
107 route_name='my_account_password_update', request_method='POST',
107 route_name='my_account_password_update', request_method='POST',
108 renderer='rhodecode:templates/admin/my_account/my_account.mako')
108 renderer='rhodecode:templates/admin/my_account/my_account.mako')
109 def my_account_password_update(self):
109 def my_account_password_update(self):
110 _ = self.request.translate
110 _ = self.request.translate
111 c = self.load_default_context()
111 c = self.load_default_context()
112 c.active = 'password'
112 c.active = 'password'
113 c.extern_type = c.user.extern_type
113 c.extern_type = c.user.extern_type
114
114
115 schema = user_schema.ChangePasswordSchema().bind(
115 schema = user_schema.ChangePasswordSchema().bind(
116 username=c.user.username)
116 username=c.user.username)
117
117
118 form = forms.Form(
118 form = forms.Form(
119 schema, buttons=(forms.buttons.save, forms.buttons.reset))
119 schema, buttons=(forms.buttons.save, forms.buttons.reset))
120
120
121 if c.extern_type != 'rhodecode':
121 if c.extern_type != 'rhodecode':
122 raise HTTPFound(self.request.route_path('my_account_password'))
122 raise HTTPFound(self.request.route_path('my_account_password'))
123
123
124 controls = self.request.POST.items()
124 controls = self.request.POST.items()
125 try:
125 try:
126 valid_data = form.validate(controls)
126 valid_data = form.validate(controls)
127 UserModel().update_user(c.user.user_id, **valid_data)
127 UserModel().update_user(c.user.user_id, **valid_data)
128 c.user.update_userdata(force_password_change=False)
128 c.user.update_userdata(force_password_change=False)
129 Session().commit()
129 Session().commit()
130 except forms.ValidationFailure as e:
130 except forms.ValidationFailure as e:
131 c.form = e
131 c.form = e
132 return self._get_template_context(c)
132 return self._get_template_context(c)
133
133
134 except Exception:
134 except Exception:
135 log.exception("Exception updating password")
135 log.exception("Exception updating password")
136 h.flash(_('Error occurred during update of user password'),
136 h.flash(_('Error occurred during update of user password'),
137 category='error')
137 category='error')
138 else:
138 else:
139 instance = c.auth_user.get_instance()
139 instance = c.auth_user.get_instance()
140 self.session.setdefault('rhodecode_user', {}).update(
140 self.session.setdefault('rhodecode_user', {}).update(
141 {'password': md5(instance.password)})
141 {'password': md5(instance.password)})
142 self.session.save()
142 self.session.save()
143 h.flash(_("Successfully updated password"), category='success')
143 h.flash(_("Successfully updated password"), category='success')
144
144
145 raise HTTPFound(self.request.route_path('my_account_password'))
145 raise HTTPFound(self.request.route_path('my_account_password'))
146
146
147 @LoginRequired()
147 @LoginRequired()
148 @NotAnonymous()
148 @NotAnonymous()
149 @view_config(
149 @view_config(
150 route_name='my_account_auth_tokens', request_method='GET',
150 route_name='my_account_auth_tokens', request_method='GET',
151 renderer='rhodecode:templates/admin/my_account/my_account.mako')
151 renderer='rhodecode:templates/admin/my_account/my_account.mako')
152 def my_account_auth_tokens(self):
152 def my_account_auth_tokens(self):
153 _ = self.request.translate
153 _ = self.request.translate
154
154
155 c = self.load_default_context()
155 c = self.load_default_context()
156 c.active = 'auth_tokens'
156 c.active = 'auth_tokens'
157 c.lifetime_values = AuthTokenModel.get_lifetime_values(translator=_)
157 c.lifetime_values = AuthTokenModel.get_lifetime_values(translator=_)
158 c.role_values = [
158 c.role_values = [
159 (x, AuthTokenModel.cls._get_role_name(x))
159 (x, AuthTokenModel.cls._get_role_name(x))
160 for x in AuthTokenModel.cls.ROLES]
160 for x in AuthTokenModel.cls.ROLES]
161 c.role_options = [(c.role_values, _("Role"))]
161 c.role_options = [(c.role_values, _("Role"))]
162 c.user_auth_tokens = AuthTokenModel().get_auth_tokens(
162 c.user_auth_tokens = AuthTokenModel().get_auth_tokens(
163 c.user.user_id, show_expired=True)
163 c.user.user_id, show_expired=True)
164 c.role_vcs = AuthTokenModel.cls.ROLE_VCS
164 c.role_vcs = AuthTokenModel.cls.ROLE_VCS
165 return self._get_template_context(c)
165 return self._get_template_context(c)
166
166
167 @LoginRequired()
167 @LoginRequired()
168 @NotAnonymous()
168 @NotAnonymous()
169 @CSRFRequired()
169 @CSRFRequired()
170 @view_config(
170 @view_config(
171 route_name='my_account_auth_tokens_view', request_method='POST', xhr=True,
171 route_name='my_account_auth_tokens_view', request_method='POST', xhr=True,
172 renderer='json_ext')
172 renderer='json_ext')
173 def my_account_auth_tokens_view(self):
173 def my_account_auth_tokens_view(self):
174 _ = self.request.translate
174 _ = self.request.translate
175 c = self.load_default_context()
175 c = self.load_default_context()
176
176
177 auth_token_id = self.request.POST.get('auth_token_id')
177 auth_token_id = self.request.POST.get('auth_token_id')
178
178
179 if auth_token_id:
179 if auth_token_id:
180 token = UserApiKeys.get_or_404(auth_token_id)
180 token = UserApiKeys.get_or_404(auth_token_id)
181 if token.user.user_id != c.user.user_id:
181 if token.user.user_id != c.user.user_id:
182 raise HTTPNotFound()
182 raise HTTPNotFound()
183
183
184 return {
184 return {
185 'auth_token': token.api_key
185 'auth_token': token.api_key
186 }
186 }
187
187
188 def maybe_attach_token_scope(self, token):
188 def maybe_attach_token_scope(self, token):
189 # implemented in EE edition
189 # implemented in EE edition
190 pass
190 pass
191
191
192 @LoginRequired()
192 @LoginRequired()
193 @NotAnonymous()
193 @NotAnonymous()
194 @CSRFRequired()
194 @CSRFRequired()
195 @view_config(
195 @view_config(
196 route_name='my_account_auth_tokens_add', request_method='POST',)
196 route_name='my_account_auth_tokens_add', request_method='POST',)
197 def my_account_auth_tokens_add(self):
197 def my_account_auth_tokens_add(self):
198 _ = self.request.translate
198 _ = self.request.translate
199 c = self.load_default_context()
199 c = self.load_default_context()
200
200
201 lifetime = safe_int(self.request.POST.get('lifetime'), -1)
201 lifetime = safe_int(self.request.POST.get('lifetime'), -1)
202 description = self.request.POST.get('description')
202 description = self.request.POST.get('description')
203 role = self.request.POST.get('role')
203 role = self.request.POST.get('role')
204
204
205 token = UserModel().add_auth_token(
205 token = UserModel().add_auth_token(
206 user=c.user.user_id,
206 user=c.user.user_id,
207 lifetime_minutes=lifetime, role=role, description=description,
207 lifetime_minutes=lifetime, role=role, description=description,
208 scope_callback=self.maybe_attach_token_scope)
208 scope_callback=self.maybe_attach_token_scope)
209 token_data = token.get_api_data()
209 token_data = token.get_api_data()
210
210
211 audit_logger.store_web(
211 audit_logger.store_web(
212 'user.edit.token.add', action_data={
212 'user.edit.token.add', action_data={
213 'data': {'token': token_data, 'user': 'self'}},
213 'data': {'token': token_data, 'user': 'self'}},
214 user=self._rhodecode_user, )
214 user=self._rhodecode_user, )
215 Session().commit()
215 Session().commit()
216
216
217 h.flash(_("Auth token successfully created"), category='success')
217 h.flash(_("Auth token successfully created"), category='success')
218 return HTTPFound(h.route_path('my_account_auth_tokens'))
218 return HTTPFound(h.route_path('my_account_auth_tokens'))
219
219
220 @LoginRequired()
220 @LoginRequired()
221 @NotAnonymous()
221 @NotAnonymous()
222 @CSRFRequired()
222 @CSRFRequired()
223 @view_config(
223 @view_config(
224 route_name='my_account_auth_tokens_delete', request_method='POST')
224 route_name='my_account_auth_tokens_delete', request_method='POST')
225 def my_account_auth_tokens_delete(self):
225 def my_account_auth_tokens_delete(self):
226 _ = self.request.translate
226 _ = self.request.translate
227 c = self.load_default_context()
227 c = self.load_default_context()
228
228
229 del_auth_token = self.request.POST.get('del_auth_token')
229 del_auth_token = self.request.POST.get('del_auth_token')
230
230
231 if del_auth_token:
231 if del_auth_token:
232 token = UserApiKeys.get_or_404(del_auth_token)
232 token = UserApiKeys.get_or_404(del_auth_token)
233 token_data = token.get_api_data()
233 token_data = token.get_api_data()
234
234
235 AuthTokenModel().delete(del_auth_token, c.user.user_id)
235 AuthTokenModel().delete(del_auth_token, c.user.user_id)
236 audit_logger.store_web(
236 audit_logger.store_web(
237 'user.edit.token.delete', action_data={
237 'user.edit.token.delete', action_data={
238 'data': {'token': token_data, 'user': 'self'}},
238 'data': {'token': token_data, 'user': 'self'}},
239 user=self._rhodecode_user,)
239 user=self._rhodecode_user,)
240 Session().commit()
240 Session().commit()
241 h.flash(_("Auth token successfully deleted"), category='success')
241 h.flash(_("Auth token successfully deleted"), category='success')
242
242
243 return HTTPFound(h.route_path('my_account_auth_tokens'))
243 return HTTPFound(h.route_path('my_account_auth_tokens'))
244
244
245 @LoginRequired()
245 @LoginRequired()
246 @NotAnonymous()
246 @NotAnonymous()
247 @view_config(
247 @view_config(
248 route_name='my_account_emails', request_method='GET',
248 route_name='my_account_emails', request_method='GET',
249 renderer='rhodecode:templates/admin/my_account/my_account.mako')
249 renderer='rhodecode:templates/admin/my_account/my_account.mako')
250 def my_account_emails(self):
250 def my_account_emails(self):
251 _ = self.request.translate
251 _ = self.request.translate
252
252
253 c = self.load_default_context()
253 c = self.load_default_context()
254 c.active = 'emails'
254 c.active = 'emails'
255
255
256 c.user_email_map = UserEmailMap.query()\
256 c.user_email_map = UserEmailMap.query()\
257 .filter(UserEmailMap.user == c.user).all()
257 .filter(UserEmailMap.user == c.user).all()
258
258
259 schema = user_schema.AddEmailSchema().bind(
259 schema = user_schema.AddEmailSchema().bind(
260 username=c.user.username, user_emails=c.user.emails)
260 username=c.user.username, user_emails=c.user.emails)
261
261
262 form = forms.RcForm(schema,
262 form = forms.RcForm(schema,
263 action=h.route_path('my_account_emails_add'),
263 action=h.route_path('my_account_emails_add'),
264 buttons=(forms.buttons.save, forms.buttons.reset))
264 buttons=(forms.buttons.save, forms.buttons.reset))
265
265
266 c.form = form
266 c.form = form
267 return self._get_template_context(c)
267 return self._get_template_context(c)
268
268
269 @LoginRequired()
269 @LoginRequired()
270 @NotAnonymous()
270 @NotAnonymous()
271 @CSRFRequired()
271 @CSRFRequired()
272 @view_config(
272 @view_config(
273 route_name='my_account_emails_add', request_method='POST',
273 route_name='my_account_emails_add', request_method='POST',
274 renderer='rhodecode:templates/admin/my_account/my_account.mako')
274 renderer='rhodecode:templates/admin/my_account/my_account.mako')
275 def my_account_emails_add(self):
275 def my_account_emails_add(self):
276 _ = self.request.translate
276 _ = self.request.translate
277 c = self.load_default_context()
277 c = self.load_default_context()
278 c.active = 'emails'
278 c.active = 'emails'
279
279
280 schema = user_schema.AddEmailSchema().bind(
280 schema = user_schema.AddEmailSchema().bind(
281 username=c.user.username, user_emails=c.user.emails)
281 username=c.user.username, user_emails=c.user.emails)
282
282
283 form = forms.RcForm(
283 form = forms.RcForm(
284 schema, action=h.route_path('my_account_emails_add'),
284 schema, action=h.route_path('my_account_emails_add'),
285 buttons=(forms.buttons.save, forms.buttons.reset))
285 buttons=(forms.buttons.save, forms.buttons.reset))
286
286
287 controls = self.request.POST.items()
287 controls = self.request.POST.items()
288 try:
288 try:
289 valid_data = form.validate(controls)
289 valid_data = form.validate(controls)
290 UserModel().add_extra_email(c.user.user_id, valid_data['email'])
290 UserModel().add_extra_email(c.user.user_id, valid_data['email'])
291 audit_logger.store_web(
291 audit_logger.store_web(
292 'user.edit.email.add', action_data={
292 'user.edit.email.add', action_data={
293 'data': {'email': valid_data['email'], 'user': 'self'}},
293 'data': {'email': valid_data['email'], 'user': 'self'}},
294 user=self._rhodecode_user,)
294 user=self._rhodecode_user,)
295 Session().commit()
295 Session().commit()
296 except formencode.Invalid as error:
296 except formencode.Invalid as error:
297 h.flash(h.escape(error.error_dict['email']), category='error')
297 h.flash(h.escape(error.error_dict['email']), category='error')
298 except forms.ValidationFailure as e:
298 except forms.ValidationFailure as e:
299 c.user_email_map = UserEmailMap.query() \
299 c.user_email_map = UserEmailMap.query() \
300 .filter(UserEmailMap.user == c.user).all()
300 .filter(UserEmailMap.user == c.user).all()
301 c.form = e
301 c.form = e
302 return self._get_template_context(c)
302 return self._get_template_context(c)
303 except Exception:
303 except Exception:
304 log.exception("Exception adding email")
304 log.exception("Exception adding email")
305 h.flash(_('Error occurred during adding email'),
305 h.flash(_('Error occurred during adding email'),
306 category='error')
306 category='error')
307 else:
307 else:
308 h.flash(_("Successfully added email"), category='success')
308 h.flash(_("Successfully added email"), category='success')
309
309
310 raise HTTPFound(self.request.route_path('my_account_emails'))
310 raise HTTPFound(self.request.route_path('my_account_emails'))
311
311
312 @LoginRequired()
312 @LoginRequired()
313 @NotAnonymous()
313 @NotAnonymous()
314 @CSRFRequired()
314 @CSRFRequired()
315 @view_config(
315 @view_config(
316 route_name='my_account_emails_delete', request_method='POST')
316 route_name='my_account_emails_delete', request_method='POST')
317 def my_account_emails_delete(self):
317 def my_account_emails_delete(self):
318 _ = self.request.translate
318 _ = self.request.translate
319 c = self.load_default_context()
319 c = self.load_default_context()
320
320
321 del_email_id = self.request.POST.get('del_email_id')
321 del_email_id = self.request.POST.get('del_email_id')
322 if del_email_id:
322 if del_email_id:
323 email = UserEmailMap.get_or_404(del_email_id).email
323 email = UserEmailMap.get_or_404(del_email_id).email
324 UserModel().delete_extra_email(c.user.user_id, del_email_id)
324 UserModel().delete_extra_email(c.user.user_id, del_email_id)
325 audit_logger.store_web(
325 audit_logger.store_web(
326 'user.edit.email.delete', action_data={
326 'user.edit.email.delete', action_data={
327 'data': {'email': email, 'user': 'self'}},
327 'data': {'email': email, 'user': 'self'}},
328 user=self._rhodecode_user,)
328 user=self._rhodecode_user,)
329 Session().commit()
329 Session().commit()
330 h.flash(_("Email successfully deleted"),
330 h.flash(_("Email successfully deleted"),
331 category='success')
331 category='success')
332 return HTTPFound(h.route_path('my_account_emails'))
332 return HTTPFound(h.route_path('my_account_emails'))
333
333
334 @LoginRequired()
334 @LoginRequired()
335 @NotAnonymous()
335 @NotAnonymous()
336 @CSRFRequired()
336 @CSRFRequired()
337 @view_config(
337 @view_config(
338 route_name='my_account_notifications_test_channelstream',
338 route_name='my_account_notifications_test_channelstream',
339 request_method='POST', renderer='json_ext')
339 request_method='POST', renderer='json_ext')
340 def my_account_notifications_test_channelstream(self):
340 def my_account_notifications_test_channelstream(self):
341 message = 'Test message sent via Channelstream by user: {}, on {}'.format(
341 message = 'Test message sent via Channelstream by user: {}, on {}'.format(
342 self._rhodecode_user.username, datetime.datetime.now())
342 self._rhodecode_user.username, datetime.datetime.now())
343 payload = {
343 payload = {
344 # 'channel': 'broadcast',
344 # 'channel': 'broadcast',
345 'type': 'message',
345 'type': 'message',
346 'timestamp': datetime.datetime.utcnow(),
346 'timestamp': datetime.datetime.utcnow(),
347 'user': 'system',
347 'user': 'system',
348 'pm_users': [self._rhodecode_user.username],
348 'pm_users': [self._rhodecode_user.username],
349 'message': {
349 'message': {
350 'message': message,
350 'message': message,
351 'level': 'info',
351 'level': 'info',
352 'topic': '/notifications'
352 'topic': '/notifications'
353 }
353 }
354 }
354 }
355
355
356 registry = self.request.registry
356 registry = self.request.registry
357 rhodecode_plugins = getattr(registry, 'rhodecode_plugins', {})
357 rhodecode_plugins = getattr(registry, 'rhodecode_plugins', {})
358 channelstream_config = rhodecode_plugins.get('channelstream', {})
358 channelstream_config = rhodecode_plugins.get('channelstream', {})
359
359
360 try:
360 try:
361 channelstream_request(channelstream_config, [payload], '/message')
361 channelstream_request(channelstream_config, [payload], '/message')
362 except ChannelstreamException as e:
362 except ChannelstreamException as e:
363 log.exception('Failed to send channelstream data')
363 log.exception('Failed to send channelstream data')
364 return {"response": 'ERROR: {}'.format(e.__class__.__name__)}
364 return {"response": 'ERROR: {}'.format(e.__class__.__name__)}
365 return {"response": 'Channelstream data sent. '
365 return {"response": 'Channelstream data sent. '
366 'You should see a new live message now.'}
366 'You should see a new live message now.'}
367
367
368 def _load_my_repos_data(self, watched=False):
368 def _load_my_repos_data(self, watched=False):
369
369
370 allowed_ids = [-1] + self._rhodecode_user.repo_acl_ids_from_stack(AuthUser.repo_read_perms)
370 allowed_ids = [-1] + self._rhodecode_user.repo_acl_ids_from_stack(AuthUser.repo_read_perms)
371
371
372 if watched:
372 if watched:
373 # repos user watch
373 # repos user watch
374 repo_list = Session().query(
374 repo_list = Session().query(
375 Repository
375 Repository
376 ) \
376 ) \
377 .join(
377 .join(
378 (UserFollowing, UserFollowing.follows_repo_id == Repository.repo_id)
378 (UserFollowing, UserFollowing.follows_repo_id == Repository.repo_id)
379 ) \
379 ) \
380 .filter(
380 .filter(
381 UserFollowing.user_id == self._rhodecode_user.user_id
381 UserFollowing.user_id == self._rhodecode_user.user_id
382 ) \
382 ) \
383 .filter(or_(
383 .filter(or_(
384 # generate multiple IN to fix limitation problems
384 # generate multiple IN to fix limitation problems
385 *in_filter_generator(Repository.repo_id, allowed_ids))
385 *in_filter_generator(Repository.repo_id, allowed_ids))
386 ) \
386 ) \
387 .order_by(Repository.repo_name) \
387 .order_by(Repository.repo_name) \
388 .all()
388 .all()
389
389
390 else:
390 else:
391 # repos user is owner of
391 # repos user is owner of
392 repo_list = Session().query(
392 repo_list = Session().query(
393 Repository
393 Repository
394 ) \
394 ) \
395 .filter(
395 .filter(
396 Repository.user_id == self._rhodecode_user.user_id
396 Repository.user_id == self._rhodecode_user.user_id
397 ) \
397 ) \
398 .filter(or_(
398 .filter(or_(
399 # generate multiple IN to fix limitation problems
399 # generate multiple IN to fix limitation problems
400 *in_filter_generator(Repository.repo_id, allowed_ids))
400 *in_filter_generator(Repository.repo_id, allowed_ids))
401 ) \
401 ) \
402 .order_by(Repository.repo_name) \
402 .order_by(Repository.repo_name) \
403 .all()
403 .all()
404
404
405 _render = self.request.get_partial_renderer(
405 _render = self.request.get_partial_renderer(
406 'rhodecode:templates/data_table/_dt_elements.mako')
406 'rhodecode:templates/data_table/_dt_elements.mako')
407
407
408 def repo_lnk(name, rtype, rstate, private, archived, fork_of):
408 def repo_lnk(name, rtype, rstate, private, archived, fork_of):
409 return _render('repo_name', name, rtype, rstate, private, archived, fork_of,
409 return _render('repo_name', name, rtype, rstate, private, archived, fork_of,
410 short_name=False, admin=False)
410 short_name=False, admin=False)
411
411
412 repos_data = []
412 repos_data = []
413 for repo in repo_list:
413 for repo in repo_list:
414 row = {
414 row = {
415 "name": repo_lnk(repo.repo_name, repo.repo_type, repo.repo_state,
415 "name": repo_lnk(repo.repo_name, repo.repo_type, repo.repo_state,
416 repo.private, repo.archived, repo.fork),
416 repo.private, repo.archived, repo.fork),
417 "name_raw": repo.repo_name.lower(),
417 "name_raw": repo.repo_name.lower(),
418 }
418 }
419
419
420 repos_data.append(row)
420 repos_data.append(row)
421
421
422 # json used to render the grid
422 # json used to render the grid
423 return json.dumps(repos_data)
423 return json.dumps(repos_data)
424
424
425 @LoginRequired()
425 @LoginRequired()
426 @NotAnonymous()
426 @NotAnonymous()
427 @view_config(
427 @view_config(
428 route_name='my_account_repos', request_method='GET',
428 route_name='my_account_repos', request_method='GET',
429 renderer='rhodecode:templates/admin/my_account/my_account.mako')
429 renderer='rhodecode:templates/admin/my_account/my_account.mako')
430 def my_account_repos(self):
430 def my_account_repos(self):
431 c = self.load_default_context()
431 c = self.load_default_context()
432 c.active = 'repos'
432 c.active = 'repos'
433
433
434 # json used to render the grid
434 # json used to render the grid
435 c.data = self._load_my_repos_data()
435 c.data = self._load_my_repos_data()
436 return self._get_template_context(c)
436 return self._get_template_context(c)
437
437
438 @LoginRequired()
438 @LoginRequired()
439 @NotAnonymous()
439 @NotAnonymous()
440 @view_config(
440 @view_config(
441 route_name='my_account_watched', request_method='GET',
441 route_name='my_account_watched', request_method='GET',
442 renderer='rhodecode:templates/admin/my_account/my_account.mako')
442 renderer='rhodecode:templates/admin/my_account/my_account.mako')
443 def my_account_watched(self):
443 def my_account_watched(self):
444 c = self.load_default_context()
444 c = self.load_default_context()
445 c.active = 'watched'
445 c.active = 'watched'
446
446
447 # json used to render the grid
447 # json used to render the grid
448 c.data = self._load_my_repos_data(watched=True)
448 c.data = self._load_my_repos_data(watched=True)
449 return self._get_template_context(c)
449 return self._get_template_context(c)
450
450
451 @LoginRequired()
451 @LoginRequired()
452 @NotAnonymous()
452 @NotAnonymous()
453 @view_config(
453 @view_config(
454 route_name='my_account_bookmarks', request_method='GET',
454 route_name='my_account_bookmarks', request_method='GET',
455 renderer='rhodecode:templates/admin/my_account/my_account.mako')
455 renderer='rhodecode:templates/admin/my_account/my_account.mako')
456 def my_account_bookmarks(self):
456 def my_account_bookmarks(self):
457 c = self.load_default_context()
457 c = self.load_default_context()
458 c.active = 'bookmarks'
458 c.active = 'bookmarks'
459 c.bookmark_items = UserBookmark.get_bookmarks_for_user(
459 c.bookmark_items = UserBookmark.get_bookmarks_for_user(
460 self._rhodecode_db_user.user_id, cache=False)
460 self._rhodecode_db_user.user_id, cache=False)
461 return self._get_template_context(c)
461 return self._get_template_context(c)
462
462
463 def _process_bookmark_entry(self, entry, user_id):
463 def _process_bookmark_entry(self, entry, user_id):
464 position = safe_int(entry.get('position'))
464 position = safe_int(entry.get('position'))
465 cur_position = safe_int(entry.get('cur_position'))
465 cur_position = safe_int(entry.get('cur_position'))
466 if position is None:
466 if position is None:
467 return
467 return
468
468
469 # check if this is an existing entry
469 # check if this is an existing entry
470 is_new = False
470 is_new = False
471 db_entry = UserBookmark().get_by_position_for_user(cur_position, user_id)
471 db_entry = UserBookmark().get_by_position_for_user(cur_position, user_id)
472
472
473 if db_entry and str2bool(entry.get('remove')):
473 if db_entry and str2bool(entry.get('remove')):
474 log.debug('Marked bookmark %s for deletion', db_entry)
474 log.debug('Marked bookmark %s for deletion', db_entry)
475 Session().delete(db_entry)
475 Session().delete(db_entry)
476 return
476 return
477
477
478 if not db_entry:
478 if not db_entry:
479 # new
479 # new
480 db_entry = UserBookmark()
480 db_entry = UserBookmark()
481 is_new = True
481 is_new = True
482
482
483 should_save = False
483 should_save = False
484 default_redirect_url = ''
484 default_redirect_url = ''
485
485
486 # save repo
486 # save repo
487 if entry.get('bookmark_repo') and safe_int(entry.get('bookmark_repo')):
487 if entry.get('bookmark_repo') and safe_int(entry.get('bookmark_repo')):
488 repo = Repository.get(entry['bookmark_repo'])
488 repo = Repository.get(entry['bookmark_repo'])
489 perm_check = HasRepoPermissionAny(
489 perm_check = HasRepoPermissionAny(
490 'repository.read', 'repository.write', 'repository.admin')
490 'repository.read', 'repository.write', 'repository.admin')
491 if repo and perm_check(repo_name=repo.repo_name):
491 if repo and perm_check(repo_name=repo.repo_name):
492 db_entry.repository = repo
492 db_entry.repository = repo
493 should_save = True
493 should_save = True
494 default_redirect_url = '${repo_url}'
494 default_redirect_url = '${repo_url}'
495 # save repo group
495 # save repo group
496 elif entry.get('bookmark_repo_group') and safe_int(entry.get('bookmark_repo_group')):
496 elif entry.get('bookmark_repo_group') and safe_int(entry.get('bookmark_repo_group')):
497 repo_group = RepoGroup.get(entry['bookmark_repo_group'])
497 repo_group = RepoGroup.get(entry['bookmark_repo_group'])
498 perm_check = HasRepoGroupPermissionAny(
498 perm_check = HasRepoGroupPermissionAny(
499 'group.read', 'group.write', 'group.admin')
499 'group.read', 'group.write', 'group.admin')
500
500
501 if repo_group and perm_check(group_name=repo_group.group_name):
501 if repo_group and perm_check(group_name=repo_group.group_name):
502 db_entry.repository_group = repo_group
502 db_entry.repository_group = repo_group
503 should_save = True
503 should_save = True
504 default_redirect_url = '${repo_group_url}'
504 default_redirect_url = '${repo_group_url}'
505 # save generic info
505 # save generic info
506 elif entry.get('title') and entry.get('redirect_url'):
506 elif entry.get('title') and entry.get('redirect_url'):
507 should_save = True
507 should_save = True
508
508
509 if should_save:
509 if should_save:
510 # mark user and position
510 # mark user and position
511 db_entry.user_id = user_id
511 db_entry.user_id = user_id
512 db_entry.position = position
512 db_entry.position = position
513 db_entry.title = entry.get('title')
513 db_entry.title = entry.get('title')
514 db_entry.redirect_url = entry.get('redirect_url') or default_redirect_url
514 db_entry.redirect_url = entry.get('redirect_url') or default_redirect_url
515 log.debug('Saving bookmark %s, new:%s', db_entry, is_new)
515 log.debug('Saving bookmark %s, new:%s', db_entry, is_new)
516
516
517 Session().add(db_entry)
517 Session().add(db_entry)
518
518
519 @LoginRequired()
519 @LoginRequired()
520 @NotAnonymous()
520 @NotAnonymous()
521 @CSRFRequired()
521 @CSRFRequired()
522 @view_config(
522 @view_config(
523 route_name='my_account_bookmarks_update', request_method='POST')
523 route_name='my_account_bookmarks_update', request_method='POST')
524 def my_account_bookmarks_update(self):
524 def my_account_bookmarks_update(self):
525 _ = self.request.translate
525 _ = self.request.translate
526 c = self.load_default_context()
526 c = self.load_default_context()
527 c.active = 'bookmarks'
527 c.active = 'bookmarks'
528
528
529 controls = peppercorn.parse(self.request.POST.items())
529 controls = peppercorn.parse(self.request.POST.items())
530 user_id = c.user.user_id
530 user_id = c.user.user_id
531
531
532 # validate positions
532 # validate positions
533 positions = {}
533 positions = {}
534 for entry in controls.get('bookmarks', []):
534 for entry in controls.get('bookmarks', []):
535 position = safe_int(entry['position'])
535 position = safe_int(entry['position'])
536 if position is None:
536 if position is None:
537 continue
537 continue
538
538
539 if position in positions:
539 if position in positions:
540 h.flash(_("Position {} is defined twice. "
540 h.flash(_("Position {} is defined twice. "
541 "Please correct this error.").format(position), category='error')
541 "Please correct this error.").format(position), category='error')
542 return HTTPFound(h.route_path('my_account_bookmarks'))
542 return HTTPFound(h.route_path('my_account_bookmarks'))
543
543
544 entry['position'] = position
544 entry['position'] = position
545 entry['cur_position'] = safe_int(entry.get('cur_position'))
545 entry['cur_position'] = safe_int(entry.get('cur_position'))
546 positions[position] = entry
546 positions[position] = entry
547
547
548 try:
548 try:
549 for entry in positions.values():
549 for entry in positions.values():
550 self._process_bookmark_entry(entry, user_id)
550 self._process_bookmark_entry(entry, user_id)
551
551
552 Session().commit()
552 Session().commit()
553 h.flash(_("Update Bookmarks"), category='success')
553 h.flash(_("Update Bookmarks"), category='success')
554 except IntegrityError:
554 except IntegrityError:
555 h.flash(_("Failed to update bookmarks. "
555 h.flash(_("Failed to update bookmarks. "
556 "Make sure an unique position is used."), category='error')
556 "Make sure an unique position is used."), category='error')
557
557
558 return HTTPFound(h.route_path('my_account_bookmarks'))
558 return HTTPFound(h.route_path('my_account_bookmarks'))
559
559
560 @LoginRequired()
560 @LoginRequired()
561 @NotAnonymous()
561 @NotAnonymous()
562 @view_config(
562 @view_config(
563 route_name='my_account_goto_bookmark', request_method='GET',
563 route_name='my_account_goto_bookmark', request_method='GET',
564 renderer='rhodecode:templates/admin/my_account/my_account.mako')
564 renderer='rhodecode:templates/admin/my_account/my_account.mako')
565 def my_account_goto_bookmark(self):
565 def my_account_goto_bookmark(self):
566
566
567 bookmark_id = self.request.matchdict['bookmark_id']
567 bookmark_id = self.request.matchdict['bookmark_id']
568 user_bookmark = UserBookmark().query()\
568 user_bookmark = UserBookmark().query()\
569 .filter(UserBookmark.user_id == self.request.user.user_id) \
569 .filter(UserBookmark.user_id == self.request.user.user_id) \
570 .filter(UserBookmark.position == bookmark_id).scalar()
570 .filter(UserBookmark.position == bookmark_id).scalar()
571
571
572 redirect_url = h.route_path('my_account_bookmarks')
572 redirect_url = h.route_path('my_account_bookmarks')
573 if not user_bookmark:
573 if not user_bookmark:
574 raise HTTPFound(redirect_url)
574 raise HTTPFound(redirect_url)
575
575
576 # repository set
576 # repository set
577 if user_bookmark.repository:
577 if user_bookmark.repository:
578 repo_name = user_bookmark.repository.repo_name
578 repo_name = user_bookmark.repository.repo_name
579 base_redirect_url = h.route_path(
579 base_redirect_url = h.route_path(
580 'repo_summary', repo_name=repo_name)
580 'repo_summary', repo_name=repo_name)
581 if user_bookmark.redirect_url and \
581 if user_bookmark.redirect_url and \
582 '${repo_url}' in user_bookmark.redirect_url:
582 '${repo_url}' in user_bookmark.redirect_url:
583 redirect_url = string.Template(user_bookmark.redirect_url)\
583 redirect_url = string.Template(user_bookmark.redirect_url)\
584 .safe_substitute({'repo_url': base_redirect_url})
584 .safe_substitute({'repo_url': base_redirect_url})
585 else:
585 else:
586 redirect_url = base_redirect_url
586 redirect_url = base_redirect_url
587 # repository group set
587 # repository group set
588 elif user_bookmark.repository_group:
588 elif user_bookmark.repository_group:
589 repo_group_name = user_bookmark.repository_group.group_name
589 repo_group_name = user_bookmark.repository_group.group_name
590 base_redirect_url = h.route_path(
590 base_redirect_url = h.route_path(
591 'repo_group_home', repo_group_name=repo_group_name)
591 'repo_group_home', repo_group_name=repo_group_name)
592 if user_bookmark.redirect_url and \
592 if user_bookmark.redirect_url and \
593 '${repo_group_url}' in user_bookmark.redirect_url:
593 '${repo_group_url}' in user_bookmark.redirect_url:
594 redirect_url = string.Template(user_bookmark.redirect_url)\
594 redirect_url = string.Template(user_bookmark.redirect_url)\
595 .safe_substitute({'repo_group_url': base_redirect_url})
595 .safe_substitute({'repo_group_url': base_redirect_url})
596 else:
596 else:
597 redirect_url = base_redirect_url
597 redirect_url = base_redirect_url
598 # custom URL set
598 # custom URL set
599 elif user_bookmark.redirect_url:
599 elif user_bookmark.redirect_url:
600 server_url = h.route_url('home').rstrip('/')
600 server_url = h.route_url('home').rstrip('/')
601 redirect_url = string.Template(user_bookmark.redirect_url) \
601 redirect_url = string.Template(user_bookmark.redirect_url) \
602 .safe_substitute({'server_url': server_url})
602 .safe_substitute({'server_url': server_url})
603
603
604 log.debug('Redirecting bookmark %s to %s', user_bookmark, redirect_url)
604 log.debug('Redirecting bookmark %s to %s', user_bookmark, redirect_url)
605 raise HTTPFound(redirect_url)
605 raise HTTPFound(redirect_url)
606
606
607 @LoginRequired()
607 @LoginRequired()
608 @NotAnonymous()
608 @NotAnonymous()
609 @view_config(
609 @view_config(
610 route_name='my_account_perms', request_method='GET',
610 route_name='my_account_perms', request_method='GET',
611 renderer='rhodecode:templates/admin/my_account/my_account.mako')
611 renderer='rhodecode:templates/admin/my_account/my_account.mako')
612 def my_account_perms(self):
612 def my_account_perms(self):
613 c = self.load_default_context()
613 c = self.load_default_context()
614 c.active = 'perms'
614 c.active = 'perms'
615
615
616 c.perm_user = c.auth_user
616 c.perm_user = c.auth_user
617 return self._get_template_context(c)
617 return self._get_template_context(c)
618
618
619 @LoginRequired()
619 @LoginRequired()
620 @NotAnonymous()
620 @NotAnonymous()
621 @view_config(
621 @view_config(
622 route_name='my_account_notifications', request_method='GET',
622 route_name='my_account_notifications', request_method='GET',
623 renderer='rhodecode:templates/admin/my_account/my_account.mako')
623 renderer='rhodecode:templates/admin/my_account/my_account.mako')
624 def my_notifications(self):
624 def my_notifications(self):
625 c = self.load_default_context()
625 c = self.load_default_context()
626 c.active = 'notifications'
626 c.active = 'notifications'
627
627
628 return self._get_template_context(c)
628 return self._get_template_context(c)
629
629
630 @LoginRequired()
630 @LoginRequired()
631 @NotAnonymous()
631 @NotAnonymous()
632 @CSRFRequired()
632 @CSRFRequired()
633 @view_config(
633 @view_config(
634 route_name='my_account_notifications_toggle_visibility',
634 route_name='my_account_notifications_toggle_visibility',
635 request_method='POST', renderer='json_ext')
635 request_method='POST', renderer='json_ext')
636 def my_notifications_toggle_visibility(self):
636 def my_notifications_toggle_visibility(self):
637 user = self._rhodecode_db_user
637 user = self._rhodecode_db_user
638 new_status = not user.user_data.get('notification_status', True)
638 new_status = not user.user_data.get('notification_status', True)
639 user.update_userdata(notification_status=new_status)
639 user.update_userdata(notification_status=new_status)
640 Session().commit()
640 Session().commit()
641 return user.user_data['notification_status']
641 return user.user_data['notification_status']
642
642
643 @LoginRequired()
643 @LoginRequired()
644 @NotAnonymous()
644 @NotAnonymous()
645 @view_config(
645 @view_config(
646 route_name='my_account_edit',
646 route_name='my_account_edit',
647 request_method='GET',
647 request_method='GET',
648 renderer='rhodecode:templates/admin/my_account/my_account.mako')
648 renderer='rhodecode:templates/admin/my_account/my_account.mako')
649 def my_account_edit(self):
649 def my_account_edit(self):
650 c = self.load_default_context()
650 c = self.load_default_context()
651 c.active = 'profile_edit'
651 c.active = 'profile_edit'
652 c.extern_type = c.user.extern_type
652 c.extern_type = c.user.extern_type
653 c.extern_name = c.user.extern_name
653 c.extern_name = c.user.extern_name
654
654
655 schema = user_schema.UserProfileSchema().bind(
655 schema = user_schema.UserProfileSchema().bind(
656 username=c.user.username, user_emails=c.user.emails)
656 username=c.user.username, user_emails=c.user.emails)
657 appstruct = {
657 appstruct = {
658 'username': c.user.username,
658 'username': c.user.username,
659 'email': c.user.email,
659 'email': c.user.email,
660 'firstname': c.user.firstname,
660 'firstname': c.user.firstname,
661 'lastname': c.user.lastname,
661 'lastname': c.user.lastname,
662 'description': c.user.description,
662 'description': c.user.description,
663 }
663 }
664 c.form = forms.RcForm(
664 c.form = forms.RcForm(
665 schema, appstruct=appstruct,
665 schema, appstruct=appstruct,
666 action=h.route_path('my_account_update'),
666 action=h.route_path('my_account_update'),
667 buttons=(forms.buttons.save, forms.buttons.reset))
667 buttons=(forms.buttons.save, forms.buttons.reset))
668
668
669 return self._get_template_context(c)
669 return self._get_template_context(c)
670
670
671 @LoginRequired()
671 @LoginRequired()
672 @NotAnonymous()
672 @NotAnonymous()
673 @CSRFRequired()
673 @CSRFRequired()
674 @view_config(
674 @view_config(
675 route_name='my_account_update',
675 route_name='my_account_update',
676 request_method='POST',
676 request_method='POST',
677 renderer='rhodecode:templates/admin/my_account/my_account.mako')
677 renderer='rhodecode:templates/admin/my_account/my_account.mako')
678 def my_account_update(self):
678 def my_account_update(self):
679 _ = self.request.translate
679 _ = self.request.translate
680 c = self.load_default_context()
680 c = self.load_default_context()
681 c.active = 'profile_edit'
681 c.active = 'profile_edit'
682 c.perm_user = c.auth_user
682 c.perm_user = c.auth_user
683 c.extern_type = c.user.extern_type
683 c.extern_type = c.user.extern_type
684 c.extern_name = c.user.extern_name
684 c.extern_name = c.user.extern_name
685
685
686 schema = user_schema.UserProfileSchema().bind(
686 schema = user_schema.UserProfileSchema().bind(
687 username=c.user.username, user_emails=c.user.emails)
687 username=c.user.username, user_emails=c.user.emails)
688 form = forms.RcForm(
688 form = forms.RcForm(
689 schema, buttons=(forms.buttons.save, forms.buttons.reset))
689 schema, buttons=(forms.buttons.save, forms.buttons.reset))
690
690
691 controls = self.request.POST.items()
691 controls = self.request.POST.items()
692 try:
692 try:
693 valid_data = form.validate(controls)
693 valid_data = form.validate(controls)
694 skip_attrs = ['admin', 'active', 'extern_type', 'extern_name',
694 skip_attrs = ['admin', 'active', 'extern_type', 'extern_name',
695 'new_password', 'password_confirmation']
695 'new_password', 'password_confirmation']
696 if c.extern_type != "rhodecode":
696 if c.extern_type != "rhodecode":
697 # forbid updating username for external accounts
697 # forbid updating username for external accounts
698 skip_attrs.append('username')
698 skip_attrs.append('username')
699 old_email = c.user.email
699 old_email = c.user.email
700 UserModel().update_user(
700 UserModel().update_user(
701 self._rhodecode_user.user_id, skip_attrs=skip_attrs,
701 self._rhodecode_user.user_id, skip_attrs=skip_attrs,
702 **valid_data)
702 **valid_data)
703 if old_email != valid_data['email']:
703 if old_email != valid_data['email']:
704 old = UserEmailMap.query() \
704 old = UserEmailMap.query() \
705 .filter(UserEmailMap.user == c.user).filter(UserEmailMap.email == valid_data['email']).first()
705 .filter(UserEmailMap.user == c.user).filter(UserEmailMap.email == valid_data['email']).first()
706 old.email = old_email
706 old.email = old_email
707 h.flash(_('Your account was updated successfully'), category='success')
707 h.flash(_('Your account was updated successfully'), category='success')
708 Session().commit()
708 Session().commit()
709 except forms.ValidationFailure as e:
709 except forms.ValidationFailure as e:
710 c.form = e
710 c.form = e
711 return self._get_template_context(c)
711 return self._get_template_context(c)
712 except Exception:
712 except Exception:
713 log.exception("Exception updating user")
713 log.exception("Exception updating user")
714 h.flash(_('Error occurred during update of user'),
714 h.flash(_('Error occurred during update of user'),
715 category='error')
715 category='error')
716 raise HTTPFound(h.route_path('my_account_profile'))
716 raise HTTPFound(h.route_path('my_account_profile'))
717
717
718 def _get_pull_requests_list(self, statuses):
718 def _get_pull_requests_list(self, statuses):
719 draw, start, limit = self._extract_chunk(self.request)
719 draw, start, limit = self._extract_chunk(self.request)
720 search_q, order_by, order_dir = self._extract_ordering(self.request)
720 search_q, order_by, order_dir = self._extract_ordering(self.request)
721 _render = self.request.get_partial_renderer(
721 _render = self.request.get_partial_renderer(
722 'rhodecode:templates/data_table/_dt_elements.mako')
722 'rhodecode:templates/data_table/_dt_elements.mako')
723
723
724 pull_requests = PullRequestModel().get_im_participating_in(
724 pull_requests = PullRequestModel().get_im_participating_in(
725 user_id=self._rhodecode_user.user_id,
725 user_id=self._rhodecode_user.user_id,
726 statuses=statuses, query=search_q,
726 statuses=statuses, query=search_q,
727 offset=start, length=limit, order_by=order_by,
727 offset=start, length=limit, order_by=order_by,
728 order_dir=order_dir)
728 order_dir=order_dir)
729
729
730 pull_requests_total_count = PullRequestModel().count_im_participating_in(
730 pull_requests_total_count = PullRequestModel().count_im_participating_in(
731 user_id=self._rhodecode_user.user_id, statuses=statuses, query=search_q)
731 user_id=self._rhodecode_user.user_id, statuses=statuses, query=search_q)
732
732
733 data = []
733 data = []
734 comments_model = CommentsModel()
734 comments_model = CommentsModel()
735 for pr in pull_requests:
735 for pr in pull_requests:
736 repo_id = pr.target_repo_id
736 repo_id = pr.target_repo_id
737 comments_count = comments_model.get_all_comments(
737 comments_count = comments_model.get_all_comments(
738 repo_id, pull_request=pr, include_drafts=False, count_only=True)
738 repo_id, pull_request=pr, include_drafts=False, count_only=True)
739 owned = pr.user_id == self._rhodecode_user.user_id
739 owned = pr.user_id == self._rhodecode_user.user_id
740
740
741 data.append({
741 data.append({
742 'target_repo': _render('pullrequest_target_repo',
742 'target_repo': _render('pullrequest_target_repo',
743 pr.target_repo.repo_name),
743 pr.target_repo.repo_name),
744 'name': _render('pullrequest_name',
744 'name': _render('pullrequest_name',
745 pr.pull_request_id, pr.pull_request_state,
745 pr.pull_request_id, pr.pull_request_state,
746 pr.work_in_progress, pr.target_repo.repo_name,
746 pr.work_in_progress, pr.target_repo.repo_name,
747 short=True),
747 short=True),
748 'name_raw': pr.pull_request_id,
748 'name_raw': pr.pull_request_id,
749 'status': _render('pullrequest_status',
749 'status': _render('pullrequest_status',
750 pr.calculated_review_status()),
750 pr.calculated_review_status()),
751 'title': _render('pullrequest_title', pr.title, pr.description),
751 'title': _render('pullrequest_title', pr.title, pr.description),
752 'description': h.escape(pr.description),
752 'description': h.escape(pr.description),
753 'updated_on': _render('pullrequest_updated_on',
753 'updated_on': _render('pullrequest_updated_on',
754 h.datetime_to_time(pr.updated_on)),
754 h.datetime_to_time(pr.updated_on),
755 pr.versions_count),
755 'updated_on_raw': h.datetime_to_time(pr.updated_on),
756 'updated_on_raw': h.datetime_to_time(pr.updated_on),
756 'created_on': _render('pullrequest_updated_on',
757 'created_on': _render('pullrequest_updated_on',
757 h.datetime_to_time(pr.created_on)),
758 h.datetime_to_time(pr.created_on)),
758 'created_on_raw': h.datetime_to_time(pr.created_on),
759 'created_on_raw': h.datetime_to_time(pr.created_on),
759 'state': pr.pull_request_state,
760 'state': pr.pull_request_state,
760 'author': _render('pullrequest_author',
761 'author': _render('pullrequest_author',
761 pr.author.full_contact, ),
762 pr.author.full_contact, ),
762 'author_raw': pr.author.full_name,
763 'author_raw': pr.author.full_name,
763 'comments': _render('pullrequest_comments', comments_count),
764 'comments': _render('pullrequest_comments', comments_count),
764 'comments_raw': comments_count,
765 'comments_raw': comments_count,
765 'closed': pr.is_closed(),
766 'closed': pr.is_closed(),
766 'owned': owned
767 'owned': owned
767 })
768 })
768
769
769 # json used to render the grid
770 # json used to render the grid
770 data = ({
771 data = ({
771 'draw': draw,
772 'draw': draw,
772 'data': data,
773 'data': data,
773 'recordsTotal': pull_requests_total_count,
774 'recordsTotal': pull_requests_total_count,
774 'recordsFiltered': pull_requests_total_count,
775 'recordsFiltered': pull_requests_total_count,
775 })
776 })
776 return data
777 return data
777
778
778 @LoginRequired()
779 @LoginRequired()
779 @NotAnonymous()
780 @NotAnonymous()
780 @view_config(
781 @view_config(
781 route_name='my_account_pullrequests',
782 route_name='my_account_pullrequests',
782 request_method='GET',
783 request_method='GET',
783 renderer='rhodecode:templates/admin/my_account/my_account.mako')
784 renderer='rhodecode:templates/admin/my_account/my_account.mako')
784 def my_account_pullrequests(self):
785 def my_account_pullrequests(self):
785 c = self.load_default_context()
786 c = self.load_default_context()
786 c.active = 'pullrequests'
787 c.active = 'pullrequests'
787 req_get = self.request.GET
788 req_get = self.request.GET
788
789
789 c.closed = str2bool(req_get.get('pr_show_closed'))
790 c.closed = str2bool(req_get.get('pr_show_closed'))
790
791
791 return self._get_template_context(c)
792 return self._get_template_context(c)
792
793
793 @LoginRequired()
794 @LoginRequired()
794 @NotAnonymous()
795 @NotAnonymous()
795 @view_config(
796 @view_config(
796 route_name='my_account_pullrequests_data',
797 route_name='my_account_pullrequests_data',
797 request_method='GET', renderer='json_ext')
798 request_method='GET', renderer='json_ext')
798 def my_account_pullrequests_data(self):
799 def my_account_pullrequests_data(self):
799 self.load_default_context()
800 self.load_default_context()
800 req_get = self.request.GET
801 req_get = self.request.GET
801 closed = str2bool(req_get.get('closed'))
802 closed = str2bool(req_get.get('closed'))
802
803
803 statuses = [PullRequest.STATUS_NEW, PullRequest.STATUS_OPEN]
804 statuses = [PullRequest.STATUS_NEW, PullRequest.STATUS_OPEN]
804 if closed:
805 if closed:
805 statuses += [PullRequest.STATUS_CLOSED]
806 statuses += [PullRequest.STATUS_CLOSED]
806
807
807 data = self._get_pull_requests_list(statuses=statuses)
808 data = self._get_pull_requests_list(statuses=statuses)
808 return data
809 return data
809
810
810 @LoginRequired()
811 @LoginRequired()
811 @NotAnonymous()
812 @NotAnonymous()
812 @view_config(
813 @view_config(
813 route_name='my_account_user_group_membership',
814 route_name='my_account_user_group_membership',
814 request_method='GET',
815 request_method='GET',
815 renderer='rhodecode:templates/admin/my_account/my_account.mako')
816 renderer='rhodecode:templates/admin/my_account/my_account.mako')
816 def my_account_user_group_membership(self):
817 def my_account_user_group_membership(self):
817 c = self.load_default_context()
818 c = self.load_default_context()
818 c.active = 'user_group_membership'
819 c.active = 'user_group_membership'
819 groups = [UserGroupModel.get_user_groups_as_dict(group.users_group)
820 groups = [UserGroupModel.get_user_groups_as_dict(group.users_group)
820 for group in self._rhodecode_db_user.group_member]
821 for group in self._rhodecode_db_user.group_member]
821 c.user_groups = json.dumps(groups)
822 c.user_groups = json.dumps(groups)
822 return self._get_template_context(c)
823 return self._get_template_context(c)
@@ -1,1854 +1,1855 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2020 RhodeCode GmbH
3 # Copyright (C) 2011-2020 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import logging
21 import logging
22 import collections
22 import collections
23
23
24 import formencode
24 import formencode
25 import formencode.htmlfill
25 import formencode.htmlfill
26 import peppercorn
26 import peppercorn
27 from pyramid.httpexceptions import (
27 from pyramid.httpexceptions import (
28 HTTPFound, HTTPNotFound, HTTPForbidden, HTTPBadRequest, HTTPConflict)
28 HTTPFound, HTTPNotFound, HTTPForbidden, HTTPBadRequest, HTTPConflict)
29 from pyramid.view import view_config
29 from pyramid.view import view_config
30 from pyramid.renderers import render
30 from pyramid.renderers import render
31
31
32 from rhodecode.apps._base import RepoAppView, DataGridAppView
32 from rhodecode.apps._base import RepoAppView, DataGridAppView
33
33
34 from rhodecode.lib import helpers as h, diffs, codeblocks, channelstream
34 from rhodecode.lib import helpers as h, diffs, codeblocks, channelstream
35 from rhodecode.lib.base import vcs_operation_context
35 from rhodecode.lib.base import vcs_operation_context
36 from rhodecode.lib.diffs import load_cached_diff, cache_diff, diff_cache_exist
36 from rhodecode.lib.diffs import load_cached_diff, cache_diff, diff_cache_exist
37 from rhodecode.lib.exceptions import CommentVersionMismatch
37 from rhodecode.lib.exceptions import CommentVersionMismatch
38 from rhodecode.lib.ext_json import json
38 from rhodecode.lib.ext_json import json
39 from rhodecode.lib.auth import (
39 from rhodecode.lib.auth import (
40 LoginRequired, HasRepoPermissionAny, HasRepoPermissionAnyDecorator,
40 LoginRequired, HasRepoPermissionAny, HasRepoPermissionAnyDecorator,
41 NotAnonymous, CSRFRequired)
41 NotAnonymous, CSRFRequired)
42 from rhodecode.lib.utils2 import str2bool, safe_str, safe_unicode, safe_int, aslist
42 from rhodecode.lib.utils2 import str2bool, safe_str, safe_unicode, safe_int, aslist
43 from rhodecode.lib.vcs.backends.base import (
43 from rhodecode.lib.vcs.backends.base import (
44 EmptyCommit, UpdateFailureReason, unicode_to_reference)
44 EmptyCommit, UpdateFailureReason, unicode_to_reference)
45 from rhodecode.lib.vcs.exceptions import (
45 from rhodecode.lib.vcs.exceptions import (
46 CommitDoesNotExistError, RepositoryRequirementError, EmptyRepositoryError)
46 CommitDoesNotExistError, RepositoryRequirementError, EmptyRepositoryError)
47 from rhodecode.model.changeset_status import ChangesetStatusModel
47 from rhodecode.model.changeset_status import ChangesetStatusModel
48 from rhodecode.model.comment import CommentsModel
48 from rhodecode.model.comment import CommentsModel
49 from rhodecode.model.db import (
49 from rhodecode.model.db import (
50 func, false, or_, PullRequest, ChangesetComment, ChangesetStatus, Repository,
50 func, false, or_, PullRequest, ChangesetComment, ChangesetStatus, Repository,
51 PullRequestReviewers)
51 PullRequestReviewers)
52 from rhodecode.model.forms import PullRequestForm
52 from rhodecode.model.forms import PullRequestForm
53 from rhodecode.model.meta import Session
53 from rhodecode.model.meta import Session
54 from rhodecode.model.pull_request import PullRequestModel, MergeCheck
54 from rhodecode.model.pull_request import PullRequestModel, MergeCheck
55 from rhodecode.model.scm import ScmModel
55 from rhodecode.model.scm import ScmModel
56
56
57 log = logging.getLogger(__name__)
57 log = logging.getLogger(__name__)
58
58
59
59
60 class RepoPullRequestsView(RepoAppView, DataGridAppView):
60 class RepoPullRequestsView(RepoAppView, DataGridAppView):
61
61
62 def load_default_context(self):
62 def load_default_context(self):
63 c = self._get_local_tmpl_context(include_app_defaults=True)
63 c = self._get_local_tmpl_context(include_app_defaults=True)
64 c.REVIEW_STATUS_APPROVED = ChangesetStatus.STATUS_APPROVED
64 c.REVIEW_STATUS_APPROVED = ChangesetStatus.STATUS_APPROVED
65 c.REVIEW_STATUS_REJECTED = ChangesetStatus.STATUS_REJECTED
65 c.REVIEW_STATUS_REJECTED = ChangesetStatus.STATUS_REJECTED
66 # backward compat., we use for OLD PRs a plain renderer
66 # backward compat., we use for OLD PRs a plain renderer
67 c.renderer = 'plain'
67 c.renderer = 'plain'
68 return c
68 return c
69
69
70 def _get_pull_requests_list(
70 def _get_pull_requests_list(
71 self, repo_name, source, filter_type, opened_by, statuses):
71 self, repo_name, source, filter_type, opened_by, statuses):
72
72
73 draw, start, limit = self._extract_chunk(self.request)
73 draw, start, limit = self._extract_chunk(self.request)
74 search_q, order_by, order_dir = self._extract_ordering(self.request)
74 search_q, order_by, order_dir = self._extract_ordering(self.request)
75 _render = self.request.get_partial_renderer(
75 _render = self.request.get_partial_renderer(
76 'rhodecode:templates/data_table/_dt_elements.mako')
76 'rhodecode:templates/data_table/_dt_elements.mako')
77
77
78 # pagination
78 # pagination
79
79
80 if filter_type == 'awaiting_review':
80 if filter_type == 'awaiting_review':
81 pull_requests = PullRequestModel().get_awaiting_review(
81 pull_requests = PullRequestModel().get_awaiting_review(
82 repo_name, search_q=search_q, source=source, opened_by=opened_by,
82 repo_name, search_q=search_q, source=source, opened_by=opened_by,
83 statuses=statuses, offset=start, length=limit,
83 statuses=statuses, offset=start, length=limit,
84 order_by=order_by, order_dir=order_dir)
84 order_by=order_by, order_dir=order_dir)
85 pull_requests_total_count = PullRequestModel().count_awaiting_review(
85 pull_requests_total_count = PullRequestModel().count_awaiting_review(
86 repo_name, search_q=search_q, source=source, statuses=statuses,
86 repo_name, search_q=search_q, source=source, statuses=statuses,
87 opened_by=opened_by)
87 opened_by=opened_by)
88 elif filter_type == 'awaiting_my_review':
88 elif filter_type == 'awaiting_my_review':
89 pull_requests = PullRequestModel().get_awaiting_my_review(
89 pull_requests = PullRequestModel().get_awaiting_my_review(
90 repo_name, search_q=search_q, source=source, opened_by=opened_by,
90 repo_name, search_q=search_q, source=source, opened_by=opened_by,
91 user_id=self._rhodecode_user.user_id, statuses=statuses,
91 user_id=self._rhodecode_user.user_id, statuses=statuses,
92 offset=start, length=limit, order_by=order_by,
92 offset=start, length=limit, order_by=order_by,
93 order_dir=order_dir)
93 order_dir=order_dir)
94 pull_requests_total_count = PullRequestModel().count_awaiting_my_review(
94 pull_requests_total_count = PullRequestModel().count_awaiting_my_review(
95 repo_name, search_q=search_q, source=source, user_id=self._rhodecode_user.user_id,
95 repo_name, search_q=search_q, source=source, user_id=self._rhodecode_user.user_id,
96 statuses=statuses, opened_by=opened_by)
96 statuses=statuses, opened_by=opened_by)
97 else:
97 else:
98 pull_requests = PullRequestModel().get_all(
98 pull_requests = PullRequestModel().get_all(
99 repo_name, search_q=search_q, source=source, opened_by=opened_by,
99 repo_name, search_q=search_q, source=source, opened_by=opened_by,
100 statuses=statuses, offset=start, length=limit,
100 statuses=statuses, offset=start, length=limit,
101 order_by=order_by, order_dir=order_dir)
101 order_by=order_by, order_dir=order_dir)
102 pull_requests_total_count = PullRequestModel().count_all(
102 pull_requests_total_count = PullRequestModel().count_all(
103 repo_name, search_q=search_q, source=source, statuses=statuses,
103 repo_name, search_q=search_q, source=source, statuses=statuses,
104 opened_by=opened_by)
104 opened_by=opened_by)
105
105
106 data = []
106 data = []
107 comments_model = CommentsModel()
107 comments_model = CommentsModel()
108 for pr in pull_requests:
108 for pr in pull_requests:
109 comments_count = comments_model.get_all_comments(
109 comments_count = comments_model.get_all_comments(
110 self.db_repo.repo_id, pull_request=pr,
110 self.db_repo.repo_id, pull_request=pr,
111 include_drafts=False, count_only=True)
111 include_drafts=False, count_only=True)
112
112
113 data.append({
113 data.append({
114 'name': _render('pullrequest_name',
114 'name': _render('pullrequest_name',
115 pr.pull_request_id, pr.pull_request_state,
115 pr.pull_request_id, pr.pull_request_state,
116 pr.work_in_progress, pr.target_repo.repo_name,
116 pr.work_in_progress, pr.target_repo.repo_name,
117 short=True),
117 short=True),
118 'name_raw': pr.pull_request_id,
118 'name_raw': pr.pull_request_id,
119 'status': _render('pullrequest_status',
119 'status': _render('pullrequest_status',
120 pr.calculated_review_status()),
120 pr.calculated_review_status()),
121 'title': _render('pullrequest_title', pr.title, pr.description),
121 'title': _render('pullrequest_title', pr.title, pr.description),
122 'description': h.escape(pr.description),
122 'description': h.escape(pr.description),
123 'updated_on': _render('pullrequest_updated_on',
123 'updated_on': _render('pullrequest_updated_on',
124 h.datetime_to_time(pr.updated_on)),
124 h.datetime_to_time(pr.updated_on),
125 pr.versions_count),
125 'updated_on_raw': h.datetime_to_time(pr.updated_on),
126 'updated_on_raw': h.datetime_to_time(pr.updated_on),
126 'created_on': _render('pullrequest_updated_on',
127 'created_on': _render('pullrequest_updated_on',
127 h.datetime_to_time(pr.created_on)),
128 h.datetime_to_time(pr.created_on)),
128 'created_on_raw': h.datetime_to_time(pr.created_on),
129 'created_on_raw': h.datetime_to_time(pr.created_on),
129 'state': pr.pull_request_state,
130 'state': pr.pull_request_state,
130 'author': _render('pullrequest_author',
131 'author': _render('pullrequest_author',
131 pr.author.full_contact, ),
132 pr.author.full_contact, ),
132 'author_raw': pr.author.full_name,
133 'author_raw': pr.author.full_name,
133 'comments': _render('pullrequest_comments', comments_count),
134 'comments': _render('pullrequest_comments', comments_count),
134 'comments_raw': comments_count,
135 'comments_raw': comments_count,
135 'closed': pr.is_closed(),
136 'closed': pr.is_closed(),
136 })
137 })
137
138
138 data = ({
139 data = ({
139 'draw': draw,
140 'draw': draw,
140 'data': data,
141 'data': data,
141 'recordsTotal': pull_requests_total_count,
142 'recordsTotal': pull_requests_total_count,
142 'recordsFiltered': pull_requests_total_count,
143 'recordsFiltered': pull_requests_total_count,
143 })
144 })
144 return data
145 return data
145
146
146 @LoginRequired()
147 @LoginRequired()
147 @HasRepoPermissionAnyDecorator(
148 @HasRepoPermissionAnyDecorator(
148 'repository.read', 'repository.write', 'repository.admin')
149 'repository.read', 'repository.write', 'repository.admin')
149 @view_config(
150 @view_config(
150 route_name='pullrequest_show_all', request_method='GET',
151 route_name='pullrequest_show_all', request_method='GET',
151 renderer='rhodecode:templates/pullrequests/pullrequests.mako')
152 renderer='rhodecode:templates/pullrequests/pullrequests.mako')
152 def pull_request_list(self):
153 def pull_request_list(self):
153 c = self.load_default_context()
154 c = self.load_default_context()
154
155
155 req_get = self.request.GET
156 req_get = self.request.GET
156 c.source = str2bool(req_get.get('source'))
157 c.source = str2bool(req_get.get('source'))
157 c.closed = str2bool(req_get.get('closed'))
158 c.closed = str2bool(req_get.get('closed'))
158 c.my = str2bool(req_get.get('my'))
159 c.my = str2bool(req_get.get('my'))
159 c.awaiting_review = str2bool(req_get.get('awaiting_review'))
160 c.awaiting_review = str2bool(req_get.get('awaiting_review'))
160 c.awaiting_my_review = str2bool(req_get.get('awaiting_my_review'))
161 c.awaiting_my_review = str2bool(req_get.get('awaiting_my_review'))
161
162
162 c.active = 'open'
163 c.active = 'open'
163 if c.my:
164 if c.my:
164 c.active = 'my'
165 c.active = 'my'
165 if c.closed:
166 if c.closed:
166 c.active = 'closed'
167 c.active = 'closed'
167 if c.awaiting_review and not c.source:
168 if c.awaiting_review and not c.source:
168 c.active = 'awaiting'
169 c.active = 'awaiting'
169 if c.source and not c.awaiting_review:
170 if c.source and not c.awaiting_review:
170 c.active = 'source'
171 c.active = 'source'
171 if c.awaiting_my_review:
172 if c.awaiting_my_review:
172 c.active = 'awaiting_my'
173 c.active = 'awaiting_my'
173
174
174 return self._get_template_context(c)
175 return self._get_template_context(c)
175
176
176 @LoginRequired()
177 @LoginRequired()
177 @HasRepoPermissionAnyDecorator(
178 @HasRepoPermissionAnyDecorator(
178 'repository.read', 'repository.write', 'repository.admin')
179 'repository.read', 'repository.write', 'repository.admin')
179 @view_config(
180 @view_config(
180 route_name='pullrequest_show_all_data', request_method='GET',
181 route_name='pullrequest_show_all_data', request_method='GET',
181 renderer='json_ext', xhr=True)
182 renderer='json_ext', xhr=True)
182 def pull_request_list_data(self):
183 def pull_request_list_data(self):
183 self.load_default_context()
184 self.load_default_context()
184
185
185 # additional filters
186 # additional filters
186 req_get = self.request.GET
187 req_get = self.request.GET
187 source = str2bool(req_get.get('source'))
188 source = str2bool(req_get.get('source'))
188 closed = str2bool(req_get.get('closed'))
189 closed = str2bool(req_get.get('closed'))
189 my = str2bool(req_get.get('my'))
190 my = str2bool(req_get.get('my'))
190 awaiting_review = str2bool(req_get.get('awaiting_review'))
191 awaiting_review = str2bool(req_get.get('awaiting_review'))
191 awaiting_my_review = str2bool(req_get.get('awaiting_my_review'))
192 awaiting_my_review = str2bool(req_get.get('awaiting_my_review'))
192
193
193 filter_type = 'awaiting_review' if awaiting_review \
194 filter_type = 'awaiting_review' if awaiting_review \
194 else 'awaiting_my_review' if awaiting_my_review \
195 else 'awaiting_my_review' if awaiting_my_review \
195 else None
196 else None
196
197
197 opened_by = None
198 opened_by = None
198 if my:
199 if my:
199 opened_by = [self._rhodecode_user.user_id]
200 opened_by = [self._rhodecode_user.user_id]
200
201
201 statuses = [PullRequest.STATUS_NEW, PullRequest.STATUS_OPEN]
202 statuses = [PullRequest.STATUS_NEW, PullRequest.STATUS_OPEN]
202 if closed:
203 if closed:
203 statuses = [PullRequest.STATUS_CLOSED]
204 statuses = [PullRequest.STATUS_CLOSED]
204
205
205 data = self._get_pull_requests_list(
206 data = self._get_pull_requests_list(
206 repo_name=self.db_repo_name, source=source,
207 repo_name=self.db_repo_name, source=source,
207 filter_type=filter_type, opened_by=opened_by, statuses=statuses)
208 filter_type=filter_type, opened_by=opened_by, statuses=statuses)
208
209
209 return data
210 return data
210
211
211 def _is_diff_cache_enabled(self, target_repo):
212 def _is_diff_cache_enabled(self, target_repo):
212 caching_enabled = self._get_general_setting(
213 caching_enabled = self._get_general_setting(
213 target_repo, 'rhodecode_diff_cache')
214 target_repo, 'rhodecode_diff_cache')
214 log.debug('Diff caching enabled: %s', caching_enabled)
215 log.debug('Diff caching enabled: %s', caching_enabled)
215 return caching_enabled
216 return caching_enabled
216
217
217 def _get_diffset(self, source_repo_name, source_repo,
218 def _get_diffset(self, source_repo_name, source_repo,
218 ancestor_commit,
219 ancestor_commit,
219 source_ref_id, target_ref_id,
220 source_ref_id, target_ref_id,
220 target_commit, source_commit, diff_limit, file_limit,
221 target_commit, source_commit, diff_limit, file_limit,
221 fulldiff, hide_whitespace_changes, diff_context, use_ancestor=True):
222 fulldiff, hide_whitespace_changes, diff_context, use_ancestor=True):
222
223
223 if use_ancestor:
224 if use_ancestor:
224 # we might want to not use it for versions
225 # we might want to not use it for versions
225 target_ref_id = ancestor_commit.raw_id
226 target_ref_id = ancestor_commit.raw_id
226
227
227 vcs_diff = PullRequestModel().get_diff(
228 vcs_diff = PullRequestModel().get_diff(
228 source_repo, source_ref_id, target_ref_id,
229 source_repo, source_ref_id, target_ref_id,
229 hide_whitespace_changes, diff_context)
230 hide_whitespace_changes, diff_context)
230
231
231 diff_processor = diffs.DiffProcessor(
232 diff_processor = diffs.DiffProcessor(
232 vcs_diff, format='newdiff', diff_limit=diff_limit,
233 vcs_diff, format='newdiff', diff_limit=diff_limit,
233 file_limit=file_limit, show_full_diff=fulldiff)
234 file_limit=file_limit, show_full_diff=fulldiff)
234
235
235 _parsed = diff_processor.prepare()
236 _parsed = diff_processor.prepare()
236
237
237 diffset = codeblocks.DiffSet(
238 diffset = codeblocks.DiffSet(
238 repo_name=self.db_repo_name,
239 repo_name=self.db_repo_name,
239 source_repo_name=source_repo_name,
240 source_repo_name=source_repo_name,
240 source_node_getter=codeblocks.diffset_node_getter(target_commit),
241 source_node_getter=codeblocks.diffset_node_getter(target_commit),
241 target_node_getter=codeblocks.diffset_node_getter(source_commit),
242 target_node_getter=codeblocks.diffset_node_getter(source_commit),
242 )
243 )
243 diffset = self.path_filter.render_patchset_filtered(
244 diffset = self.path_filter.render_patchset_filtered(
244 diffset, _parsed, target_commit.raw_id, source_commit.raw_id)
245 diffset, _parsed, target_commit.raw_id, source_commit.raw_id)
245
246
246 return diffset
247 return diffset
247
248
248 def _get_range_diffset(self, source_scm, source_repo,
249 def _get_range_diffset(self, source_scm, source_repo,
249 commit1, commit2, diff_limit, file_limit,
250 commit1, commit2, diff_limit, file_limit,
250 fulldiff, hide_whitespace_changes, diff_context):
251 fulldiff, hide_whitespace_changes, diff_context):
251 vcs_diff = source_scm.get_diff(
252 vcs_diff = source_scm.get_diff(
252 commit1, commit2,
253 commit1, commit2,
253 ignore_whitespace=hide_whitespace_changes,
254 ignore_whitespace=hide_whitespace_changes,
254 context=diff_context)
255 context=diff_context)
255
256
256 diff_processor = diffs.DiffProcessor(
257 diff_processor = diffs.DiffProcessor(
257 vcs_diff, format='newdiff', diff_limit=diff_limit,
258 vcs_diff, format='newdiff', diff_limit=diff_limit,
258 file_limit=file_limit, show_full_diff=fulldiff)
259 file_limit=file_limit, show_full_diff=fulldiff)
259
260
260 _parsed = diff_processor.prepare()
261 _parsed = diff_processor.prepare()
261
262
262 diffset = codeblocks.DiffSet(
263 diffset = codeblocks.DiffSet(
263 repo_name=source_repo.repo_name,
264 repo_name=source_repo.repo_name,
264 source_node_getter=codeblocks.diffset_node_getter(commit1),
265 source_node_getter=codeblocks.diffset_node_getter(commit1),
265 target_node_getter=codeblocks.diffset_node_getter(commit2))
266 target_node_getter=codeblocks.diffset_node_getter(commit2))
266
267
267 diffset = self.path_filter.render_patchset_filtered(
268 diffset = self.path_filter.render_patchset_filtered(
268 diffset, _parsed, commit1.raw_id, commit2.raw_id)
269 diffset, _parsed, commit1.raw_id, commit2.raw_id)
269
270
270 return diffset
271 return diffset
271
272
272 def register_comments_vars(self, c, pull_request, versions, include_drafts=True):
273 def register_comments_vars(self, c, pull_request, versions, include_drafts=True):
273 comments_model = CommentsModel()
274 comments_model = CommentsModel()
274
275
275 # GENERAL COMMENTS with versions #
276 # GENERAL COMMENTS with versions #
276 q = comments_model._all_general_comments_of_pull_request(pull_request)
277 q = comments_model._all_general_comments_of_pull_request(pull_request)
277 q = q.order_by(ChangesetComment.comment_id.asc())
278 q = q.order_by(ChangesetComment.comment_id.asc())
278 if not include_drafts:
279 if not include_drafts:
279 q = q.filter(ChangesetComment.draft == false())
280 q = q.filter(ChangesetComment.draft == false())
280 general_comments = q
281 general_comments = q
281
282
282 # pick comments we want to render at current version
283 # pick comments we want to render at current version
283 c.comment_versions = comments_model.aggregate_comments(
284 c.comment_versions = comments_model.aggregate_comments(
284 general_comments, versions, c.at_version_num)
285 general_comments, versions, c.at_version_num)
285
286
286 # INLINE COMMENTS with versions #
287 # INLINE COMMENTS with versions #
287 q = comments_model._all_inline_comments_of_pull_request(pull_request)
288 q = comments_model._all_inline_comments_of_pull_request(pull_request)
288 q = q.order_by(ChangesetComment.comment_id.asc())
289 q = q.order_by(ChangesetComment.comment_id.asc())
289 if not include_drafts:
290 if not include_drafts:
290 q = q.filter(ChangesetComment.draft == false())
291 q = q.filter(ChangesetComment.draft == false())
291 inline_comments = q
292 inline_comments = q
292
293
293 c.inline_versions = comments_model.aggregate_comments(
294 c.inline_versions = comments_model.aggregate_comments(
294 inline_comments, versions, c.at_version_num, inline=True)
295 inline_comments, versions, c.at_version_num, inline=True)
295
296
296 # Comments inline+general
297 # Comments inline+general
297 if c.at_version:
298 if c.at_version:
298 c.inline_comments_flat = c.inline_versions[c.at_version_num]['display']
299 c.inline_comments_flat = c.inline_versions[c.at_version_num]['display']
299 c.comments = c.comment_versions[c.at_version_num]['display']
300 c.comments = c.comment_versions[c.at_version_num]['display']
300 else:
301 else:
301 c.inline_comments_flat = c.inline_versions[c.at_version_num]['until']
302 c.inline_comments_flat = c.inline_versions[c.at_version_num]['until']
302 c.comments = c.comment_versions[c.at_version_num]['until']
303 c.comments = c.comment_versions[c.at_version_num]['until']
303
304
304 return general_comments, inline_comments
305 return general_comments, inline_comments
305
306
306 @LoginRequired()
307 @LoginRequired()
307 @HasRepoPermissionAnyDecorator(
308 @HasRepoPermissionAnyDecorator(
308 'repository.read', 'repository.write', 'repository.admin')
309 'repository.read', 'repository.write', 'repository.admin')
309 @view_config(
310 @view_config(
310 route_name='pullrequest_show', request_method='GET',
311 route_name='pullrequest_show', request_method='GET',
311 renderer='rhodecode:templates/pullrequests/pullrequest_show.mako')
312 renderer='rhodecode:templates/pullrequests/pullrequest_show.mako')
312 def pull_request_show(self):
313 def pull_request_show(self):
313 _ = self.request.translate
314 _ = self.request.translate
314 c = self.load_default_context()
315 c = self.load_default_context()
315
316
316 pull_request = PullRequest.get_or_404(
317 pull_request = PullRequest.get_or_404(
317 self.request.matchdict['pull_request_id'])
318 self.request.matchdict['pull_request_id'])
318 pull_request_id = pull_request.pull_request_id
319 pull_request_id = pull_request.pull_request_id
319
320
320 c.state_progressing = pull_request.is_state_changing()
321 c.state_progressing = pull_request.is_state_changing()
321 c.pr_broadcast_channel = channelstream.pr_channel(pull_request)
322 c.pr_broadcast_channel = channelstream.pr_channel(pull_request)
322
323
323 _new_state = {
324 _new_state = {
324 'created': PullRequest.STATE_CREATED,
325 'created': PullRequest.STATE_CREATED,
325 }.get(self.request.GET.get('force_state'))
326 }.get(self.request.GET.get('force_state'))
326
327
327 if c.is_super_admin and _new_state:
328 if c.is_super_admin and _new_state:
328 with pull_request.set_state(PullRequest.STATE_UPDATING, final_state=_new_state):
329 with pull_request.set_state(PullRequest.STATE_UPDATING, final_state=_new_state):
329 h.flash(
330 h.flash(
330 _('Pull Request state was force changed to `{}`').format(_new_state),
331 _('Pull Request state was force changed to `{}`').format(_new_state),
331 category='success')
332 category='success')
332 Session().commit()
333 Session().commit()
333
334
334 raise HTTPFound(h.route_path(
335 raise HTTPFound(h.route_path(
335 'pullrequest_show', repo_name=self.db_repo_name,
336 'pullrequest_show', repo_name=self.db_repo_name,
336 pull_request_id=pull_request_id))
337 pull_request_id=pull_request_id))
337
338
338 version = self.request.GET.get('version')
339 version = self.request.GET.get('version')
339 from_version = self.request.GET.get('from_version') or version
340 from_version = self.request.GET.get('from_version') or version
340 merge_checks = self.request.GET.get('merge_checks')
341 merge_checks = self.request.GET.get('merge_checks')
341 c.fulldiff = str2bool(self.request.GET.get('fulldiff'))
342 c.fulldiff = str2bool(self.request.GET.get('fulldiff'))
342 force_refresh = str2bool(self.request.GET.get('force_refresh'))
343 force_refresh = str2bool(self.request.GET.get('force_refresh'))
343 c.range_diff_on = self.request.GET.get('range-diff') == "1"
344 c.range_diff_on = self.request.GET.get('range-diff') == "1"
344
345
345 # fetch global flags of ignore ws or context lines
346 # fetch global flags of ignore ws or context lines
346 diff_context = diffs.get_diff_context(self.request)
347 diff_context = diffs.get_diff_context(self.request)
347 hide_whitespace_changes = diffs.get_diff_whitespace_flag(self.request)
348 hide_whitespace_changes = diffs.get_diff_whitespace_flag(self.request)
348
349
349 (pull_request_latest,
350 (pull_request_latest,
350 pull_request_at_ver,
351 pull_request_at_ver,
351 pull_request_display_obj,
352 pull_request_display_obj,
352 at_version) = PullRequestModel().get_pr_version(
353 at_version) = PullRequestModel().get_pr_version(
353 pull_request_id, version=version)
354 pull_request_id, version=version)
354
355
355 pr_closed = pull_request_latest.is_closed()
356 pr_closed = pull_request_latest.is_closed()
356
357
357 if pr_closed and (version or from_version):
358 if pr_closed and (version or from_version):
358 # not allow to browse versions for closed PR
359 # not allow to browse versions for closed PR
359 raise HTTPFound(h.route_path(
360 raise HTTPFound(h.route_path(
360 'pullrequest_show', repo_name=self.db_repo_name,
361 'pullrequest_show', repo_name=self.db_repo_name,
361 pull_request_id=pull_request_id))
362 pull_request_id=pull_request_id))
362
363
363 versions = pull_request_display_obj.versions()
364 versions = pull_request_display_obj.versions()
364 # used to store per-commit range diffs
365 # used to store per-commit range diffs
365 c.changes = collections.OrderedDict()
366 c.changes = collections.OrderedDict()
366
367
367 c.at_version = at_version
368 c.at_version = at_version
368 c.at_version_num = (at_version
369 c.at_version_num = (at_version
369 if at_version and at_version != PullRequest.LATEST_VER
370 if at_version and at_version != PullRequest.LATEST_VER
370 else None)
371 else None)
371
372
372 c.at_version_index = ChangesetComment.get_index_from_version(
373 c.at_version_index = ChangesetComment.get_index_from_version(
373 c.at_version_num, versions)
374 c.at_version_num, versions)
374
375
375 (prev_pull_request_latest,
376 (prev_pull_request_latest,
376 prev_pull_request_at_ver,
377 prev_pull_request_at_ver,
377 prev_pull_request_display_obj,
378 prev_pull_request_display_obj,
378 prev_at_version) = PullRequestModel().get_pr_version(
379 prev_at_version) = PullRequestModel().get_pr_version(
379 pull_request_id, version=from_version)
380 pull_request_id, version=from_version)
380
381
381 c.from_version = prev_at_version
382 c.from_version = prev_at_version
382 c.from_version_num = (prev_at_version
383 c.from_version_num = (prev_at_version
383 if prev_at_version and prev_at_version != PullRequest.LATEST_VER
384 if prev_at_version and prev_at_version != PullRequest.LATEST_VER
384 else None)
385 else None)
385 c.from_version_index = ChangesetComment.get_index_from_version(
386 c.from_version_index = ChangesetComment.get_index_from_version(
386 c.from_version_num, versions)
387 c.from_version_num, versions)
387
388
388 # define if we're in COMPARE mode or VIEW at version mode
389 # define if we're in COMPARE mode or VIEW at version mode
389 compare = at_version != prev_at_version
390 compare = at_version != prev_at_version
390
391
391 # pull_requests repo_name we opened it against
392 # pull_requests repo_name we opened it against
392 # ie. target_repo must match
393 # ie. target_repo must match
393 if self.db_repo_name != pull_request_at_ver.target_repo.repo_name:
394 if self.db_repo_name != pull_request_at_ver.target_repo.repo_name:
394 log.warning('Mismatch between the current repo: %s, and target %s',
395 log.warning('Mismatch between the current repo: %s, and target %s',
395 self.db_repo_name, pull_request_at_ver.target_repo.repo_name)
396 self.db_repo_name, pull_request_at_ver.target_repo.repo_name)
396 raise HTTPNotFound()
397 raise HTTPNotFound()
397
398
398 c.shadow_clone_url = PullRequestModel().get_shadow_clone_url(pull_request_at_ver)
399 c.shadow_clone_url = PullRequestModel().get_shadow_clone_url(pull_request_at_ver)
399
400
400 c.pull_request = pull_request_display_obj
401 c.pull_request = pull_request_display_obj
401 c.renderer = pull_request_at_ver.description_renderer or c.renderer
402 c.renderer = pull_request_at_ver.description_renderer or c.renderer
402 c.pull_request_latest = pull_request_latest
403 c.pull_request_latest = pull_request_latest
403
404
404 # inject latest version
405 # inject latest version
405 latest_ver = PullRequest.get_pr_display_object(pull_request_latest, pull_request_latest)
406 latest_ver = PullRequest.get_pr_display_object(pull_request_latest, pull_request_latest)
406 c.versions = versions + [latest_ver]
407 c.versions = versions + [latest_ver]
407
408
408 if compare or (at_version and not at_version == PullRequest.LATEST_VER):
409 if compare or (at_version and not at_version == PullRequest.LATEST_VER):
409 c.allowed_to_change_status = False
410 c.allowed_to_change_status = False
410 c.allowed_to_update = False
411 c.allowed_to_update = False
411 c.allowed_to_merge = False
412 c.allowed_to_merge = False
412 c.allowed_to_delete = False
413 c.allowed_to_delete = False
413 c.allowed_to_comment = False
414 c.allowed_to_comment = False
414 c.allowed_to_close = False
415 c.allowed_to_close = False
415 else:
416 else:
416 can_change_status = PullRequestModel().check_user_change_status(
417 can_change_status = PullRequestModel().check_user_change_status(
417 pull_request_at_ver, self._rhodecode_user)
418 pull_request_at_ver, self._rhodecode_user)
418 c.allowed_to_change_status = can_change_status and not pr_closed
419 c.allowed_to_change_status = can_change_status and not pr_closed
419
420
420 c.allowed_to_update = PullRequestModel().check_user_update(
421 c.allowed_to_update = PullRequestModel().check_user_update(
421 pull_request_latest, self._rhodecode_user) and not pr_closed
422 pull_request_latest, self._rhodecode_user) and not pr_closed
422 c.allowed_to_merge = PullRequestModel().check_user_merge(
423 c.allowed_to_merge = PullRequestModel().check_user_merge(
423 pull_request_latest, self._rhodecode_user) and not pr_closed
424 pull_request_latest, self._rhodecode_user) and not pr_closed
424 c.allowed_to_delete = PullRequestModel().check_user_delete(
425 c.allowed_to_delete = PullRequestModel().check_user_delete(
425 pull_request_latest, self._rhodecode_user) and not pr_closed
426 pull_request_latest, self._rhodecode_user) and not pr_closed
426 c.allowed_to_comment = not pr_closed
427 c.allowed_to_comment = not pr_closed
427 c.allowed_to_close = c.allowed_to_merge and not pr_closed
428 c.allowed_to_close = c.allowed_to_merge and not pr_closed
428
429
429 c.forbid_adding_reviewers = False
430 c.forbid_adding_reviewers = False
430 c.forbid_author_to_review = False
431 c.forbid_author_to_review = False
431 c.forbid_commit_author_to_review = False
432 c.forbid_commit_author_to_review = False
432
433
433 if pull_request_latest.reviewer_data and \
434 if pull_request_latest.reviewer_data and \
434 'rules' in pull_request_latest.reviewer_data:
435 'rules' in pull_request_latest.reviewer_data:
435 rules = pull_request_latest.reviewer_data['rules'] or {}
436 rules = pull_request_latest.reviewer_data['rules'] or {}
436 try:
437 try:
437 c.forbid_adding_reviewers = rules.get('forbid_adding_reviewers')
438 c.forbid_adding_reviewers = rules.get('forbid_adding_reviewers')
438 c.forbid_author_to_review = rules.get('forbid_author_to_review')
439 c.forbid_author_to_review = rules.get('forbid_author_to_review')
439 c.forbid_commit_author_to_review = rules.get('forbid_commit_author_to_review')
440 c.forbid_commit_author_to_review = rules.get('forbid_commit_author_to_review')
440 except Exception:
441 except Exception:
441 pass
442 pass
442
443
443 # check merge capabilities
444 # check merge capabilities
444 _merge_check = MergeCheck.validate(
445 _merge_check = MergeCheck.validate(
445 pull_request_latest, auth_user=self._rhodecode_user,
446 pull_request_latest, auth_user=self._rhodecode_user,
446 translator=self.request.translate,
447 translator=self.request.translate,
447 force_shadow_repo_refresh=force_refresh)
448 force_shadow_repo_refresh=force_refresh)
448
449
449 c.pr_merge_errors = _merge_check.error_details
450 c.pr_merge_errors = _merge_check.error_details
450 c.pr_merge_possible = not _merge_check.failed
451 c.pr_merge_possible = not _merge_check.failed
451 c.pr_merge_message = _merge_check.merge_msg
452 c.pr_merge_message = _merge_check.merge_msg
452 c.pr_merge_source_commit = _merge_check.source_commit
453 c.pr_merge_source_commit = _merge_check.source_commit
453 c.pr_merge_target_commit = _merge_check.target_commit
454 c.pr_merge_target_commit = _merge_check.target_commit
454
455
455 c.pr_merge_info = MergeCheck.get_merge_conditions(
456 c.pr_merge_info = MergeCheck.get_merge_conditions(
456 pull_request_latest, translator=self.request.translate)
457 pull_request_latest, translator=self.request.translate)
457
458
458 c.pull_request_review_status = _merge_check.review_status
459 c.pull_request_review_status = _merge_check.review_status
459 if merge_checks:
460 if merge_checks:
460 self.request.override_renderer = \
461 self.request.override_renderer = \
461 'rhodecode:templates/pullrequests/pullrequest_merge_checks.mako'
462 'rhodecode:templates/pullrequests/pullrequest_merge_checks.mako'
462 return self._get_template_context(c)
463 return self._get_template_context(c)
463
464
464 c.reviewers_count = pull_request.reviewers_count
465 c.reviewers_count = pull_request.reviewers_count
465 c.observers_count = pull_request.observers_count
466 c.observers_count = pull_request.observers_count
466
467
467 # reviewers and statuses
468 # reviewers and statuses
468 c.pull_request_default_reviewers_data_json = json.dumps(pull_request.reviewer_data)
469 c.pull_request_default_reviewers_data_json = json.dumps(pull_request.reviewer_data)
469 c.pull_request_set_reviewers_data_json = collections.OrderedDict({'reviewers': []})
470 c.pull_request_set_reviewers_data_json = collections.OrderedDict({'reviewers': []})
470 c.pull_request_set_observers_data_json = collections.OrderedDict({'observers': []})
471 c.pull_request_set_observers_data_json = collections.OrderedDict({'observers': []})
471
472
472 for review_obj, member, reasons, mandatory, status in pull_request_at_ver.reviewers_statuses():
473 for review_obj, member, reasons, mandatory, status in pull_request_at_ver.reviewers_statuses():
473 member_reviewer = h.reviewer_as_json(
474 member_reviewer = h.reviewer_as_json(
474 member, reasons=reasons, mandatory=mandatory,
475 member, reasons=reasons, mandatory=mandatory,
475 role=review_obj.role,
476 role=review_obj.role,
476 user_group=review_obj.rule_user_group_data()
477 user_group=review_obj.rule_user_group_data()
477 )
478 )
478
479
479 current_review_status = status[0][1].status if status else ChangesetStatus.STATUS_NOT_REVIEWED
480 current_review_status = status[0][1].status if status else ChangesetStatus.STATUS_NOT_REVIEWED
480 member_reviewer['review_status'] = current_review_status
481 member_reviewer['review_status'] = current_review_status
481 member_reviewer['review_status_label'] = h.commit_status_lbl(current_review_status)
482 member_reviewer['review_status_label'] = h.commit_status_lbl(current_review_status)
482 member_reviewer['allowed_to_update'] = c.allowed_to_update
483 member_reviewer['allowed_to_update'] = c.allowed_to_update
483 c.pull_request_set_reviewers_data_json['reviewers'].append(member_reviewer)
484 c.pull_request_set_reviewers_data_json['reviewers'].append(member_reviewer)
484
485
485 c.pull_request_set_reviewers_data_json = json.dumps(c.pull_request_set_reviewers_data_json)
486 c.pull_request_set_reviewers_data_json = json.dumps(c.pull_request_set_reviewers_data_json)
486
487
487 for observer_obj, member in pull_request_at_ver.observers():
488 for observer_obj, member in pull_request_at_ver.observers():
488 member_observer = h.reviewer_as_json(
489 member_observer = h.reviewer_as_json(
489 member, reasons=[], mandatory=False,
490 member, reasons=[], mandatory=False,
490 role=observer_obj.role,
491 role=observer_obj.role,
491 user_group=observer_obj.rule_user_group_data()
492 user_group=observer_obj.rule_user_group_data()
492 )
493 )
493 member_observer['allowed_to_update'] = c.allowed_to_update
494 member_observer['allowed_to_update'] = c.allowed_to_update
494 c.pull_request_set_observers_data_json['observers'].append(member_observer)
495 c.pull_request_set_observers_data_json['observers'].append(member_observer)
495
496
496 c.pull_request_set_observers_data_json = json.dumps(c.pull_request_set_observers_data_json)
497 c.pull_request_set_observers_data_json = json.dumps(c.pull_request_set_observers_data_json)
497
498
498 general_comments, inline_comments = \
499 general_comments, inline_comments = \
499 self.register_comments_vars(c, pull_request_latest, versions)
500 self.register_comments_vars(c, pull_request_latest, versions)
500
501
501 # TODOs
502 # TODOs
502 c.unresolved_comments = CommentsModel() \
503 c.unresolved_comments = CommentsModel() \
503 .get_pull_request_unresolved_todos(pull_request_latest)
504 .get_pull_request_unresolved_todos(pull_request_latest)
504 c.resolved_comments = CommentsModel() \
505 c.resolved_comments = CommentsModel() \
505 .get_pull_request_resolved_todos(pull_request_latest)
506 .get_pull_request_resolved_todos(pull_request_latest)
506
507
507 # if we use version, then do not show later comments
508 # if we use version, then do not show later comments
508 # than current version
509 # than current version
509 display_inline_comments = collections.defaultdict(
510 display_inline_comments = collections.defaultdict(
510 lambda: collections.defaultdict(list))
511 lambda: collections.defaultdict(list))
511 for co in inline_comments:
512 for co in inline_comments:
512 if c.at_version_num:
513 if c.at_version_num:
513 # pick comments that are at least UPTO given version, so we
514 # pick comments that are at least UPTO given version, so we
514 # don't render comments for higher version
515 # don't render comments for higher version
515 should_render = co.pull_request_version_id and \
516 should_render = co.pull_request_version_id and \
516 co.pull_request_version_id <= c.at_version_num
517 co.pull_request_version_id <= c.at_version_num
517 else:
518 else:
518 # showing all, for 'latest'
519 # showing all, for 'latest'
519 should_render = True
520 should_render = True
520
521
521 if should_render:
522 if should_render:
522 display_inline_comments[co.f_path][co.line_no].append(co)
523 display_inline_comments[co.f_path][co.line_no].append(co)
523
524
524 # load diff data into template context, if we use compare mode then
525 # load diff data into template context, if we use compare mode then
525 # diff is calculated based on changes between versions of PR
526 # diff is calculated based on changes between versions of PR
526
527
527 source_repo = pull_request_at_ver.source_repo
528 source_repo = pull_request_at_ver.source_repo
528 source_ref_id = pull_request_at_ver.source_ref_parts.commit_id
529 source_ref_id = pull_request_at_ver.source_ref_parts.commit_id
529
530
530 target_repo = pull_request_at_ver.target_repo
531 target_repo = pull_request_at_ver.target_repo
531 target_ref_id = pull_request_at_ver.target_ref_parts.commit_id
532 target_ref_id = pull_request_at_ver.target_ref_parts.commit_id
532
533
533 if compare:
534 if compare:
534 # in compare switch the diff base to latest commit from prev version
535 # in compare switch the diff base to latest commit from prev version
535 target_ref_id = prev_pull_request_display_obj.revisions[0]
536 target_ref_id = prev_pull_request_display_obj.revisions[0]
536
537
537 # despite opening commits for bookmarks/branches/tags, we always
538 # despite opening commits for bookmarks/branches/tags, we always
538 # convert this to rev to prevent changes after bookmark or branch change
539 # convert this to rev to prevent changes after bookmark or branch change
539 c.source_ref_type = 'rev'
540 c.source_ref_type = 'rev'
540 c.source_ref = source_ref_id
541 c.source_ref = source_ref_id
541
542
542 c.target_ref_type = 'rev'
543 c.target_ref_type = 'rev'
543 c.target_ref = target_ref_id
544 c.target_ref = target_ref_id
544
545
545 c.source_repo = source_repo
546 c.source_repo = source_repo
546 c.target_repo = target_repo
547 c.target_repo = target_repo
547
548
548 c.commit_ranges = []
549 c.commit_ranges = []
549 source_commit = EmptyCommit()
550 source_commit = EmptyCommit()
550 target_commit = EmptyCommit()
551 target_commit = EmptyCommit()
551 c.missing_requirements = False
552 c.missing_requirements = False
552
553
553 source_scm = source_repo.scm_instance()
554 source_scm = source_repo.scm_instance()
554 target_scm = target_repo.scm_instance()
555 target_scm = target_repo.scm_instance()
555
556
556 shadow_scm = None
557 shadow_scm = None
557 try:
558 try:
558 shadow_scm = pull_request_latest.get_shadow_repo()
559 shadow_scm = pull_request_latest.get_shadow_repo()
559 except Exception:
560 except Exception:
560 log.debug('Failed to get shadow repo', exc_info=True)
561 log.debug('Failed to get shadow repo', exc_info=True)
561 # try first the existing source_repo, and then shadow
562 # try first the existing source_repo, and then shadow
562 # repo if we can obtain one
563 # repo if we can obtain one
563 commits_source_repo = source_scm
564 commits_source_repo = source_scm
564 if shadow_scm:
565 if shadow_scm:
565 commits_source_repo = shadow_scm
566 commits_source_repo = shadow_scm
566
567
567 c.commits_source_repo = commits_source_repo
568 c.commits_source_repo = commits_source_repo
568 c.ancestor = None # set it to None, to hide it from PR view
569 c.ancestor = None # set it to None, to hide it from PR view
569
570
570 # empty version means latest, so we keep this to prevent
571 # empty version means latest, so we keep this to prevent
571 # double caching
572 # double caching
572 version_normalized = version or PullRequest.LATEST_VER
573 version_normalized = version or PullRequest.LATEST_VER
573 from_version_normalized = from_version or PullRequest.LATEST_VER
574 from_version_normalized = from_version or PullRequest.LATEST_VER
574
575
575 cache_path = self.rhodecode_vcs_repo.get_create_shadow_cache_pr_path(target_repo)
576 cache_path = self.rhodecode_vcs_repo.get_create_shadow_cache_pr_path(target_repo)
576 cache_file_path = diff_cache_exist(
577 cache_file_path = diff_cache_exist(
577 cache_path, 'pull_request', pull_request_id, version_normalized,
578 cache_path, 'pull_request', pull_request_id, version_normalized,
578 from_version_normalized, source_ref_id, target_ref_id,
579 from_version_normalized, source_ref_id, target_ref_id,
579 hide_whitespace_changes, diff_context, c.fulldiff)
580 hide_whitespace_changes, diff_context, c.fulldiff)
580
581
581 caching_enabled = self._is_diff_cache_enabled(c.target_repo)
582 caching_enabled = self._is_diff_cache_enabled(c.target_repo)
582 force_recache = self.get_recache_flag()
583 force_recache = self.get_recache_flag()
583
584
584 cached_diff = None
585 cached_diff = None
585 if caching_enabled:
586 if caching_enabled:
586 cached_diff = load_cached_diff(cache_file_path)
587 cached_diff = load_cached_diff(cache_file_path)
587
588
588 has_proper_commit_cache = (
589 has_proper_commit_cache = (
589 cached_diff and cached_diff.get('commits')
590 cached_diff and cached_diff.get('commits')
590 and len(cached_diff.get('commits', [])) == 5
591 and len(cached_diff.get('commits', [])) == 5
591 and cached_diff.get('commits')[0]
592 and cached_diff.get('commits')[0]
592 and cached_diff.get('commits')[3])
593 and cached_diff.get('commits')[3])
593
594
594 if not force_recache and not c.range_diff_on and has_proper_commit_cache:
595 if not force_recache and not c.range_diff_on and has_proper_commit_cache:
595 diff_commit_cache = \
596 diff_commit_cache = \
596 (ancestor_commit, commit_cache, missing_requirements,
597 (ancestor_commit, commit_cache, missing_requirements,
597 source_commit, target_commit) = cached_diff['commits']
598 source_commit, target_commit) = cached_diff['commits']
598 else:
599 else:
599 # NOTE(marcink): we reach potentially unreachable errors when a PR has
600 # NOTE(marcink): we reach potentially unreachable errors when a PR has
600 # merge errors resulting in potentially hidden commits in the shadow repo.
601 # merge errors resulting in potentially hidden commits in the shadow repo.
601 maybe_unreachable = _merge_check.MERGE_CHECK in _merge_check.error_details \
602 maybe_unreachable = _merge_check.MERGE_CHECK in _merge_check.error_details \
602 and _merge_check.merge_response
603 and _merge_check.merge_response
603 maybe_unreachable = maybe_unreachable \
604 maybe_unreachable = maybe_unreachable \
604 and _merge_check.merge_response.metadata.get('unresolved_files')
605 and _merge_check.merge_response.metadata.get('unresolved_files')
605 log.debug("Using unreachable commits due to MERGE_CHECK in merge simulation")
606 log.debug("Using unreachable commits due to MERGE_CHECK in merge simulation")
606 diff_commit_cache = \
607 diff_commit_cache = \
607 (ancestor_commit, commit_cache, missing_requirements,
608 (ancestor_commit, commit_cache, missing_requirements,
608 source_commit, target_commit) = self.get_commits(
609 source_commit, target_commit) = self.get_commits(
609 commits_source_repo,
610 commits_source_repo,
610 pull_request_at_ver,
611 pull_request_at_ver,
611 source_commit,
612 source_commit,
612 source_ref_id,
613 source_ref_id,
613 source_scm,
614 source_scm,
614 target_commit,
615 target_commit,
615 target_ref_id,
616 target_ref_id,
616 target_scm,
617 target_scm,
617 maybe_unreachable=maybe_unreachable)
618 maybe_unreachable=maybe_unreachable)
618
619
619 # register our commit range
620 # register our commit range
620 for comm in commit_cache.values():
621 for comm in commit_cache.values():
621 c.commit_ranges.append(comm)
622 c.commit_ranges.append(comm)
622
623
623 c.missing_requirements = missing_requirements
624 c.missing_requirements = missing_requirements
624 c.ancestor_commit = ancestor_commit
625 c.ancestor_commit = ancestor_commit
625 c.statuses = source_repo.statuses(
626 c.statuses = source_repo.statuses(
626 [x.raw_id for x in c.commit_ranges])
627 [x.raw_id for x in c.commit_ranges])
627
628
628 # auto collapse if we have more than limit
629 # auto collapse if we have more than limit
629 collapse_limit = diffs.DiffProcessor._collapse_commits_over
630 collapse_limit = diffs.DiffProcessor._collapse_commits_over
630 c.collapse_all_commits = len(c.commit_ranges) > collapse_limit
631 c.collapse_all_commits = len(c.commit_ranges) > collapse_limit
631 c.compare_mode = compare
632 c.compare_mode = compare
632
633
633 # diff_limit is the old behavior, will cut off the whole diff
634 # diff_limit is the old behavior, will cut off the whole diff
634 # if the limit is applied otherwise will just hide the
635 # if the limit is applied otherwise will just hide the
635 # big files from the front-end
636 # big files from the front-end
636 diff_limit = c.visual.cut_off_limit_diff
637 diff_limit = c.visual.cut_off_limit_diff
637 file_limit = c.visual.cut_off_limit_file
638 file_limit = c.visual.cut_off_limit_file
638
639
639 c.missing_commits = False
640 c.missing_commits = False
640 if (c.missing_requirements
641 if (c.missing_requirements
641 or isinstance(source_commit, EmptyCommit)
642 or isinstance(source_commit, EmptyCommit)
642 or source_commit == target_commit):
643 or source_commit == target_commit):
643
644
644 c.missing_commits = True
645 c.missing_commits = True
645 else:
646 else:
646 c.inline_comments = display_inline_comments
647 c.inline_comments = display_inline_comments
647
648
648 use_ancestor = True
649 use_ancestor = True
649 if from_version_normalized != version_normalized:
650 if from_version_normalized != version_normalized:
650 use_ancestor = False
651 use_ancestor = False
651
652
652 has_proper_diff_cache = cached_diff and cached_diff.get('commits')
653 has_proper_diff_cache = cached_diff and cached_diff.get('commits')
653 if not force_recache and has_proper_diff_cache:
654 if not force_recache and has_proper_diff_cache:
654 c.diffset = cached_diff['diff']
655 c.diffset = cached_diff['diff']
655 else:
656 else:
656 try:
657 try:
657 c.diffset = self._get_diffset(
658 c.diffset = self._get_diffset(
658 c.source_repo.repo_name, commits_source_repo,
659 c.source_repo.repo_name, commits_source_repo,
659 c.ancestor_commit,
660 c.ancestor_commit,
660 source_ref_id, target_ref_id,
661 source_ref_id, target_ref_id,
661 target_commit, source_commit,
662 target_commit, source_commit,
662 diff_limit, file_limit, c.fulldiff,
663 diff_limit, file_limit, c.fulldiff,
663 hide_whitespace_changes, diff_context,
664 hide_whitespace_changes, diff_context,
664 use_ancestor=use_ancestor
665 use_ancestor=use_ancestor
665 )
666 )
666
667
667 # save cached diff
668 # save cached diff
668 if caching_enabled:
669 if caching_enabled:
669 cache_diff(cache_file_path, c.diffset, diff_commit_cache)
670 cache_diff(cache_file_path, c.diffset, diff_commit_cache)
670 except CommitDoesNotExistError:
671 except CommitDoesNotExistError:
671 log.exception('Failed to generate diffset')
672 log.exception('Failed to generate diffset')
672 c.missing_commits = True
673 c.missing_commits = True
673
674
674 if not c.missing_commits:
675 if not c.missing_commits:
675
676
676 c.limited_diff = c.diffset.limited_diff
677 c.limited_diff = c.diffset.limited_diff
677
678
678 # calculate removed files that are bound to comments
679 # calculate removed files that are bound to comments
679 comment_deleted_files = [
680 comment_deleted_files = [
680 fname for fname in display_inline_comments
681 fname for fname in display_inline_comments
681 if fname not in c.diffset.file_stats]
682 if fname not in c.diffset.file_stats]
682
683
683 c.deleted_files_comments = collections.defaultdict(dict)
684 c.deleted_files_comments = collections.defaultdict(dict)
684 for fname, per_line_comments in display_inline_comments.items():
685 for fname, per_line_comments in display_inline_comments.items():
685 if fname in comment_deleted_files:
686 if fname in comment_deleted_files:
686 c.deleted_files_comments[fname]['stats'] = 0
687 c.deleted_files_comments[fname]['stats'] = 0
687 c.deleted_files_comments[fname]['comments'] = list()
688 c.deleted_files_comments[fname]['comments'] = list()
688 for lno, comments in per_line_comments.items():
689 for lno, comments in per_line_comments.items():
689 c.deleted_files_comments[fname]['comments'].extend(comments)
690 c.deleted_files_comments[fname]['comments'].extend(comments)
690
691
691 # maybe calculate the range diff
692 # maybe calculate the range diff
692 if c.range_diff_on:
693 if c.range_diff_on:
693 # TODO(marcink): set whitespace/context
694 # TODO(marcink): set whitespace/context
694 context_lcl = 3
695 context_lcl = 3
695 ign_whitespace_lcl = False
696 ign_whitespace_lcl = False
696
697
697 for commit in c.commit_ranges:
698 for commit in c.commit_ranges:
698 commit2 = commit
699 commit2 = commit
699 commit1 = commit.first_parent
700 commit1 = commit.first_parent
700
701
701 range_diff_cache_file_path = diff_cache_exist(
702 range_diff_cache_file_path = diff_cache_exist(
702 cache_path, 'diff', commit.raw_id,
703 cache_path, 'diff', commit.raw_id,
703 ign_whitespace_lcl, context_lcl, c.fulldiff)
704 ign_whitespace_lcl, context_lcl, c.fulldiff)
704
705
705 cached_diff = None
706 cached_diff = None
706 if caching_enabled:
707 if caching_enabled:
707 cached_diff = load_cached_diff(range_diff_cache_file_path)
708 cached_diff = load_cached_diff(range_diff_cache_file_path)
708
709
709 has_proper_diff_cache = cached_diff and cached_diff.get('diff')
710 has_proper_diff_cache = cached_diff and cached_diff.get('diff')
710 if not force_recache and has_proper_diff_cache:
711 if not force_recache and has_proper_diff_cache:
711 diffset = cached_diff['diff']
712 diffset = cached_diff['diff']
712 else:
713 else:
713 diffset = self._get_range_diffset(
714 diffset = self._get_range_diffset(
714 commits_source_repo, source_repo,
715 commits_source_repo, source_repo,
715 commit1, commit2, diff_limit, file_limit,
716 commit1, commit2, diff_limit, file_limit,
716 c.fulldiff, ign_whitespace_lcl, context_lcl
717 c.fulldiff, ign_whitespace_lcl, context_lcl
717 )
718 )
718
719
719 # save cached diff
720 # save cached diff
720 if caching_enabled:
721 if caching_enabled:
721 cache_diff(range_diff_cache_file_path, diffset, None)
722 cache_diff(range_diff_cache_file_path, diffset, None)
722
723
723 c.changes[commit.raw_id] = diffset
724 c.changes[commit.raw_id] = diffset
724
725
725 # this is a hack to properly display links, when creating PR, the
726 # this is a hack to properly display links, when creating PR, the
726 # compare view and others uses different notation, and
727 # compare view and others uses different notation, and
727 # compare_commits.mako renders links based on the target_repo.
728 # compare_commits.mako renders links based on the target_repo.
728 # We need to swap that here to generate it properly on the html side
729 # We need to swap that here to generate it properly on the html side
729 c.target_repo = c.source_repo
730 c.target_repo = c.source_repo
730
731
731 c.commit_statuses = ChangesetStatus.STATUSES
732 c.commit_statuses = ChangesetStatus.STATUSES
732
733
733 c.show_version_changes = not pr_closed
734 c.show_version_changes = not pr_closed
734 if c.show_version_changes:
735 if c.show_version_changes:
735 cur_obj = pull_request_at_ver
736 cur_obj = pull_request_at_ver
736 prev_obj = prev_pull_request_at_ver
737 prev_obj = prev_pull_request_at_ver
737
738
738 old_commit_ids = prev_obj.revisions
739 old_commit_ids = prev_obj.revisions
739 new_commit_ids = cur_obj.revisions
740 new_commit_ids = cur_obj.revisions
740 commit_changes = PullRequestModel()._calculate_commit_id_changes(
741 commit_changes = PullRequestModel()._calculate_commit_id_changes(
741 old_commit_ids, new_commit_ids)
742 old_commit_ids, new_commit_ids)
742 c.commit_changes_summary = commit_changes
743 c.commit_changes_summary = commit_changes
743
744
744 # calculate the diff for commits between versions
745 # calculate the diff for commits between versions
745 c.commit_changes = []
746 c.commit_changes = []
746
747
747 def mark(cs, fw):
748 def mark(cs, fw):
748 return list(h.itertools.izip_longest([], cs, fillvalue=fw))
749 return list(h.itertools.izip_longest([], cs, fillvalue=fw))
749
750
750 for c_type, raw_id in mark(commit_changes.added, 'a') \
751 for c_type, raw_id in mark(commit_changes.added, 'a') \
751 + mark(commit_changes.removed, 'r') \
752 + mark(commit_changes.removed, 'r') \
752 + mark(commit_changes.common, 'c'):
753 + mark(commit_changes.common, 'c'):
753
754
754 if raw_id in commit_cache:
755 if raw_id in commit_cache:
755 commit = commit_cache[raw_id]
756 commit = commit_cache[raw_id]
756 else:
757 else:
757 try:
758 try:
758 commit = commits_source_repo.get_commit(raw_id)
759 commit = commits_source_repo.get_commit(raw_id)
759 except CommitDoesNotExistError:
760 except CommitDoesNotExistError:
760 # in case we fail extracting still use "dummy" commit
761 # in case we fail extracting still use "dummy" commit
761 # for display in commit diff
762 # for display in commit diff
762 commit = h.AttributeDict(
763 commit = h.AttributeDict(
763 {'raw_id': raw_id,
764 {'raw_id': raw_id,
764 'message': 'EMPTY or MISSING COMMIT'})
765 'message': 'EMPTY or MISSING COMMIT'})
765 c.commit_changes.append([c_type, commit])
766 c.commit_changes.append([c_type, commit])
766
767
767 # current user review statuses for each version
768 # current user review statuses for each version
768 c.review_versions = {}
769 c.review_versions = {}
769 is_reviewer = PullRequestModel().is_user_reviewer(
770 is_reviewer = PullRequestModel().is_user_reviewer(
770 pull_request, self._rhodecode_user)
771 pull_request, self._rhodecode_user)
771 if is_reviewer:
772 if is_reviewer:
772 for co in general_comments:
773 for co in general_comments:
773 if co.author.user_id == self._rhodecode_user.user_id:
774 if co.author.user_id == self._rhodecode_user.user_id:
774 status = co.status_change
775 status = co.status_change
775 if status:
776 if status:
776 _ver_pr = status[0].comment.pull_request_version_id
777 _ver_pr = status[0].comment.pull_request_version_id
777 c.review_versions[_ver_pr] = status[0]
778 c.review_versions[_ver_pr] = status[0]
778
779
779 return self._get_template_context(c)
780 return self._get_template_context(c)
780
781
781 def get_commits(
782 def get_commits(
782 self, commits_source_repo, pull_request_at_ver, source_commit,
783 self, commits_source_repo, pull_request_at_ver, source_commit,
783 source_ref_id, source_scm, target_commit, target_ref_id, target_scm,
784 source_ref_id, source_scm, target_commit, target_ref_id, target_scm,
784 maybe_unreachable=False):
785 maybe_unreachable=False):
785
786
786 commit_cache = collections.OrderedDict()
787 commit_cache = collections.OrderedDict()
787 missing_requirements = False
788 missing_requirements = False
788
789
789 try:
790 try:
790 pre_load = ["author", "date", "message", "branch", "parents"]
791 pre_load = ["author", "date", "message", "branch", "parents"]
791
792
792 pull_request_commits = pull_request_at_ver.revisions
793 pull_request_commits = pull_request_at_ver.revisions
793 log.debug('Loading %s commits from %s',
794 log.debug('Loading %s commits from %s',
794 len(pull_request_commits), commits_source_repo)
795 len(pull_request_commits), commits_source_repo)
795
796
796 for rev in pull_request_commits:
797 for rev in pull_request_commits:
797 comm = commits_source_repo.get_commit(commit_id=rev, pre_load=pre_load,
798 comm = commits_source_repo.get_commit(commit_id=rev, pre_load=pre_load,
798 maybe_unreachable=maybe_unreachable)
799 maybe_unreachable=maybe_unreachable)
799 commit_cache[comm.raw_id] = comm
800 commit_cache[comm.raw_id] = comm
800
801
801 # Order here matters, we first need to get target, and then
802 # Order here matters, we first need to get target, and then
802 # the source
803 # the source
803 target_commit = commits_source_repo.get_commit(
804 target_commit = commits_source_repo.get_commit(
804 commit_id=safe_str(target_ref_id))
805 commit_id=safe_str(target_ref_id))
805
806
806 source_commit = commits_source_repo.get_commit(
807 source_commit = commits_source_repo.get_commit(
807 commit_id=safe_str(source_ref_id), maybe_unreachable=True)
808 commit_id=safe_str(source_ref_id), maybe_unreachable=True)
808 except CommitDoesNotExistError:
809 except CommitDoesNotExistError:
809 log.warning('Failed to get commit from `{}` repo'.format(
810 log.warning('Failed to get commit from `{}` repo'.format(
810 commits_source_repo), exc_info=True)
811 commits_source_repo), exc_info=True)
811 except RepositoryRequirementError:
812 except RepositoryRequirementError:
812 log.warning('Failed to get all required data from repo', exc_info=True)
813 log.warning('Failed to get all required data from repo', exc_info=True)
813 missing_requirements = True
814 missing_requirements = True
814
815
815 pr_ancestor_id = pull_request_at_ver.common_ancestor_id
816 pr_ancestor_id = pull_request_at_ver.common_ancestor_id
816
817
817 try:
818 try:
818 ancestor_commit = source_scm.get_commit(pr_ancestor_id)
819 ancestor_commit = source_scm.get_commit(pr_ancestor_id)
819 except Exception:
820 except Exception:
820 ancestor_commit = None
821 ancestor_commit = None
821
822
822 return ancestor_commit, commit_cache, missing_requirements, source_commit, target_commit
823 return ancestor_commit, commit_cache, missing_requirements, source_commit, target_commit
823
824
824 def assure_not_empty_repo(self):
825 def assure_not_empty_repo(self):
825 _ = self.request.translate
826 _ = self.request.translate
826
827
827 try:
828 try:
828 self.db_repo.scm_instance().get_commit()
829 self.db_repo.scm_instance().get_commit()
829 except EmptyRepositoryError:
830 except EmptyRepositoryError:
830 h.flash(h.literal(_('There are no commits yet')),
831 h.flash(h.literal(_('There are no commits yet')),
831 category='warning')
832 category='warning')
832 raise HTTPFound(
833 raise HTTPFound(
833 h.route_path('repo_summary', repo_name=self.db_repo.repo_name))
834 h.route_path('repo_summary', repo_name=self.db_repo.repo_name))
834
835
835 @LoginRequired()
836 @LoginRequired()
836 @NotAnonymous()
837 @NotAnonymous()
837 @HasRepoPermissionAnyDecorator(
838 @HasRepoPermissionAnyDecorator(
838 'repository.read', 'repository.write', 'repository.admin')
839 'repository.read', 'repository.write', 'repository.admin')
839 @view_config(
840 @view_config(
840 route_name='pullrequest_new', request_method='GET',
841 route_name='pullrequest_new', request_method='GET',
841 renderer='rhodecode:templates/pullrequests/pullrequest.mako')
842 renderer='rhodecode:templates/pullrequests/pullrequest.mako')
842 def pull_request_new(self):
843 def pull_request_new(self):
843 _ = self.request.translate
844 _ = self.request.translate
844 c = self.load_default_context()
845 c = self.load_default_context()
845
846
846 self.assure_not_empty_repo()
847 self.assure_not_empty_repo()
847 source_repo = self.db_repo
848 source_repo = self.db_repo
848
849
849 commit_id = self.request.GET.get('commit')
850 commit_id = self.request.GET.get('commit')
850 branch_ref = self.request.GET.get('branch')
851 branch_ref = self.request.GET.get('branch')
851 bookmark_ref = self.request.GET.get('bookmark')
852 bookmark_ref = self.request.GET.get('bookmark')
852
853
853 try:
854 try:
854 source_repo_data = PullRequestModel().generate_repo_data(
855 source_repo_data = PullRequestModel().generate_repo_data(
855 source_repo, commit_id=commit_id,
856 source_repo, commit_id=commit_id,
856 branch=branch_ref, bookmark=bookmark_ref,
857 branch=branch_ref, bookmark=bookmark_ref,
857 translator=self.request.translate)
858 translator=self.request.translate)
858 except CommitDoesNotExistError as e:
859 except CommitDoesNotExistError as e:
859 log.exception(e)
860 log.exception(e)
860 h.flash(_('Commit does not exist'), 'error')
861 h.flash(_('Commit does not exist'), 'error')
861 raise HTTPFound(
862 raise HTTPFound(
862 h.route_path('pullrequest_new', repo_name=source_repo.repo_name))
863 h.route_path('pullrequest_new', repo_name=source_repo.repo_name))
863
864
864 default_target_repo = source_repo
865 default_target_repo = source_repo
865
866
866 if source_repo.parent and c.has_origin_repo_read_perm:
867 if source_repo.parent and c.has_origin_repo_read_perm:
867 parent_vcs_obj = source_repo.parent.scm_instance()
868 parent_vcs_obj = source_repo.parent.scm_instance()
868 if parent_vcs_obj and not parent_vcs_obj.is_empty():
869 if parent_vcs_obj and not parent_vcs_obj.is_empty():
869 # change default if we have a parent repo
870 # change default if we have a parent repo
870 default_target_repo = source_repo.parent
871 default_target_repo = source_repo.parent
871
872
872 target_repo_data = PullRequestModel().generate_repo_data(
873 target_repo_data = PullRequestModel().generate_repo_data(
873 default_target_repo, translator=self.request.translate)
874 default_target_repo, translator=self.request.translate)
874
875
875 selected_source_ref = source_repo_data['refs']['selected_ref']
876 selected_source_ref = source_repo_data['refs']['selected_ref']
876 title_source_ref = ''
877 title_source_ref = ''
877 if selected_source_ref:
878 if selected_source_ref:
878 title_source_ref = selected_source_ref.split(':', 2)[1]
879 title_source_ref = selected_source_ref.split(':', 2)[1]
879 c.default_title = PullRequestModel().generate_pullrequest_title(
880 c.default_title = PullRequestModel().generate_pullrequest_title(
880 source=source_repo.repo_name,
881 source=source_repo.repo_name,
881 source_ref=title_source_ref,
882 source_ref=title_source_ref,
882 target=default_target_repo.repo_name
883 target=default_target_repo.repo_name
883 )
884 )
884
885
885 c.default_repo_data = {
886 c.default_repo_data = {
886 'source_repo_name': source_repo.repo_name,
887 'source_repo_name': source_repo.repo_name,
887 'source_refs_json': json.dumps(source_repo_data),
888 'source_refs_json': json.dumps(source_repo_data),
888 'target_repo_name': default_target_repo.repo_name,
889 'target_repo_name': default_target_repo.repo_name,
889 'target_refs_json': json.dumps(target_repo_data),
890 'target_refs_json': json.dumps(target_repo_data),
890 }
891 }
891 c.default_source_ref = selected_source_ref
892 c.default_source_ref = selected_source_ref
892
893
893 return self._get_template_context(c)
894 return self._get_template_context(c)
894
895
895 @LoginRequired()
896 @LoginRequired()
896 @NotAnonymous()
897 @NotAnonymous()
897 @HasRepoPermissionAnyDecorator(
898 @HasRepoPermissionAnyDecorator(
898 'repository.read', 'repository.write', 'repository.admin')
899 'repository.read', 'repository.write', 'repository.admin')
899 @view_config(
900 @view_config(
900 route_name='pullrequest_repo_refs', request_method='GET',
901 route_name='pullrequest_repo_refs', request_method='GET',
901 renderer='json_ext', xhr=True)
902 renderer='json_ext', xhr=True)
902 def pull_request_repo_refs(self):
903 def pull_request_repo_refs(self):
903 self.load_default_context()
904 self.load_default_context()
904 target_repo_name = self.request.matchdict['target_repo_name']
905 target_repo_name = self.request.matchdict['target_repo_name']
905 repo = Repository.get_by_repo_name(target_repo_name)
906 repo = Repository.get_by_repo_name(target_repo_name)
906 if not repo:
907 if not repo:
907 raise HTTPNotFound()
908 raise HTTPNotFound()
908
909
909 target_perm = HasRepoPermissionAny(
910 target_perm = HasRepoPermissionAny(
910 'repository.read', 'repository.write', 'repository.admin')(
911 'repository.read', 'repository.write', 'repository.admin')(
911 target_repo_name)
912 target_repo_name)
912 if not target_perm:
913 if not target_perm:
913 raise HTTPNotFound()
914 raise HTTPNotFound()
914
915
915 return PullRequestModel().generate_repo_data(
916 return PullRequestModel().generate_repo_data(
916 repo, translator=self.request.translate)
917 repo, translator=self.request.translate)
917
918
918 @LoginRequired()
919 @LoginRequired()
919 @NotAnonymous()
920 @NotAnonymous()
920 @HasRepoPermissionAnyDecorator(
921 @HasRepoPermissionAnyDecorator(
921 'repository.read', 'repository.write', 'repository.admin')
922 'repository.read', 'repository.write', 'repository.admin')
922 @view_config(
923 @view_config(
923 route_name='pullrequest_repo_targets', request_method='GET',
924 route_name='pullrequest_repo_targets', request_method='GET',
924 renderer='json_ext', xhr=True)
925 renderer='json_ext', xhr=True)
925 def pullrequest_repo_targets(self):
926 def pullrequest_repo_targets(self):
926 _ = self.request.translate
927 _ = self.request.translate
927 filter_query = self.request.GET.get('query')
928 filter_query = self.request.GET.get('query')
928
929
929 # get the parents
930 # get the parents
930 parent_target_repos = []
931 parent_target_repos = []
931 if self.db_repo.parent:
932 if self.db_repo.parent:
932 parents_query = Repository.query() \
933 parents_query = Repository.query() \
933 .order_by(func.length(Repository.repo_name)) \
934 .order_by(func.length(Repository.repo_name)) \
934 .filter(Repository.fork_id == self.db_repo.parent.repo_id)
935 .filter(Repository.fork_id == self.db_repo.parent.repo_id)
935
936
936 if filter_query:
937 if filter_query:
937 ilike_expression = u'%{}%'.format(safe_unicode(filter_query))
938 ilike_expression = u'%{}%'.format(safe_unicode(filter_query))
938 parents_query = parents_query.filter(
939 parents_query = parents_query.filter(
939 Repository.repo_name.ilike(ilike_expression))
940 Repository.repo_name.ilike(ilike_expression))
940 parents = parents_query.limit(20).all()
941 parents = parents_query.limit(20).all()
941
942
942 for parent in parents:
943 for parent in parents:
943 parent_vcs_obj = parent.scm_instance()
944 parent_vcs_obj = parent.scm_instance()
944 if parent_vcs_obj and not parent_vcs_obj.is_empty():
945 if parent_vcs_obj and not parent_vcs_obj.is_empty():
945 parent_target_repos.append(parent)
946 parent_target_repos.append(parent)
946
947
947 # get other forks, and repo itself
948 # get other forks, and repo itself
948 query = Repository.query() \
949 query = Repository.query() \
949 .order_by(func.length(Repository.repo_name)) \
950 .order_by(func.length(Repository.repo_name)) \
950 .filter(
951 .filter(
951 or_(Repository.repo_id == self.db_repo.repo_id, # repo itself
952 or_(Repository.repo_id == self.db_repo.repo_id, # repo itself
952 Repository.fork_id == self.db_repo.repo_id) # forks of this repo
953 Repository.fork_id == self.db_repo.repo_id) # forks of this repo
953 ) \
954 ) \
954 .filter(~Repository.repo_id.in_([x.repo_id for x in parent_target_repos]))
955 .filter(~Repository.repo_id.in_([x.repo_id for x in parent_target_repos]))
955
956
956 if filter_query:
957 if filter_query:
957 ilike_expression = u'%{}%'.format(safe_unicode(filter_query))
958 ilike_expression = u'%{}%'.format(safe_unicode(filter_query))
958 query = query.filter(Repository.repo_name.ilike(ilike_expression))
959 query = query.filter(Repository.repo_name.ilike(ilike_expression))
959
960
960 limit = max(20 - len(parent_target_repos), 5) # not less then 5
961 limit = max(20 - len(parent_target_repos), 5) # not less then 5
961 target_repos = query.limit(limit).all()
962 target_repos = query.limit(limit).all()
962
963
963 all_target_repos = target_repos + parent_target_repos
964 all_target_repos = target_repos + parent_target_repos
964
965
965 repos = []
966 repos = []
966 # This checks permissions to the repositories
967 # This checks permissions to the repositories
967 for obj in ScmModel().get_repos(all_target_repos):
968 for obj in ScmModel().get_repos(all_target_repos):
968 repos.append({
969 repos.append({
969 'id': obj['name'],
970 'id': obj['name'],
970 'text': obj['name'],
971 'text': obj['name'],
971 'type': 'repo',
972 'type': 'repo',
972 'repo_id': obj['dbrepo']['repo_id'],
973 'repo_id': obj['dbrepo']['repo_id'],
973 'repo_type': obj['dbrepo']['repo_type'],
974 'repo_type': obj['dbrepo']['repo_type'],
974 'private': obj['dbrepo']['private'],
975 'private': obj['dbrepo']['private'],
975
976
976 })
977 })
977
978
978 data = {
979 data = {
979 'more': False,
980 'more': False,
980 'results': [{
981 'results': [{
981 'text': _('Repositories'),
982 'text': _('Repositories'),
982 'children': repos
983 'children': repos
983 }] if repos else []
984 }] if repos else []
984 }
985 }
985 return data
986 return data
986
987
987 @classmethod
988 @classmethod
988 def get_comment_ids(cls, post_data):
989 def get_comment_ids(cls, post_data):
989 return filter(lambda e: e > 0, map(safe_int, aslist(post_data.get('comments'), ',')))
990 return filter(lambda e: e > 0, map(safe_int, aslist(post_data.get('comments'), ',')))
990
991
991 @LoginRequired()
992 @LoginRequired()
992 @NotAnonymous()
993 @NotAnonymous()
993 @HasRepoPermissionAnyDecorator(
994 @HasRepoPermissionAnyDecorator(
994 'repository.read', 'repository.write', 'repository.admin')
995 'repository.read', 'repository.write', 'repository.admin')
995 @view_config(
996 @view_config(
996 route_name='pullrequest_comments', request_method='POST',
997 route_name='pullrequest_comments', request_method='POST',
997 renderer='string_html', xhr=True)
998 renderer='string_html', xhr=True)
998 def pullrequest_comments(self):
999 def pullrequest_comments(self):
999 self.load_default_context()
1000 self.load_default_context()
1000
1001
1001 pull_request = PullRequest.get_or_404(
1002 pull_request = PullRequest.get_or_404(
1002 self.request.matchdict['pull_request_id'])
1003 self.request.matchdict['pull_request_id'])
1003 pull_request_id = pull_request.pull_request_id
1004 pull_request_id = pull_request.pull_request_id
1004 version = self.request.GET.get('version')
1005 version = self.request.GET.get('version')
1005
1006
1006 _render = self.request.get_partial_renderer(
1007 _render = self.request.get_partial_renderer(
1007 'rhodecode:templates/base/sidebar.mako')
1008 'rhodecode:templates/base/sidebar.mako')
1008 c = _render.get_call_context()
1009 c = _render.get_call_context()
1009
1010
1010 (pull_request_latest,
1011 (pull_request_latest,
1011 pull_request_at_ver,
1012 pull_request_at_ver,
1012 pull_request_display_obj,
1013 pull_request_display_obj,
1013 at_version) = PullRequestModel().get_pr_version(
1014 at_version) = PullRequestModel().get_pr_version(
1014 pull_request_id, version=version)
1015 pull_request_id, version=version)
1015 versions = pull_request_display_obj.versions()
1016 versions = pull_request_display_obj.versions()
1016 latest_ver = PullRequest.get_pr_display_object(pull_request_latest, pull_request_latest)
1017 latest_ver = PullRequest.get_pr_display_object(pull_request_latest, pull_request_latest)
1017 c.versions = versions + [latest_ver]
1018 c.versions = versions + [latest_ver]
1018
1019
1019 c.at_version = at_version
1020 c.at_version = at_version
1020 c.at_version_num = (at_version
1021 c.at_version_num = (at_version
1021 if at_version and at_version != PullRequest.LATEST_VER
1022 if at_version and at_version != PullRequest.LATEST_VER
1022 else None)
1023 else None)
1023
1024
1024 self.register_comments_vars(c, pull_request_latest, versions, include_drafts=False)
1025 self.register_comments_vars(c, pull_request_latest, versions, include_drafts=False)
1025 all_comments = c.inline_comments_flat + c.comments
1026 all_comments = c.inline_comments_flat + c.comments
1026
1027
1027 existing_ids = self.get_comment_ids(self.request.POST)
1028 existing_ids = self.get_comment_ids(self.request.POST)
1028 return _render('comments_table', all_comments, len(all_comments),
1029 return _render('comments_table', all_comments, len(all_comments),
1029 existing_ids=existing_ids)
1030 existing_ids=existing_ids)
1030
1031
1031 @LoginRequired()
1032 @LoginRequired()
1032 @NotAnonymous()
1033 @NotAnonymous()
1033 @HasRepoPermissionAnyDecorator(
1034 @HasRepoPermissionAnyDecorator(
1034 'repository.read', 'repository.write', 'repository.admin')
1035 'repository.read', 'repository.write', 'repository.admin')
1035 @view_config(
1036 @view_config(
1036 route_name='pullrequest_todos', request_method='POST',
1037 route_name='pullrequest_todos', request_method='POST',
1037 renderer='string_html', xhr=True)
1038 renderer='string_html', xhr=True)
1038 def pullrequest_todos(self):
1039 def pullrequest_todos(self):
1039 self.load_default_context()
1040 self.load_default_context()
1040
1041
1041 pull_request = PullRequest.get_or_404(
1042 pull_request = PullRequest.get_or_404(
1042 self.request.matchdict['pull_request_id'])
1043 self.request.matchdict['pull_request_id'])
1043 pull_request_id = pull_request.pull_request_id
1044 pull_request_id = pull_request.pull_request_id
1044 version = self.request.GET.get('version')
1045 version = self.request.GET.get('version')
1045
1046
1046 _render = self.request.get_partial_renderer(
1047 _render = self.request.get_partial_renderer(
1047 'rhodecode:templates/base/sidebar.mako')
1048 'rhodecode:templates/base/sidebar.mako')
1048 c = _render.get_call_context()
1049 c = _render.get_call_context()
1049 (pull_request_latest,
1050 (pull_request_latest,
1050 pull_request_at_ver,
1051 pull_request_at_ver,
1051 pull_request_display_obj,
1052 pull_request_display_obj,
1052 at_version) = PullRequestModel().get_pr_version(
1053 at_version) = PullRequestModel().get_pr_version(
1053 pull_request_id, version=version)
1054 pull_request_id, version=version)
1054 versions = pull_request_display_obj.versions()
1055 versions = pull_request_display_obj.versions()
1055 latest_ver = PullRequest.get_pr_display_object(pull_request_latest, pull_request_latest)
1056 latest_ver = PullRequest.get_pr_display_object(pull_request_latest, pull_request_latest)
1056 c.versions = versions + [latest_ver]
1057 c.versions = versions + [latest_ver]
1057
1058
1058 c.at_version = at_version
1059 c.at_version = at_version
1059 c.at_version_num = (at_version
1060 c.at_version_num = (at_version
1060 if at_version and at_version != PullRequest.LATEST_VER
1061 if at_version and at_version != PullRequest.LATEST_VER
1061 else None)
1062 else None)
1062
1063
1063 c.unresolved_comments = CommentsModel() \
1064 c.unresolved_comments = CommentsModel() \
1064 .get_pull_request_unresolved_todos(pull_request, include_drafts=False)
1065 .get_pull_request_unresolved_todos(pull_request, include_drafts=False)
1065 c.resolved_comments = CommentsModel() \
1066 c.resolved_comments = CommentsModel() \
1066 .get_pull_request_resolved_todos(pull_request, include_drafts=False)
1067 .get_pull_request_resolved_todos(pull_request, include_drafts=False)
1067
1068
1068 all_comments = c.unresolved_comments + c.resolved_comments
1069 all_comments = c.unresolved_comments + c.resolved_comments
1069 existing_ids = self.get_comment_ids(self.request.POST)
1070 existing_ids = self.get_comment_ids(self.request.POST)
1070 return _render('comments_table', all_comments, len(c.unresolved_comments),
1071 return _render('comments_table', all_comments, len(c.unresolved_comments),
1071 todo_comments=True, existing_ids=existing_ids)
1072 todo_comments=True, existing_ids=existing_ids)
1072
1073
1073 @LoginRequired()
1074 @LoginRequired()
1074 @NotAnonymous()
1075 @NotAnonymous()
1075 @HasRepoPermissionAnyDecorator(
1076 @HasRepoPermissionAnyDecorator(
1076 'repository.read', 'repository.write', 'repository.admin')
1077 'repository.read', 'repository.write', 'repository.admin')
1077 @CSRFRequired()
1078 @CSRFRequired()
1078 @view_config(
1079 @view_config(
1079 route_name='pullrequest_create', request_method='POST',
1080 route_name='pullrequest_create', request_method='POST',
1080 renderer=None)
1081 renderer=None)
1081 def pull_request_create(self):
1082 def pull_request_create(self):
1082 _ = self.request.translate
1083 _ = self.request.translate
1083 self.assure_not_empty_repo()
1084 self.assure_not_empty_repo()
1084 self.load_default_context()
1085 self.load_default_context()
1085
1086
1086 controls = peppercorn.parse(self.request.POST.items())
1087 controls = peppercorn.parse(self.request.POST.items())
1087
1088
1088 try:
1089 try:
1089 form = PullRequestForm(
1090 form = PullRequestForm(
1090 self.request.translate, self.db_repo.repo_id)()
1091 self.request.translate, self.db_repo.repo_id)()
1091 _form = form.to_python(controls)
1092 _form = form.to_python(controls)
1092 except formencode.Invalid as errors:
1093 except formencode.Invalid as errors:
1093 if errors.error_dict.get('revisions'):
1094 if errors.error_dict.get('revisions'):
1094 msg = 'Revisions: %s' % errors.error_dict['revisions']
1095 msg = 'Revisions: %s' % errors.error_dict['revisions']
1095 elif errors.error_dict.get('pullrequest_title'):
1096 elif errors.error_dict.get('pullrequest_title'):
1096 msg = errors.error_dict.get('pullrequest_title')
1097 msg = errors.error_dict.get('pullrequest_title')
1097 else:
1098 else:
1098 msg = _('Error creating pull request: {}').format(errors)
1099 msg = _('Error creating pull request: {}').format(errors)
1099 log.exception(msg)
1100 log.exception(msg)
1100 h.flash(msg, 'error')
1101 h.flash(msg, 'error')
1101
1102
1102 # would rather just go back to form ...
1103 # would rather just go back to form ...
1103 raise HTTPFound(
1104 raise HTTPFound(
1104 h.route_path('pullrequest_new', repo_name=self.db_repo_name))
1105 h.route_path('pullrequest_new', repo_name=self.db_repo_name))
1105
1106
1106 source_repo = _form['source_repo']
1107 source_repo = _form['source_repo']
1107 source_ref = _form['source_ref']
1108 source_ref = _form['source_ref']
1108 target_repo = _form['target_repo']
1109 target_repo = _form['target_repo']
1109 target_ref = _form['target_ref']
1110 target_ref = _form['target_ref']
1110 commit_ids = _form['revisions'][::-1]
1111 commit_ids = _form['revisions'][::-1]
1111 common_ancestor_id = _form['common_ancestor']
1112 common_ancestor_id = _form['common_ancestor']
1112
1113
1113 # find the ancestor for this pr
1114 # find the ancestor for this pr
1114 source_db_repo = Repository.get_by_repo_name(_form['source_repo'])
1115 source_db_repo = Repository.get_by_repo_name(_form['source_repo'])
1115 target_db_repo = Repository.get_by_repo_name(_form['target_repo'])
1116 target_db_repo = Repository.get_by_repo_name(_form['target_repo'])
1116
1117
1117 if not (source_db_repo or target_db_repo):
1118 if not (source_db_repo or target_db_repo):
1118 h.flash(_('source_repo or target repo not found'), category='error')
1119 h.flash(_('source_repo or target repo not found'), category='error')
1119 raise HTTPFound(
1120 raise HTTPFound(
1120 h.route_path('pullrequest_new', repo_name=self.db_repo_name))
1121 h.route_path('pullrequest_new', repo_name=self.db_repo_name))
1121
1122
1122 # re-check permissions again here
1123 # re-check permissions again here
1123 # source_repo we must have read permissions
1124 # source_repo we must have read permissions
1124
1125
1125 source_perm = HasRepoPermissionAny(
1126 source_perm = HasRepoPermissionAny(
1126 'repository.read', 'repository.write', 'repository.admin')(
1127 'repository.read', 'repository.write', 'repository.admin')(
1127 source_db_repo.repo_name)
1128 source_db_repo.repo_name)
1128 if not source_perm:
1129 if not source_perm:
1129 msg = _('Not Enough permissions to source repo `{}`.'.format(
1130 msg = _('Not Enough permissions to source repo `{}`.'.format(
1130 source_db_repo.repo_name))
1131 source_db_repo.repo_name))
1131 h.flash(msg, category='error')
1132 h.flash(msg, category='error')
1132 # copy the args back to redirect
1133 # copy the args back to redirect
1133 org_query = self.request.GET.mixed()
1134 org_query = self.request.GET.mixed()
1134 raise HTTPFound(
1135 raise HTTPFound(
1135 h.route_path('pullrequest_new', repo_name=self.db_repo_name,
1136 h.route_path('pullrequest_new', repo_name=self.db_repo_name,
1136 _query=org_query))
1137 _query=org_query))
1137
1138
1138 # target repo we must have read permissions, and also later on
1139 # target repo we must have read permissions, and also later on
1139 # we want to check branch permissions here
1140 # we want to check branch permissions here
1140 target_perm = HasRepoPermissionAny(
1141 target_perm = HasRepoPermissionAny(
1141 'repository.read', 'repository.write', 'repository.admin')(
1142 'repository.read', 'repository.write', 'repository.admin')(
1142 target_db_repo.repo_name)
1143 target_db_repo.repo_name)
1143 if not target_perm:
1144 if not target_perm:
1144 msg = _('Not Enough permissions to target repo `{}`.'.format(
1145 msg = _('Not Enough permissions to target repo `{}`.'.format(
1145 target_db_repo.repo_name))
1146 target_db_repo.repo_name))
1146 h.flash(msg, category='error')
1147 h.flash(msg, category='error')
1147 # copy the args back to redirect
1148 # copy the args back to redirect
1148 org_query = self.request.GET.mixed()
1149 org_query = self.request.GET.mixed()
1149 raise HTTPFound(
1150 raise HTTPFound(
1150 h.route_path('pullrequest_new', repo_name=self.db_repo_name,
1151 h.route_path('pullrequest_new', repo_name=self.db_repo_name,
1151 _query=org_query))
1152 _query=org_query))
1152
1153
1153 source_scm = source_db_repo.scm_instance()
1154 source_scm = source_db_repo.scm_instance()
1154 target_scm = target_db_repo.scm_instance()
1155 target_scm = target_db_repo.scm_instance()
1155
1156
1156 source_ref_obj = unicode_to_reference(source_ref)
1157 source_ref_obj = unicode_to_reference(source_ref)
1157 target_ref_obj = unicode_to_reference(target_ref)
1158 target_ref_obj = unicode_to_reference(target_ref)
1158
1159
1159 source_commit = source_scm.get_commit(source_ref_obj.commit_id)
1160 source_commit = source_scm.get_commit(source_ref_obj.commit_id)
1160 target_commit = target_scm.get_commit(target_ref_obj.commit_id)
1161 target_commit = target_scm.get_commit(target_ref_obj.commit_id)
1161
1162
1162 ancestor = source_scm.get_common_ancestor(
1163 ancestor = source_scm.get_common_ancestor(
1163 source_commit.raw_id, target_commit.raw_id, target_scm)
1164 source_commit.raw_id, target_commit.raw_id, target_scm)
1164
1165
1165 # recalculate target ref based on ancestor
1166 # recalculate target ref based on ancestor
1166 target_ref = ':'.join((target_ref_obj.type, target_ref_obj.name, ancestor))
1167 target_ref = ':'.join((target_ref_obj.type, target_ref_obj.name, ancestor))
1167
1168
1168 get_default_reviewers_data, validate_default_reviewers, validate_observers = \
1169 get_default_reviewers_data, validate_default_reviewers, validate_observers = \
1169 PullRequestModel().get_reviewer_functions()
1170 PullRequestModel().get_reviewer_functions()
1170
1171
1171 # recalculate reviewers logic, to make sure we can validate this
1172 # recalculate reviewers logic, to make sure we can validate this
1172 reviewer_rules = get_default_reviewers_data(
1173 reviewer_rules = get_default_reviewers_data(
1173 self._rhodecode_db_user,
1174 self._rhodecode_db_user,
1174 source_db_repo,
1175 source_db_repo,
1175 source_ref_obj,
1176 source_ref_obj,
1176 target_db_repo,
1177 target_db_repo,
1177 target_ref_obj,
1178 target_ref_obj,
1178 include_diff_info=False)
1179 include_diff_info=False)
1179
1180
1180 reviewers = validate_default_reviewers(_form['review_members'], reviewer_rules)
1181 reviewers = validate_default_reviewers(_form['review_members'], reviewer_rules)
1181 observers = validate_observers(_form['observer_members'], reviewer_rules)
1182 observers = validate_observers(_form['observer_members'], reviewer_rules)
1182
1183
1183 pullrequest_title = _form['pullrequest_title']
1184 pullrequest_title = _form['pullrequest_title']
1184 title_source_ref = source_ref_obj.name
1185 title_source_ref = source_ref_obj.name
1185 if not pullrequest_title:
1186 if not pullrequest_title:
1186 pullrequest_title = PullRequestModel().generate_pullrequest_title(
1187 pullrequest_title = PullRequestModel().generate_pullrequest_title(
1187 source=source_repo,
1188 source=source_repo,
1188 source_ref=title_source_ref,
1189 source_ref=title_source_ref,
1189 target=target_repo
1190 target=target_repo
1190 )
1191 )
1191
1192
1192 description = _form['pullrequest_desc']
1193 description = _form['pullrequest_desc']
1193 description_renderer = _form['description_renderer']
1194 description_renderer = _form['description_renderer']
1194
1195
1195 try:
1196 try:
1196 pull_request = PullRequestModel().create(
1197 pull_request = PullRequestModel().create(
1197 created_by=self._rhodecode_user.user_id,
1198 created_by=self._rhodecode_user.user_id,
1198 source_repo=source_repo,
1199 source_repo=source_repo,
1199 source_ref=source_ref,
1200 source_ref=source_ref,
1200 target_repo=target_repo,
1201 target_repo=target_repo,
1201 target_ref=target_ref,
1202 target_ref=target_ref,
1202 revisions=commit_ids,
1203 revisions=commit_ids,
1203 common_ancestor_id=common_ancestor_id,
1204 common_ancestor_id=common_ancestor_id,
1204 reviewers=reviewers,
1205 reviewers=reviewers,
1205 observers=observers,
1206 observers=observers,
1206 title=pullrequest_title,
1207 title=pullrequest_title,
1207 description=description,
1208 description=description,
1208 description_renderer=description_renderer,
1209 description_renderer=description_renderer,
1209 reviewer_data=reviewer_rules,
1210 reviewer_data=reviewer_rules,
1210 auth_user=self._rhodecode_user
1211 auth_user=self._rhodecode_user
1211 )
1212 )
1212 Session().commit()
1213 Session().commit()
1213
1214
1214 h.flash(_('Successfully opened new pull request'),
1215 h.flash(_('Successfully opened new pull request'),
1215 category='success')
1216 category='success')
1216 except Exception:
1217 except Exception:
1217 msg = _('Error occurred during creation of this pull request.')
1218 msg = _('Error occurred during creation of this pull request.')
1218 log.exception(msg)
1219 log.exception(msg)
1219 h.flash(msg, category='error')
1220 h.flash(msg, category='error')
1220
1221
1221 # copy the args back to redirect
1222 # copy the args back to redirect
1222 org_query = self.request.GET.mixed()
1223 org_query = self.request.GET.mixed()
1223 raise HTTPFound(
1224 raise HTTPFound(
1224 h.route_path('pullrequest_new', repo_name=self.db_repo_name,
1225 h.route_path('pullrequest_new', repo_name=self.db_repo_name,
1225 _query=org_query))
1226 _query=org_query))
1226
1227
1227 raise HTTPFound(
1228 raise HTTPFound(
1228 h.route_path('pullrequest_show', repo_name=target_repo,
1229 h.route_path('pullrequest_show', repo_name=target_repo,
1229 pull_request_id=pull_request.pull_request_id))
1230 pull_request_id=pull_request.pull_request_id))
1230
1231
1231 @LoginRequired()
1232 @LoginRequired()
1232 @NotAnonymous()
1233 @NotAnonymous()
1233 @HasRepoPermissionAnyDecorator(
1234 @HasRepoPermissionAnyDecorator(
1234 'repository.read', 'repository.write', 'repository.admin')
1235 'repository.read', 'repository.write', 'repository.admin')
1235 @CSRFRequired()
1236 @CSRFRequired()
1236 @view_config(
1237 @view_config(
1237 route_name='pullrequest_update', request_method='POST',
1238 route_name='pullrequest_update', request_method='POST',
1238 renderer='json_ext')
1239 renderer='json_ext')
1239 def pull_request_update(self):
1240 def pull_request_update(self):
1240 pull_request = PullRequest.get_or_404(
1241 pull_request = PullRequest.get_or_404(
1241 self.request.matchdict['pull_request_id'])
1242 self.request.matchdict['pull_request_id'])
1242 _ = self.request.translate
1243 _ = self.request.translate
1243
1244
1244 c = self.load_default_context()
1245 c = self.load_default_context()
1245 redirect_url = None
1246 redirect_url = None
1246
1247
1247 if pull_request.is_closed():
1248 if pull_request.is_closed():
1248 log.debug('update: forbidden because pull request is closed')
1249 log.debug('update: forbidden because pull request is closed')
1249 msg = _(u'Cannot update closed pull requests.')
1250 msg = _(u'Cannot update closed pull requests.')
1250 h.flash(msg, category='error')
1251 h.flash(msg, category='error')
1251 return {'response': True,
1252 return {'response': True,
1252 'redirect_url': redirect_url}
1253 'redirect_url': redirect_url}
1253
1254
1254 is_state_changing = pull_request.is_state_changing()
1255 is_state_changing = pull_request.is_state_changing()
1255 c.pr_broadcast_channel = channelstream.pr_channel(pull_request)
1256 c.pr_broadcast_channel = channelstream.pr_channel(pull_request)
1256
1257
1257 # only owner or admin can update it
1258 # only owner or admin can update it
1258 allowed_to_update = PullRequestModel().check_user_update(
1259 allowed_to_update = PullRequestModel().check_user_update(
1259 pull_request, self._rhodecode_user)
1260 pull_request, self._rhodecode_user)
1260
1261
1261 if allowed_to_update:
1262 if allowed_to_update:
1262 controls = peppercorn.parse(self.request.POST.items())
1263 controls = peppercorn.parse(self.request.POST.items())
1263 force_refresh = str2bool(self.request.POST.get('force_refresh'))
1264 force_refresh = str2bool(self.request.POST.get('force_refresh'))
1264
1265
1265 if 'review_members' in controls:
1266 if 'review_members' in controls:
1266 self._update_reviewers(
1267 self._update_reviewers(
1267 c,
1268 c,
1268 pull_request, controls['review_members'],
1269 pull_request, controls['review_members'],
1269 pull_request.reviewer_data,
1270 pull_request.reviewer_data,
1270 PullRequestReviewers.ROLE_REVIEWER)
1271 PullRequestReviewers.ROLE_REVIEWER)
1271 elif 'observer_members' in controls:
1272 elif 'observer_members' in controls:
1272 self._update_reviewers(
1273 self._update_reviewers(
1273 c,
1274 c,
1274 pull_request, controls['observer_members'],
1275 pull_request, controls['observer_members'],
1275 pull_request.reviewer_data,
1276 pull_request.reviewer_data,
1276 PullRequestReviewers.ROLE_OBSERVER)
1277 PullRequestReviewers.ROLE_OBSERVER)
1277 elif str2bool(self.request.POST.get('update_commits', 'false')):
1278 elif str2bool(self.request.POST.get('update_commits', 'false')):
1278 if is_state_changing:
1279 if is_state_changing:
1279 log.debug('commits update: forbidden because pull request is in state %s',
1280 log.debug('commits update: forbidden because pull request is in state %s',
1280 pull_request.pull_request_state)
1281 pull_request.pull_request_state)
1281 msg = _(u'Cannot update pull requests commits in state other than `{}`. '
1282 msg = _(u'Cannot update pull requests commits in state other than `{}`. '
1282 u'Current state is: `{}`').format(
1283 u'Current state is: `{}`').format(
1283 PullRequest.STATE_CREATED, pull_request.pull_request_state)
1284 PullRequest.STATE_CREATED, pull_request.pull_request_state)
1284 h.flash(msg, category='error')
1285 h.flash(msg, category='error')
1285 return {'response': True,
1286 return {'response': True,
1286 'redirect_url': redirect_url}
1287 'redirect_url': redirect_url}
1287
1288
1288 self._update_commits(c, pull_request)
1289 self._update_commits(c, pull_request)
1289 if force_refresh:
1290 if force_refresh:
1290 redirect_url = h.route_path(
1291 redirect_url = h.route_path(
1291 'pullrequest_show', repo_name=self.db_repo_name,
1292 'pullrequest_show', repo_name=self.db_repo_name,
1292 pull_request_id=pull_request.pull_request_id,
1293 pull_request_id=pull_request.pull_request_id,
1293 _query={"force_refresh": 1})
1294 _query={"force_refresh": 1})
1294 elif str2bool(self.request.POST.get('edit_pull_request', 'false')):
1295 elif str2bool(self.request.POST.get('edit_pull_request', 'false')):
1295 self._edit_pull_request(pull_request)
1296 self._edit_pull_request(pull_request)
1296 else:
1297 else:
1297 log.error('Unhandled update data.')
1298 log.error('Unhandled update data.')
1298 raise HTTPBadRequest()
1299 raise HTTPBadRequest()
1299
1300
1300 return {'response': True,
1301 return {'response': True,
1301 'redirect_url': redirect_url}
1302 'redirect_url': redirect_url}
1302 raise HTTPForbidden()
1303 raise HTTPForbidden()
1303
1304
1304 def _edit_pull_request(self, pull_request):
1305 def _edit_pull_request(self, pull_request):
1305 """
1306 """
1306 Edit title and description
1307 Edit title and description
1307 """
1308 """
1308 _ = self.request.translate
1309 _ = self.request.translate
1309
1310
1310 try:
1311 try:
1311 PullRequestModel().edit(
1312 PullRequestModel().edit(
1312 pull_request,
1313 pull_request,
1313 self.request.POST.get('title'),
1314 self.request.POST.get('title'),
1314 self.request.POST.get('description'),
1315 self.request.POST.get('description'),
1315 self.request.POST.get('description_renderer'),
1316 self.request.POST.get('description_renderer'),
1316 self._rhodecode_user)
1317 self._rhodecode_user)
1317 except ValueError:
1318 except ValueError:
1318 msg = _(u'Cannot update closed pull requests.')
1319 msg = _(u'Cannot update closed pull requests.')
1319 h.flash(msg, category='error')
1320 h.flash(msg, category='error')
1320 return
1321 return
1321 else:
1322 else:
1322 Session().commit()
1323 Session().commit()
1323
1324
1324 msg = _(u'Pull request title & description updated.')
1325 msg = _(u'Pull request title & description updated.')
1325 h.flash(msg, category='success')
1326 h.flash(msg, category='success')
1326 return
1327 return
1327
1328
1328 def _update_commits(self, c, pull_request):
1329 def _update_commits(self, c, pull_request):
1329 _ = self.request.translate
1330 _ = self.request.translate
1330
1331
1331 with pull_request.set_state(PullRequest.STATE_UPDATING):
1332 with pull_request.set_state(PullRequest.STATE_UPDATING):
1332 resp = PullRequestModel().update_commits(
1333 resp = PullRequestModel().update_commits(
1333 pull_request, self._rhodecode_db_user)
1334 pull_request, self._rhodecode_db_user)
1334
1335
1335 if resp.executed:
1336 if resp.executed:
1336
1337
1337 if resp.target_changed and resp.source_changed:
1338 if resp.target_changed and resp.source_changed:
1338 changed = 'target and source repositories'
1339 changed = 'target and source repositories'
1339 elif resp.target_changed and not resp.source_changed:
1340 elif resp.target_changed and not resp.source_changed:
1340 changed = 'target repository'
1341 changed = 'target repository'
1341 elif not resp.target_changed and resp.source_changed:
1342 elif not resp.target_changed and resp.source_changed:
1342 changed = 'source repository'
1343 changed = 'source repository'
1343 else:
1344 else:
1344 changed = 'nothing'
1345 changed = 'nothing'
1345
1346
1346 msg = _(u'Pull request updated to "{source_commit_id}" with '
1347 msg = _(u'Pull request updated to "{source_commit_id}" with '
1347 u'{count_added} added, {count_removed} removed commits. '
1348 u'{count_added} added, {count_removed} removed commits. '
1348 u'Source of changes: {change_source}.')
1349 u'Source of changes: {change_source}.')
1349 msg = msg.format(
1350 msg = msg.format(
1350 source_commit_id=pull_request.source_ref_parts.commit_id,
1351 source_commit_id=pull_request.source_ref_parts.commit_id,
1351 count_added=len(resp.changes.added),
1352 count_added=len(resp.changes.added),
1352 count_removed=len(resp.changes.removed),
1353 count_removed=len(resp.changes.removed),
1353 change_source=changed)
1354 change_source=changed)
1354 h.flash(msg, category='success')
1355 h.flash(msg, category='success')
1355 channelstream.pr_update_channelstream_push(
1356 channelstream.pr_update_channelstream_push(
1356 self.request, c.pr_broadcast_channel, self._rhodecode_user, msg)
1357 self.request, c.pr_broadcast_channel, self._rhodecode_user, msg)
1357 else:
1358 else:
1358 msg = PullRequestModel.UPDATE_STATUS_MESSAGES[resp.reason]
1359 msg = PullRequestModel.UPDATE_STATUS_MESSAGES[resp.reason]
1359 warning_reasons = [
1360 warning_reasons = [
1360 UpdateFailureReason.NO_CHANGE,
1361 UpdateFailureReason.NO_CHANGE,
1361 UpdateFailureReason.WRONG_REF_TYPE,
1362 UpdateFailureReason.WRONG_REF_TYPE,
1362 ]
1363 ]
1363 category = 'warning' if resp.reason in warning_reasons else 'error'
1364 category = 'warning' if resp.reason in warning_reasons else 'error'
1364 h.flash(msg, category=category)
1365 h.flash(msg, category=category)
1365
1366
1366 def _update_reviewers(self, c, pull_request, review_members, reviewer_rules, role):
1367 def _update_reviewers(self, c, pull_request, review_members, reviewer_rules, role):
1367 _ = self.request.translate
1368 _ = self.request.translate
1368
1369
1369 get_default_reviewers_data, validate_default_reviewers, validate_observers = \
1370 get_default_reviewers_data, validate_default_reviewers, validate_observers = \
1370 PullRequestModel().get_reviewer_functions()
1371 PullRequestModel().get_reviewer_functions()
1371
1372
1372 if role == PullRequestReviewers.ROLE_REVIEWER:
1373 if role == PullRequestReviewers.ROLE_REVIEWER:
1373 try:
1374 try:
1374 reviewers = validate_default_reviewers(review_members, reviewer_rules)
1375 reviewers = validate_default_reviewers(review_members, reviewer_rules)
1375 except ValueError as e:
1376 except ValueError as e:
1376 log.error('Reviewers Validation: {}'.format(e))
1377 log.error('Reviewers Validation: {}'.format(e))
1377 h.flash(e, category='error')
1378 h.flash(e, category='error')
1378 return
1379 return
1379
1380
1380 old_calculated_status = pull_request.calculated_review_status()
1381 old_calculated_status = pull_request.calculated_review_status()
1381 PullRequestModel().update_reviewers(
1382 PullRequestModel().update_reviewers(
1382 pull_request, reviewers, self._rhodecode_db_user)
1383 pull_request, reviewers, self._rhodecode_db_user)
1383
1384
1384 Session().commit()
1385 Session().commit()
1385
1386
1386 msg = _('Pull request reviewers updated.')
1387 msg = _('Pull request reviewers updated.')
1387 h.flash(msg, category='success')
1388 h.flash(msg, category='success')
1388 channelstream.pr_update_channelstream_push(
1389 channelstream.pr_update_channelstream_push(
1389 self.request, c.pr_broadcast_channel, self._rhodecode_user, msg)
1390 self.request, c.pr_broadcast_channel, self._rhodecode_user, msg)
1390
1391
1391 # trigger status changed if change in reviewers changes the status
1392 # trigger status changed if change in reviewers changes the status
1392 calculated_status = pull_request.calculated_review_status()
1393 calculated_status = pull_request.calculated_review_status()
1393 if old_calculated_status != calculated_status:
1394 if old_calculated_status != calculated_status:
1394 PullRequestModel().trigger_pull_request_hook(
1395 PullRequestModel().trigger_pull_request_hook(
1395 pull_request, self._rhodecode_user, 'review_status_change',
1396 pull_request, self._rhodecode_user, 'review_status_change',
1396 data={'status': calculated_status})
1397 data={'status': calculated_status})
1397
1398
1398 elif role == PullRequestReviewers.ROLE_OBSERVER:
1399 elif role == PullRequestReviewers.ROLE_OBSERVER:
1399 try:
1400 try:
1400 observers = validate_observers(review_members, reviewer_rules)
1401 observers = validate_observers(review_members, reviewer_rules)
1401 except ValueError as e:
1402 except ValueError as e:
1402 log.error('Observers Validation: {}'.format(e))
1403 log.error('Observers Validation: {}'.format(e))
1403 h.flash(e, category='error')
1404 h.flash(e, category='error')
1404 return
1405 return
1405
1406
1406 PullRequestModel().update_observers(
1407 PullRequestModel().update_observers(
1407 pull_request, observers, self._rhodecode_db_user)
1408 pull_request, observers, self._rhodecode_db_user)
1408
1409
1409 Session().commit()
1410 Session().commit()
1410 msg = _('Pull request observers updated.')
1411 msg = _('Pull request observers updated.')
1411 h.flash(msg, category='success')
1412 h.flash(msg, category='success')
1412 channelstream.pr_update_channelstream_push(
1413 channelstream.pr_update_channelstream_push(
1413 self.request, c.pr_broadcast_channel, self._rhodecode_user, msg)
1414 self.request, c.pr_broadcast_channel, self._rhodecode_user, msg)
1414
1415
1415 @LoginRequired()
1416 @LoginRequired()
1416 @NotAnonymous()
1417 @NotAnonymous()
1417 @HasRepoPermissionAnyDecorator(
1418 @HasRepoPermissionAnyDecorator(
1418 'repository.read', 'repository.write', 'repository.admin')
1419 'repository.read', 'repository.write', 'repository.admin')
1419 @CSRFRequired()
1420 @CSRFRequired()
1420 @view_config(
1421 @view_config(
1421 route_name='pullrequest_merge', request_method='POST',
1422 route_name='pullrequest_merge', request_method='POST',
1422 renderer='json_ext')
1423 renderer='json_ext')
1423 def pull_request_merge(self):
1424 def pull_request_merge(self):
1424 """
1425 """
1425 Merge will perform a server-side merge of the specified
1426 Merge will perform a server-side merge of the specified
1426 pull request, if the pull request is approved and mergeable.
1427 pull request, if the pull request is approved and mergeable.
1427 After successful merging, the pull request is automatically
1428 After successful merging, the pull request is automatically
1428 closed, with a relevant comment.
1429 closed, with a relevant comment.
1429 """
1430 """
1430 pull_request = PullRequest.get_or_404(
1431 pull_request = PullRequest.get_or_404(
1431 self.request.matchdict['pull_request_id'])
1432 self.request.matchdict['pull_request_id'])
1432 _ = self.request.translate
1433 _ = self.request.translate
1433
1434
1434 if pull_request.is_state_changing():
1435 if pull_request.is_state_changing():
1435 log.debug('show: forbidden because pull request is in state %s',
1436 log.debug('show: forbidden because pull request is in state %s',
1436 pull_request.pull_request_state)
1437 pull_request.pull_request_state)
1437 msg = _(u'Cannot merge pull requests in state other than `{}`. '
1438 msg = _(u'Cannot merge pull requests in state other than `{}`. '
1438 u'Current state is: `{}`').format(PullRequest.STATE_CREATED,
1439 u'Current state is: `{}`').format(PullRequest.STATE_CREATED,
1439 pull_request.pull_request_state)
1440 pull_request.pull_request_state)
1440 h.flash(msg, category='error')
1441 h.flash(msg, category='error')
1441 raise HTTPFound(
1442 raise HTTPFound(
1442 h.route_path('pullrequest_show',
1443 h.route_path('pullrequest_show',
1443 repo_name=pull_request.target_repo.repo_name,
1444 repo_name=pull_request.target_repo.repo_name,
1444 pull_request_id=pull_request.pull_request_id))
1445 pull_request_id=pull_request.pull_request_id))
1445
1446
1446 self.load_default_context()
1447 self.load_default_context()
1447
1448
1448 with pull_request.set_state(PullRequest.STATE_UPDATING):
1449 with pull_request.set_state(PullRequest.STATE_UPDATING):
1449 check = MergeCheck.validate(
1450 check = MergeCheck.validate(
1450 pull_request, auth_user=self._rhodecode_user,
1451 pull_request, auth_user=self._rhodecode_user,
1451 translator=self.request.translate)
1452 translator=self.request.translate)
1452 merge_possible = not check.failed
1453 merge_possible = not check.failed
1453
1454
1454 for err_type, error_msg in check.errors:
1455 for err_type, error_msg in check.errors:
1455 h.flash(error_msg, category=err_type)
1456 h.flash(error_msg, category=err_type)
1456
1457
1457 if merge_possible:
1458 if merge_possible:
1458 log.debug("Pre-conditions checked, trying to merge.")
1459 log.debug("Pre-conditions checked, trying to merge.")
1459 extras = vcs_operation_context(
1460 extras = vcs_operation_context(
1460 self.request.environ, repo_name=pull_request.target_repo.repo_name,
1461 self.request.environ, repo_name=pull_request.target_repo.repo_name,
1461 username=self._rhodecode_db_user.username, action='push',
1462 username=self._rhodecode_db_user.username, action='push',
1462 scm=pull_request.target_repo.repo_type)
1463 scm=pull_request.target_repo.repo_type)
1463 with pull_request.set_state(PullRequest.STATE_UPDATING):
1464 with pull_request.set_state(PullRequest.STATE_UPDATING):
1464 self._merge_pull_request(
1465 self._merge_pull_request(
1465 pull_request, self._rhodecode_db_user, extras)
1466 pull_request, self._rhodecode_db_user, extras)
1466 else:
1467 else:
1467 log.debug("Pre-conditions failed, NOT merging.")
1468 log.debug("Pre-conditions failed, NOT merging.")
1468
1469
1469 raise HTTPFound(
1470 raise HTTPFound(
1470 h.route_path('pullrequest_show',
1471 h.route_path('pullrequest_show',
1471 repo_name=pull_request.target_repo.repo_name,
1472 repo_name=pull_request.target_repo.repo_name,
1472 pull_request_id=pull_request.pull_request_id))
1473 pull_request_id=pull_request.pull_request_id))
1473
1474
1474 def _merge_pull_request(self, pull_request, user, extras):
1475 def _merge_pull_request(self, pull_request, user, extras):
1475 _ = self.request.translate
1476 _ = self.request.translate
1476 merge_resp = PullRequestModel().merge_repo(pull_request, user, extras=extras)
1477 merge_resp = PullRequestModel().merge_repo(pull_request, user, extras=extras)
1477
1478
1478 if merge_resp.executed:
1479 if merge_resp.executed:
1479 log.debug("The merge was successful, closing the pull request.")
1480 log.debug("The merge was successful, closing the pull request.")
1480 PullRequestModel().close_pull_request(
1481 PullRequestModel().close_pull_request(
1481 pull_request.pull_request_id, user)
1482 pull_request.pull_request_id, user)
1482 Session().commit()
1483 Session().commit()
1483 msg = _('Pull request was successfully merged and closed.')
1484 msg = _('Pull request was successfully merged and closed.')
1484 h.flash(msg, category='success')
1485 h.flash(msg, category='success')
1485 else:
1486 else:
1486 log.debug(
1487 log.debug(
1487 "The merge was not successful. Merge response: %s", merge_resp)
1488 "The merge was not successful. Merge response: %s", merge_resp)
1488 msg = merge_resp.merge_status_message
1489 msg = merge_resp.merge_status_message
1489 h.flash(msg, category='error')
1490 h.flash(msg, category='error')
1490
1491
1491 @LoginRequired()
1492 @LoginRequired()
1492 @NotAnonymous()
1493 @NotAnonymous()
1493 @HasRepoPermissionAnyDecorator(
1494 @HasRepoPermissionAnyDecorator(
1494 'repository.read', 'repository.write', 'repository.admin')
1495 'repository.read', 'repository.write', 'repository.admin')
1495 @CSRFRequired()
1496 @CSRFRequired()
1496 @view_config(
1497 @view_config(
1497 route_name='pullrequest_delete', request_method='POST',
1498 route_name='pullrequest_delete', request_method='POST',
1498 renderer='json_ext')
1499 renderer='json_ext')
1499 def pull_request_delete(self):
1500 def pull_request_delete(self):
1500 _ = self.request.translate
1501 _ = self.request.translate
1501
1502
1502 pull_request = PullRequest.get_or_404(
1503 pull_request = PullRequest.get_or_404(
1503 self.request.matchdict['pull_request_id'])
1504 self.request.matchdict['pull_request_id'])
1504 self.load_default_context()
1505 self.load_default_context()
1505
1506
1506 pr_closed = pull_request.is_closed()
1507 pr_closed = pull_request.is_closed()
1507 allowed_to_delete = PullRequestModel().check_user_delete(
1508 allowed_to_delete = PullRequestModel().check_user_delete(
1508 pull_request, self._rhodecode_user) and not pr_closed
1509 pull_request, self._rhodecode_user) and not pr_closed
1509
1510
1510 # only owner can delete it !
1511 # only owner can delete it !
1511 if allowed_to_delete:
1512 if allowed_to_delete:
1512 PullRequestModel().delete(pull_request, self._rhodecode_user)
1513 PullRequestModel().delete(pull_request, self._rhodecode_user)
1513 Session().commit()
1514 Session().commit()
1514 h.flash(_('Successfully deleted pull request'),
1515 h.flash(_('Successfully deleted pull request'),
1515 category='success')
1516 category='success')
1516 raise HTTPFound(h.route_path('pullrequest_show_all',
1517 raise HTTPFound(h.route_path('pullrequest_show_all',
1517 repo_name=self.db_repo_name))
1518 repo_name=self.db_repo_name))
1518
1519
1519 log.warning('user %s tried to delete pull request without access',
1520 log.warning('user %s tried to delete pull request without access',
1520 self._rhodecode_user)
1521 self._rhodecode_user)
1521 raise HTTPNotFound()
1522 raise HTTPNotFound()
1522
1523
1523 def _pull_request_comments_create(self, pull_request, comments):
1524 def _pull_request_comments_create(self, pull_request, comments):
1524 _ = self.request.translate
1525 _ = self.request.translate
1525 data = {}
1526 data = {}
1526 if not comments:
1527 if not comments:
1527 return
1528 return
1528 pull_request_id = pull_request.pull_request_id
1529 pull_request_id = pull_request.pull_request_id
1529
1530
1530 all_drafts = len([x for x in comments if str2bool(x['is_draft'])]) == len(comments)
1531 all_drafts = len([x for x in comments if str2bool(x['is_draft'])]) == len(comments)
1531
1532
1532 for entry in comments:
1533 for entry in comments:
1533 c = self.load_default_context()
1534 c = self.load_default_context()
1534 comment_type = entry['comment_type']
1535 comment_type = entry['comment_type']
1535 text = entry['text']
1536 text = entry['text']
1536 status = entry['status']
1537 status = entry['status']
1537 is_draft = str2bool(entry['is_draft'])
1538 is_draft = str2bool(entry['is_draft'])
1538 resolves_comment_id = entry['resolves_comment_id']
1539 resolves_comment_id = entry['resolves_comment_id']
1539 close_pull_request = entry['close_pull_request']
1540 close_pull_request = entry['close_pull_request']
1540 f_path = entry['f_path']
1541 f_path = entry['f_path']
1541 line_no = entry['line']
1542 line_no = entry['line']
1542 target_elem_id = 'file-{}'.format(h.safeid(h.safe_unicode(f_path)))
1543 target_elem_id = 'file-{}'.format(h.safeid(h.safe_unicode(f_path)))
1543
1544
1544 # the logic here should work like following, if we submit close
1545 # the logic here should work like following, if we submit close
1545 # pr comment, use `close_pull_request_with_comment` function
1546 # pr comment, use `close_pull_request_with_comment` function
1546 # else handle regular comment logic
1547 # else handle regular comment logic
1547
1548
1548 if close_pull_request:
1549 if close_pull_request:
1549 # only owner or admin or person with write permissions
1550 # only owner or admin or person with write permissions
1550 allowed_to_close = PullRequestModel().check_user_update(
1551 allowed_to_close = PullRequestModel().check_user_update(
1551 pull_request, self._rhodecode_user)
1552 pull_request, self._rhodecode_user)
1552 if not allowed_to_close:
1553 if not allowed_to_close:
1553 log.debug('comment: forbidden because not allowed to close '
1554 log.debug('comment: forbidden because not allowed to close '
1554 'pull request %s', pull_request_id)
1555 'pull request %s', pull_request_id)
1555 raise HTTPForbidden()
1556 raise HTTPForbidden()
1556
1557
1557 # This also triggers `review_status_change`
1558 # This also triggers `review_status_change`
1558 comment, status = PullRequestModel().close_pull_request_with_comment(
1559 comment, status = PullRequestModel().close_pull_request_with_comment(
1559 pull_request, self._rhodecode_user, self.db_repo, message=text,
1560 pull_request, self._rhodecode_user, self.db_repo, message=text,
1560 auth_user=self._rhodecode_user)
1561 auth_user=self._rhodecode_user)
1561 Session().flush()
1562 Session().flush()
1562 is_inline = comment.is_inline
1563 is_inline = comment.is_inline
1563
1564
1564 PullRequestModel().trigger_pull_request_hook(
1565 PullRequestModel().trigger_pull_request_hook(
1565 pull_request, self._rhodecode_user, 'comment',
1566 pull_request, self._rhodecode_user, 'comment',
1566 data={'comment': comment})
1567 data={'comment': comment})
1567
1568
1568 else:
1569 else:
1569 # regular comment case, could be inline, or one with status.
1570 # regular comment case, could be inline, or one with status.
1570 # for that one we check also permissions
1571 # for that one we check also permissions
1571 # Additionally ENSURE if somehow draft is sent we're then unable to change status
1572 # Additionally ENSURE if somehow draft is sent we're then unable to change status
1572 allowed_to_change_status = PullRequestModel().check_user_change_status(
1573 allowed_to_change_status = PullRequestModel().check_user_change_status(
1573 pull_request, self._rhodecode_user) and not is_draft
1574 pull_request, self._rhodecode_user) and not is_draft
1574
1575
1575 if status and allowed_to_change_status:
1576 if status and allowed_to_change_status:
1576 message = (_('Status change %(transition_icon)s %(status)s')
1577 message = (_('Status change %(transition_icon)s %(status)s')
1577 % {'transition_icon': '>',
1578 % {'transition_icon': '>',
1578 'status': ChangesetStatus.get_status_lbl(status)})
1579 'status': ChangesetStatus.get_status_lbl(status)})
1579 text = text or message
1580 text = text or message
1580
1581
1581 comment = CommentsModel().create(
1582 comment = CommentsModel().create(
1582 text=text,
1583 text=text,
1583 repo=self.db_repo.repo_id,
1584 repo=self.db_repo.repo_id,
1584 user=self._rhodecode_user.user_id,
1585 user=self._rhodecode_user.user_id,
1585 pull_request=pull_request,
1586 pull_request=pull_request,
1586 f_path=f_path,
1587 f_path=f_path,
1587 line_no=line_no,
1588 line_no=line_no,
1588 status_change=(ChangesetStatus.get_status_lbl(status)
1589 status_change=(ChangesetStatus.get_status_lbl(status)
1589 if status and allowed_to_change_status else None),
1590 if status and allowed_to_change_status else None),
1590 status_change_type=(status
1591 status_change_type=(status
1591 if status and allowed_to_change_status else None),
1592 if status and allowed_to_change_status else None),
1592 comment_type=comment_type,
1593 comment_type=comment_type,
1593 is_draft=is_draft,
1594 is_draft=is_draft,
1594 resolves_comment_id=resolves_comment_id,
1595 resolves_comment_id=resolves_comment_id,
1595 auth_user=self._rhodecode_user,
1596 auth_user=self._rhodecode_user,
1596 send_email=not is_draft, # skip notification for draft comments
1597 send_email=not is_draft, # skip notification for draft comments
1597 )
1598 )
1598 is_inline = comment.is_inline
1599 is_inline = comment.is_inline
1599
1600
1600 if allowed_to_change_status:
1601 if allowed_to_change_status:
1601 # calculate old status before we change it
1602 # calculate old status before we change it
1602 old_calculated_status = pull_request.calculated_review_status()
1603 old_calculated_status = pull_request.calculated_review_status()
1603
1604
1604 # get status if set !
1605 # get status if set !
1605 if status:
1606 if status:
1606 ChangesetStatusModel().set_status(
1607 ChangesetStatusModel().set_status(
1607 self.db_repo.repo_id,
1608 self.db_repo.repo_id,
1608 status,
1609 status,
1609 self._rhodecode_user.user_id,
1610 self._rhodecode_user.user_id,
1610 comment,
1611 comment,
1611 pull_request=pull_request
1612 pull_request=pull_request
1612 )
1613 )
1613
1614
1614 Session().flush()
1615 Session().flush()
1615 # this is somehow required to get access to some relationship
1616 # this is somehow required to get access to some relationship
1616 # loaded on comment
1617 # loaded on comment
1617 Session().refresh(comment)
1618 Session().refresh(comment)
1618
1619
1619 # skip notifications for drafts
1620 # skip notifications for drafts
1620 if not is_draft:
1621 if not is_draft:
1621 PullRequestModel().trigger_pull_request_hook(
1622 PullRequestModel().trigger_pull_request_hook(
1622 pull_request, self._rhodecode_user, 'comment',
1623 pull_request, self._rhodecode_user, 'comment',
1623 data={'comment': comment})
1624 data={'comment': comment})
1624
1625
1625 # we now calculate the status of pull request, and based on that
1626 # we now calculate the status of pull request, and based on that
1626 # calculation we set the commits status
1627 # calculation we set the commits status
1627 calculated_status = pull_request.calculated_review_status()
1628 calculated_status = pull_request.calculated_review_status()
1628 if old_calculated_status != calculated_status:
1629 if old_calculated_status != calculated_status:
1629 PullRequestModel().trigger_pull_request_hook(
1630 PullRequestModel().trigger_pull_request_hook(
1630 pull_request, self._rhodecode_user, 'review_status_change',
1631 pull_request, self._rhodecode_user, 'review_status_change',
1631 data={'status': calculated_status})
1632 data={'status': calculated_status})
1632
1633
1633 comment_id = comment.comment_id
1634 comment_id = comment.comment_id
1634 data[comment_id] = {
1635 data[comment_id] = {
1635 'target_id': target_elem_id
1636 'target_id': target_elem_id
1636 }
1637 }
1637 Session().flush()
1638 Session().flush()
1638
1639
1639 c.co = comment
1640 c.co = comment
1640 c.at_version_num = None
1641 c.at_version_num = None
1641 c.is_new = True
1642 c.is_new = True
1642 rendered_comment = render(
1643 rendered_comment = render(
1643 'rhodecode:templates/changeset/changeset_comment_block.mako',
1644 'rhodecode:templates/changeset/changeset_comment_block.mako',
1644 self._get_template_context(c), self.request)
1645 self._get_template_context(c), self.request)
1645
1646
1646 data[comment_id].update(comment.get_dict())
1647 data[comment_id].update(comment.get_dict())
1647 data[comment_id].update({'rendered_text': rendered_comment})
1648 data[comment_id].update({'rendered_text': rendered_comment})
1648
1649
1649 Session().commit()
1650 Session().commit()
1650
1651
1651 # skip channelstream for draft comments
1652 # skip channelstream for draft comments
1652 if not all_drafts:
1653 if not all_drafts:
1653 comment_broadcast_channel = channelstream.comment_channel(
1654 comment_broadcast_channel = channelstream.comment_channel(
1654 self.db_repo_name, pull_request_obj=pull_request)
1655 self.db_repo_name, pull_request_obj=pull_request)
1655
1656
1656 comment_data = data
1657 comment_data = data
1657 posted_comment_type = 'inline' if is_inline else 'general'
1658 posted_comment_type = 'inline' if is_inline else 'general'
1658 if len(data) == 1:
1659 if len(data) == 1:
1659 msg = _('posted {} new {} comment').format(len(data), posted_comment_type)
1660 msg = _('posted {} new {} comment').format(len(data), posted_comment_type)
1660 else:
1661 else:
1661 msg = _('posted {} new {} comments').format(len(data), posted_comment_type)
1662 msg = _('posted {} new {} comments').format(len(data), posted_comment_type)
1662
1663
1663 channelstream.comment_channelstream_push(
1664 channelstream.comment_channelstream_push(
1664 self.request, comment_broadcast_channel, self._rhodecode_user, msg,
1665 self.request, comment_broadcast_channel, self._rhodecode_user, msg,
1665 comment_data=comment_data)
1666 comment_data=comment_data)
1666
1667
1667 return data
1668 return data
1668
1669
1669 @LoginRequired()
1670 @LoginRequired()
1670 @NotAnonymous()
1671 @NotAnonymous()
1671 @HasRepoPermissionAnyDecorator(
1672 @HasRepoPermissionAnyDecorator(
1672 'repository.read', 'repository.write', 'repository.admin')
1673 'repository.read', 'repository.write', 'repository.admin')
1673 @CSRFRequired()
1674 @CSRFRequired()
1674 @view_config(
1675 @view_config(
1675 route_name='pullrequest_comment_create', request_method='POST',
1676 route_name='pullrequest_comment_create', request_method='POST',
1676 renderer='json_ext')
1677 renderer='json_ext')
1677 def pull_request_comment_create(self):
1678 def pull_request_comment_create(self):
1678 _ = self.request.translate
1679 _ = self.request.translate
1679
1680
1680 pull_request = PullRequest.get_or_404(self.request.matchdict['pull_request_id'])
1681 pull_request = PullRequest.get_or_404(self.request.matchdict['pull_request_id'])
1681
1682
1682 if pull_request.is_closed():
1683 if pull_request.is_closed():
1683 log.debug('comment: forbidden because pull request is closed')
1684 log.debug('comment: forbidden because pull request is closed')
1684 raise HTTPForbidden()
1685 raise HTTPForbidden()
1685
1686
1686 allowed_to_comment = PullRequestModel().check_user_comment(
1687 allowed_to_comment = PullRequestModel().check_user_comment(
1687 pull_request, self._rhodecode_user)
1688 pull_request, self._rhodecode_user)
1688 if not allowed_to_comment:
1689 if not allowed_to_comment:
1689 log.debug('comment: forbidden because pull request is from forbidden repo')
1690 log.debug('comment: forbidden because pull request is from forbidden repo')
1690 raise HTTPForbidden()
1691 raise HTTPForbidden()
1691
1692
1692 comment_data = {
1693 comment_data = {
1693 'comment_type': self.request.POST.get('comment_type'),
1694 'comment_type': self.request.POST.get('comment_type'),
1694 'text': self.request.POST.get('text'),
1695 'text': self.request.POST.get('text'),
1695 'status': self.request.POST.get('changeset_status', None),
1696 'status': self.request.POST.get('changeset_status', None),
1696 'is_draft': self.request.POST.get('draft'),
1697 'is_draft': self.request.POST.get('draft'),
1697 'resolves_comment_id': self.request.POST.get('resolves_comment_id', None),
1698 'resolves_comment_id': self.request.POST.get('resolves_comment_id', None),
1698 'close_pull_request': self.request.POST.get('close_pull_request'),
1699 'close_pull_request': self.request.POST.get('close_pull_request'),
1699 'f_path': self.request.POST.get('f_path'),
1700 'f_path': self.request.POST.get('f_path'),
1700 'line': self.request.POST.get('line'),
1701 'line': self.request.POST.get('line'),
1701 }
1702 }
1702 data = self._pull_request_comments_create(pull_request, [comment_data])
1703 data = self._pull_request_comments_create(pull_request, [comment_data])
1703
1704
1704 return data
1705 return data
1705
1706
1706 @LoginRequired()
1707 @LoginRequired()
1707 @NotAnonymous()
1708 @NotAnonymous()
1708 @HasRepoPermissionAnyDecorator(
1709 @HasRepoPermissionAnyDecorator(
1709 'repository.read', 'repository.write', 'repository.admin')
1710 'repository.read', 'repository.write', 'repository.admin')
1710 @CSRFRequired()
1711 @CSRFRequired()
1711 @view_config(
1712 @view_config(
1712 route_name='pullrequest_comment_delete', request_method='POST',
1713 route_name='pullrequest_comment_delete', request_method='POST',
1713 renderer='json_ext')
1714 renderer='json_ext')
1714 def pull_request_comment_delete(self):
1715 def pull_request_comment_delete(self):
1715 pull_request = PullRequest.get_or_404(
1716 pull_request = PullRequest.get_or_404(
1716 self.request.matchdict['pull_request_id'])
1717 self.request.matchdict['pull_request_id'])
1717
1718
1718 comment = ChangesetComment.get_or_404(
1719 comment = ChangesetComment.get_or_404(
1719 self.request.matchdict['comment_id'])
1720 self.request.matchdict['comment_id'])
1720 comment_id = comment.comment_id
1721 comment_id = comment.comment_id
1721
1722
1722 if comment.immutable:
1723 if comment.immutable:
1723 # don't allow deleting comments that are immutable
1724 # don't allow deleting comments that are immutable
1724 raise HTTPForbidden()
1725 raise HTTPForbidden()
1725
1726
1726 if pull_request.is_closed():
1727 if pull_request.is_closed():
1727 log.debug('comment: forbidden because pull request is closed')
1728 log.debug('comment: forbidden because pull request is closed')
1728 raise HTTPForbidden()
1729 raise HTTPForbidden()
1729
1730
1730 if not comment:
1731 if not comment:
1731 log.debug('Comment with id:%s not found, skipping', comment_id)
1732 log.debug('Comment with id:%s not found, skipping', comment_id)
1732 # comment already deleted in another call probably
1733 # comment already deleted in another call probably
1733 return True
1734 return True
1734
1735
1735 if comment.pull_request.is_closed():
1736 if comment.pull_request.is_closed():
1736 # don't allow deleting comments on closed pull request
1737 # don't allow deleting comments on closed pull request
1737 raise HTTPForbidden()
1738 raise HTTPForbidden()
1738
1739
1739 is_repo_admin = h.HasRepoPermissionAny('repository.admin')(self.db_repo_name)
1740 is_repo_admin = h.HasRepoPermissionAny('repository.admin')(self.db_repo_name)
1740 super_admin = h.HasPermissionAny('hg.admin')()
1741 super_admin = h.HasPermissionAny('hg.admin')()
1741 comment_owner = comment.author.user_id == self._rhodecode_user.user_id
1742 comment_owner = comment.author.user_id == self._rhodecode_user.user_id
1742 is_repo_comment = comment.repo.repo_name == self.db_repo_name
1743 is_repo_comment = comment.repo.repo_name == self.db_repo_name
1743 comment_repo_admin = is_repo_admin and is_repo_comment
1744 comment_repo_admin = is_repo_admin and is_repo_comment
1744
1745
1745 if super_admin or comment_owner or comment_repo_admin:
1746 if super_admin or comment_owner or comment_repo_admin:
1746 old_calculated_status = comment.pull_request.calculated_review_status()
1747 old_calculated_status = comment.pull_request.calculated_review_status()
1747 CommentsModel().delete(comment=comment, auth_user=self._rhodecode_user)
1748 CommentsModel().delete(comment=comment, auth_user=self._rhodecode_user)
1748 Session().commit()
1749 Session().commit()
1749 calculated_status = comment.pull_request.calculated_review_status()
1750 calculated_status = comment.pull_request.calculated_review_status()
1750 if old_calculated_status != calculated_status:
1751 if old_calculated_status != calculated_status:
1751 PullRequestModel().trigger_pull_request_hook(
1752 PullRequestModel().trigger_pull_request_hook(
1752 comment.pull_request, self._rhodecode_user, 'review_status_change',
1753 comment.pull_request, self._rhodecode_user, 'review_status_change',
1753 data={'status': calculated_status})
1754 data={'status': calculated_status})
1754 return True
1755 return True
1755 else:
1756 else:
1756 log.warning('No permissions for user %s to delete comment_id: %s',
1757 log.warning('No permissions for user %s to delete comment_id: %s',
1757 self._rhodecode_db_user, comment_id)
1758 self._rhodecode_db_user, comment_id)
1758 raise HTTPNotFound()
1759 raise HTTPNotFound()
1759
1760
1760 @LoginRequired()
1761 @LoginRequired()
1761 @NotAnonymous()
1762 @NotAnonymous()
1762 @HasRepoPermissionAnyDecorator(
1763 @HasRepoPermissionAnyDecorator(
1763 'repository.read', 'repository.write', 'repository.admin')
1764 'repository.read', 'repository.write', 'repository.admin')
1764 @CSRFRequired()
1765 @CSRFRequired()
1765 @view_config(
1766 @view_config(
1766 route_name='pullrequest_comment_edit', request_method='POST',
1767 route_name='pullrequest_comment_edit', request_method='POST',
1767 renderer='json_ext')
1768 renderer='json_ext')
1768 def pull_request_comment_edit(self):
1769 def pull_request_comment_edit(self):
1769 self.load_default_context()
1770 self.load_default_context()
1770
1771
1771 pull_request = PullRequest.get_or_404(
1772 pull_request = PullRequest.get_or_404(
1772 self.request.matchdict['pull_request_id']
1773 self.request.matchdict['pull_request_id']
1773 )
1774 )
1774 comment = ChangesetComment.get_or_404(
1775 comment = ChangesetComment.get_or_404(
1775 self.request.matchdict['comment_id']
1776 self.request.matchdict['comment_id']
1776 )
1777 )
1777 comment_id = comment.comment_id
1778 comment_id = comment.comment_id
1778
1779
1779 if comment.immutable:
1780 if comment.immutable:
1780 # don't allow deleting comments that are immutable
1781 # don't allow deleting comments that are immutable
1781 raise HTTPForbidden()
1782 raise HTTPForbidden()
1782
1783
1783 if pull_request.is_closed():
1784 if pull_request.is_closed():
1784 log.debug('comment: forbidden because pull request is closed')
1785 log.debug('comment: forbidden because pull request is closed')
1785 raise HTTPForbidden()
1786 raise HTTPForbidden()
1786
1787
1787 if comment.pull_request.is_closed():
1788 if comment.pull_request.is_closed():
1788 # don't allow deleting comments on closed pull request
1789 # don't allow deleting comments on closed pull request
1789 raise HTTPForbidden()
1790 raise HTTPForbidden()
1790
1791
1791 is_repo_admin = h.HasRepoPermissionAny('repository.admin')(self.db_repo_name)
1792 is_repo_admin = h.HasRepoPermissionAny('repository.admin')(self.db_repo_name)
1792 super_admin = h.HasPermissionAny('hg.admin')()
1793 super_admin = h.HasPermissionAny('hg.admin')()
1793 comment_owner = comment.author.user_id == self._rhodecode_user.user_id
1794 comment_owner = comment.author.user_id == self._rhodecode_user.user_id
1794 is_repo_comment = comment.repo.repo_name == self.db_repo_name
1795 is_repo_comment = comment.repo.repo_name == self.db_repo_name
1795 comment_repo_admin = is_repo_admin and is_repo_comment
1796 comment_repo_admin = is_repo_admin and is_repo_comment
1796
1797
1797 if super_admin or comment_owner or comment_repo_admin:
1798 if super_admin or comment_owner or comment_repo_admin:
1798 text = self.request.POST.get('text')
1799 text = self.request.POST.get('text')
1799 version = self.request.POST.get('version')
1800 version = self.request.POST.get('version')
1800 if text == comment.text:
1801 if text == comment.text:
1801 log.warning(
1802 log.warning(
1802 'Comment(PR): '
1803 'Comment(PR): '
1803 'Trying to create new version '
1804 'Trying to create new version '
1804 'with the same comment body {}'.format(
1805 'with the same comment body {}'.format(
1805 comment_id,
1806 comment_id,
1806 )
1807 )
1807 )
1808 )
1808 raise HTTPNotFound()
1809 raise HTTPNotFound()
1809
1810
1810 if version.isdigit():
1811 if version.isdigit():
1811 version = int(version)
1812 version = int(version)
1812 else:
1813 else:
1813 log.warning(
1814 log.warning(
1814 'Comment(PR): Wrong version type {} {} '
1815 'Comment(PR): Wrong version type {} {} '
1815 'for comment {}'.format(
1816 'for comment {}'.format(
1816 version,
1817 version,
1817 type(version),
1818 type(version),
1818 comment_id,
1819 comment_id,
1819 )
1820 )
1820 )
1821 )
1821 raise HTTPNotFound()
1822 raise HTTPNotFound()
1822
1823
1823 try:
1824 try:
1824 comment_history = CommentsModel().edit(
1825 comment_history = CommentsModel().edit(
1825 comment_id=comment_id,
1826 comment_id=comment_id,
1826 text=text,
1827 text=text,
1827 auth_user=self._rhodecode_user,
1828 auth_user=self._rhodecode_user,
1828 version=version,
1829 version=version,
1829 )
1830 )
1830 except CommentVersionMismatch:
1831 except CommentVersionMismatch:
1831 raise HTTPConflict()
1832 raise HTTPConflict()
1832
1833
1833 if not comment_history:
1834 if not comment_history:
1834 raise HTTPNotFound()
1835 raise HTTPNotFound()
1835
1836
1836 Session().commit()
1837 Session().commit()
1837 if not comment.draft:
1838 if not comment.draft:
1838 PullRequestModel().trigger_pull_request_hook(
1839 PullRequestModel().trigger_pull_request_hook(
1839 pull_request, self._rhodecode_user, 'comment_edit',
1840 pull_request, self._rhodecode_user, 'comment_edit',
1840 data={'comment': comment})
1841 data={'comment': comment})
1841
1842
1842 return {
1843 return {
1843 'comment_history_id': comment_history.comment_history_id,
1844 'comment_history_id': comment_history.comment_history_id,
1844 'comment_id': comment.comment_id,
1845 'comment_id': comment.comment_id,
1845 'comment_version': comment_history.version,
1846 'comment_version': comment_history.version,
1846 'comment_author_username': comment_history.author.username,
1847 'comment_author_username': comment_history.author.username,
1847 'comment_author_gravatar': h.gravatar_url(comment_history.author.email, 16),
1848 'comment_author_gravatar': h.gravatar_url(comment_history.author.email, 16),
1848 'comment_created_on': h.age_component(comment_history.created_on,
1849 'comment_created_on': h.age_component(comment_history.created_on,
1849 time_is_local=True),
1850 time_is_local=True),
1850 }
1851 }
1851 else:
1852 else:
1852 log.warning('No permissions for user %s to edit comment_id: %s',
1853 log.warning('No permissions for user %s to edit comment_id: %s',
1853 self._rhodecode_db_user, comment_id)
1854 self._rhodecode_db_user, comment_id)
1854 raise HTTPNotFound()
1855 raise HTTPNotFound()
@@ -1,489 +1,492 b''
1 ## DATA TABLE RE USABLE ELEMENTS
1 ## DATA TABLE RE USABLE ELEMENTS
2 ## usage:
2 ## usage:
3 ## <%namespace name="dt" file="/data_table/_dt_elements.mako"/>
3 ## <%namespace name="dt" file="/data_table/_dt_elements.mako"/>
4 <%namespace name="base" file="/base/base.mako"/>
4 <%namespace name="base" file="/base/base.mako"/>
5
5
6 <%def name="metatags_help()">
6 <%def name="metatags_help()">
7 <table>
7 <table>
8 <%
8 <%
9 example_tags = [
9 example_tags = [
10 ('state','[stable]'),
10 ('state','[stable]'),
11 ('state','[stale]'),
11 ('state','[stale]'),
12 ('state','[featured]'),
12 ('state','[featured]'),
13 ('state','[dev]'),
13 ('state','[dev]'),
14 ('state','[dead]'),
14 ('state','[dead]'),
15 ('state','[deprecated]'),
15 ('state','[deprecated]'),
16
16
17 ('label','[personal]'),
17 ('label','[personal]'),
18 ('generic','[v2.0.0]'),
18 ('generic','[v2.0.0]'),
19
19
20 ('lang','[lang =&gt; JavaScript]'),
20 ('lang','[lang =&gt; JavaScript]'),
21 ('license','[license =&gt; LicenseName]'),
21 ('license','[license =&gt; LicenseName]'),
22
22
23 ('ref','[requires =&gt; RepoName]'),
23 ('ref','[requires =&gt; RepoName]'),
24 ('ref','[recommends =&gt; GroupName]'),
24 ('ref','[recommends =&gt; GroupName]'),
25 ('ref','[conflicts =&gt; SomeName]'),
25 ('ref','[conflicts =&gt; SomeName]'),
26 ('ref','[base =&gt; SomeName]'),
26 ('ref','[base =&gt; SomeName]'),
27 ('url','[url =&gt; [linkName](https://rhodecode.com)]'),
27 ('url','[url =&gt; [linkName](https://rhodecode.com)]'),
28 ('see','[see =&gt; http://rhodecode.com]'),
28 ('see','[see =&gt; http://rhodecode.com]'),
29 ]
29 ]
30 %>
30 %>
31 % for tag_type, tag in example_tags:
31 % for tag_type, tag in example_tags:
32 <tr>
32 <tr>
33 <td>${tag|n}</td>
33 <td>${tag|n}</td>
34 <td>${h.style_metatag(tag_type, tag)|n}</td>
34 <td>${h.style_metatag(tag_type, tag)|n}</td>
35 </tr>
35 </tr>
36 % endfor
36 % endfor
37 </table>
37 </table>
38 </%def>
38 </%def>
39
39
40 <%def name="render_description(description, stylify_metatags)">
40 <%def name="render_description(description, stylify_metatags)">
41 <%
41 <%
42 tags = []
42 tags = []
43 if stylify_metatags:
43 if stylify_metatags:
44 tags, description = h.extract_metatags(description)
44 tags, description = h.extract_metatags(description)
45 %>
45 %>
46 % for tag_type, tag in tags:
46 % for tag_type, tag in tags:
47 ${h.style_metatag(tag_type, tag)|n,trim}
47 ${h.style_metatag(tag_type, tag)|n,trim}
48 % endfor
48 % endfor
49 <code style="white-space: pre-wrap">${description}</code>
49 <code style="white-space: pre-wrap">${description}</code>
50 </%def>
50 </%def>
51
51
52 ## REPOSITORY RENDERERS
52 ## REPOSITORY RENDERERS
53 <%def name="quick_menu(repo_name)">
53 <%def name="quick_menu(repo_name)">
54 <i class="icon-more"></i>
54 <i class="icon-more"></i>
55 <div class="menu_items_container hidden">
55 <div class="menu_items_container hidden">
56 <ul class="menu_items">
56 <ul class="menu_items">
57 <li>
57 <li>
58 <a title="${_('Summary')}" href="${h.route_path('repo_summary',repo_name=repo_name)}">
58 <a title="${_('Summary')}" href="${h.route_path('repo_summary',repo_name=repo_name)}">
59 <span>${_('Summary')}</span>
59 <span>${_('Summary')}</span>
60 </a>
60 </a>
61 </li>
61 </li>
62 <li>
62 <li>
63 <a title="${_('Commits')}" href="${h.route_path('repo_commits',repo_name=repo_name)}">
63 <a title="${_('Commits')}" href="${h.route_path('repo_commits',repo_name=repo_name)}">
64 <span>${_('Commits')}</span>
64 <span>${_('Commits')}</span>
65 </a>
65 </a>
66 </li>
66 </li>
67 <li>
67 <li>
68 <a title="${_('Files')}" href="${h.route_path('repo_files:default_commit',repo_name=repo_name)}">
68 <a title="${_('Files')}" href="${h.route_path('repo_files:default_commit',repo_name=repo_name)}">
69 <span>${_('Files')}</span>
69 <span>${_('Files')}</span>
70 </a>
70 </a>
71 </li>
71 </li>
72 <li>
72 <li>
73 <a title="${_('Fork')}" href="${h.route_path('repo_fork_new',repo_name=repo_name)}">
73 <a title="${_('Fork')}" href="${h.route_path('repo_fork_new',repo_name=repo_name)}">
74 <span>${_('Fork')}</span>
74 <span>${_('Fork')}</span>
75 </a>
75 </a>
76 </li>
76 </li>
77 </ul>
77 </ul>
78 </div>
78 </div>
79 </%def>
79 </%def>
80
80
81 <%def name="repo_name(name,rtype,rstate,private,archived,fork_of,short_name=False,admin=False)">
81 <%def name="repo_name(name,rtype,rstate,private,archived,fork_of,short_name=False,admin=False)">
82 <%
82 <%
83 def get_name(name,short_name=short_name):
83 def get_name(name,short_name=short_name):
84 if short_name:
84 if short_name:
85 return name.split('/')[-1]
85 return name.split('/')[-1]
86 else:
86 else:
87 return name
87 return name
88 %>
88 %>
89 <div class="${'repo_state_pending' if rstate == 'repo_state_pending' else ''} truncate">
89 <div class="${'repo_state_pending' if rstate == 'repo_state_pending' else ''} truncate">
90 ##NAME
90 ##NAME
91 <a href="${h.route_path('edit_repo',repo_name=name) if admin else h.route_path('repo_summary',repo_name=name)}">
91 <a href="${h.route_path('edit_repo',repo_name=name) if admin else h.route_path('repo_summary',repo_name=name)}">
92
92
93 ##TYPE OF REPO
93 ##TYPE OF REPO
94 %if h.is_hg(rtype):
94 %if h.is_hg(rtype):
95 <span title="${_('Mercurial repository')}"><i class="icon-hg" style="font-size: 14px;"></i></span>
95 <span title="${_('Mercurial repository')}"><i class="icon-hg" style="font-size: 14px;"></i></span>
96 %elif h.is_git(rtype):
96 %elif h.is_git(rtype):
97 <span title="${_('Git repository')}"><i class="icon-git" style="font-size: 14px"></i></span>
97 <span title="${_('Git repository')}"><i class="icon-git" style="font-size: 14px"></i></span>
98 %elif h.is_svn(rtype):
98 %elif h.is_svn(rtype):
99 <span title="${_('Subversion repository')}"><i class="icon-svn" style="font-size: 14px"></i></span>
99 <span title="${_('Subversion repository')}"><i class="icon-svn" style="font-size: 14px"></i></span>
100 %endif
100 %endif
101
101
102 ##PRIVATE/PUBLIC
102 ##PRIVATE/PUBLIC
103 %if private is True and c.visual.show_private_icon:
103 %if private is True and c.visual.show_private_icon:
104 <i class="icon-lock" title="${_('Private repository')}"></i>
104 <i class="icon-lock" title="${_('Private repository')}"></i>
105 %elif private is False and c.visual.show_public_icon:
105 %elif private is False and c.visual.show_public_icon:
106 <i class="icon-unlock-alt" title="${_('Public repository')}"></i>
106 <i class="icon-unlock-alt" title="${_('Public repository')}"></i>
107 %else:
107 %else:
108 <span></span>
108 <span></span>
109 %endif
109 %endif
110 ${get_name(name)}
110 ${get_name(name)}
111 </a>
111 </a>
112 %if fork_of:
112 %if fork_of:
113 <a href="${h.route_path('repo_summary',repo_name=fork_of.repo_name)}"><i class="icon-code-fork"></i></a>
113 <a href="${h.route_path('repo_summary',repo_name=fork_of.repo_name)}"><i class="icon-code-fork"></i></a>
114 %endif
114 %endif
115 %if rstate == 'repo_state_pending':
115 %if rstate == 'repo_state_pending':
116 <span class="creation_in_progress tooltip" title="${_('This repository is being created in a background task')}">
116 <span class="creation_in_progress tooltip" title="${_('This repository is being created in a background task')}">
117 (${_('creating...')})
117 (${_('creating...')})
118 </span>
118 </span>
119 %endif
119 %endif
120
120
121 </div>
121 </div>
122 </%def>
122 </%def>
123
123
124 <%def name="repo_desc(description, stylify_metatags)">
124 <%def name="repo_desc(description, stylify_metatags)">
125 <%
125 <%
126 tags, description = h.extract_metatags(description)
126 tags, description = h.extract_metatags(description)
127 %>
127 %>
128
128
129 <div class="truncate-wrap">
129 <div class="truncate-wrap">
130 % if stylify_metatags:
130 % if stylify_metatags:
131 % for tag_type, tag in tags:
131 % for tag_type, tag in tags:
132 ${h.style_metatag(tag_type, tag)|n}
132 ${h.style_metatag(tag_type, tag)|n}
133 % endfor
133 % endfor
134 % endif
134 % endif
135 ${description}
135 ${description}
136 </div>
136 </div>
137
137
138 </%def>
138 </%def>
139
139
140 <%def name="last_change(last_change)">
140 <%def name="last_change(last_change)">
141 ${h.age_component(last_change, time_is_local=True)}
141 ${h.age_component(last_change, time_is_local=True)}
142 </%def>
142 </%def>
143
143
144 <%def name="revision(repo_name, rev, commit_id, author, last_msg, commit_date)">
144 <%def name="revision(repo_name, rev, commit_id, author, last_msg, commit_date)">
145 <div>
145 <div>
146 %if rev >= 0:
146 %if rev >= 0:
147 <code><a class="tooltip-hovercard" data-hovercard-alt=${h.tooltip(last_msg)} data-hovercard-url="${h.route_path('hovercard_repo_commit', repo_name=repo_name, commit_id=commit_id)}" href="${h.route_path('repo_commit',repo_name=repo_name,commit_id=commit_id)}">${'r{}:{}'.format(rev,h.short_id(commit_id))}</a></code>
147 <code><a class="tooltip-hovercard" data-hovercard-alt=${h.tooltip(last_msg)} data-hovercard-url="${h.route_path('hovercard_repo_commit', repo_name=repo_name, commit_id=commit_id)}" href="${h.route_path('repo_commit',repo_name=repo_name,commit_id=commit_id)}">${'r{}:{}'.format(rev,h.short_id(commit_id))}</a></code>
148 %else:
148 %else:
149 ${_('No commits yet')}
149 ${_('No commits yet')}
150 %endif
150 %endif
151 </div>
151 </div>
152 </%def>
152 </%def>
153
153
154 <%def name="rss(name)">
154 <%def name="rss(name)">
155 %if c.rhodecode_user.username != h.DEFAULT_USER:
155 %if c.rhodecode_user.username != h.DEFAULT_USER:
156 <a title="${h.tooltip(_('Subscribe to %s rss feed')% name)}" href="${h.route_path('rss_feed_home', repo_name=name, _query=dict(auth_token=c.rhodecode_user.feed_token))}"><i class="icon-rss-sign"></i></a>
156 <a title="${h.tooltip(_('Subscribe to %s rss feed')% name)}" href="${h.route_path('rss_feed_home', repo_name=name, _query=dict(auth_token=c.rhodecode_user.feed_token))}"><i class="icon-rss-sign"></i></a>
157 %else:
157 %else:
158 <a title="${h.tooltip(_('Subscribe to %s rss feed')% name)}" href="${h.route_path('rss_feed_home', repo_name=name)}"><i class="icon-rss-sign"></i></a>
158 <a title="${h.tooltip(_('Subscribe to %s rss feed')% name)}" href="${h.route_path('rss_feed_home', repo_name=name)}"><i class="icon-rss-sign"></i></a>
159 %endif
159 %endif
160 </%def>
160 </%def>
161
161
162 <%def name="atom(name)">
162 <%def name="atom(name)">
163 %if c.rhodecode_user.username != h.DEFAULT_USER:
163 %if c.rhodecode_user.username != h.DEFAULT_USER:
164 <a title="${h.tooltip(_('Subscribe to %s atom feed')% name)}" href="${h.route_path('atom_feed_home', repo_name=name, _query=dict(auth_token=c.rhodecode_user.feed_token))}"><i class="icon-rss-sign"></i></a>
164 <a title="${h.tooltip(_('Subscribe to %s atom feed')% name)}" href="${h.route_path('atom_feed_home', repo_name=name, _query=dict(auth_token=c.rhodecode_user.feed_token))}"><i class="icon-rss-sign"></i></a>
165 %else:
165 %else:
166 <a title="${h.tooltip(_('Subscribe to %s atom feed')% name)}" href="${h.route_path('atom_feed_home', repo_name=name)}"><i class="icon-rss-sign"></i></a>
166 <a title="${h.tooltip(_('Subscribe to %s atom feed')% name)}" href="${h.route_path('atom_feed_home', repo_name=name)}"><i class="icon-rss-sign"></i></a>
167 %endif
167 %endif
168 </%def>
168 </%def>
169
169
170 <%def name="repo_actions(repo_name, super_user=True)">
170 <%def name="repo_actions(repo_name, super_user=True)">
171 <div>
171 <div>
172 <div class="grid_edit">
172 <div class="grid_edit">
173 <a href="${h.route_path('edit_repo',repo_name=repo_name)}" title="${_('Edit')}">
173 <a href="${h.route_path('edit_repo',repo_name=repo_name)}" title="${_('Edit')}">
174 Edit
174 Edit
175 </a>
175 </a>
176 </div>
176 </div>
177 <div class="grid_delete">
177 <div class="grid_delete">
178 ${h.secure_form(h.route_path('edit_repo_advanced_delete', repo_name=repo_name), request=request)}
178 ${h.secure_form(h.route_path('edit_repo_advanced_delete', repo_name=repo_name), request=request)}
179 <input class="btn btn-link btn-danger" id="remove_${repo_name}" name="remove_${repo_name}"
179 <input class="btn btn-link btn-danger" id="remove_${repo_name}" name="remove_${repo_name}"
180 onclick="submitConfirm(event, this, _gettext('Confirm to delete this repository'), _gettext('Delete'), '${repo_name}')"
180 onclick="submitConfirm(event, this, _gettext('Confirm to delete this repository'), _gettext('Delete'), '${repo_name}')"
181 type="submit" value="Delete"
181 type="submit" value="Delete"
182 >
182 >
183 ${h.end_form()}
183 ${h.end_form()}
184 </div>
184 </div>
185 </div>
185 </div>
186 </%def>
186 </%def>
187
187
188 <%def name="repo_state(repo_state)">
188 <%def name="repo_state(repo_state)">
189 <div>
189 <div>
190 %if repo_state == 'repo_state_pending':
190 %if repo_state == 'repo_state_pending':
191 <div class="tag tag4">${_('Creating')}</div>
191 <div class="tag tag4">${_('Creating')}</div>
192 %elif repo_state == 'repo_state_created':
192 %elif repo_state == 'repo_state_created':
193 <div class="tag tag1">${_('Created')}</div>
193 <div class="tag tag1">${_('Created')}</div>
194 %else:
194 %else:
195 <div class="tag alert2" title="${h.tooltip(repo_state)}">invalid</div>
195 <div class="tag alert2" title="${h.tooltip(repo_state)}">invalid</div>
196 %endif
196 %endif
197 </div>
197 </div>
198 </%def>
198 </%def>
199
199
200
200
201 ## REPO GROUP RENDERERS
201 ## REPO GROUP RENDERERS
202 <%def name="quick_repo_group_menu(repo_group_name)">
202 <%def name="quick_repo_group_menu(repo_group_name)">
203 <i class="icon-more"></i>
203 <i class="icon-more"></i>
204 <div class="menu_items_container hidden">
204 <div class="menu_items_container hidden">
205 <ul class="menu_items">
205 <ul class="menu_items">
206 <li>
206 <li>
207 <a href="${h.route_path('repo_group_home', repo_group_name=repo_group_name)}">${_('Summary')}</a>
207 <a href="${h.route_path('repo_group_home', repo_group_name=repo_group_name)}">${_('Summary')}</a>
208 </li>
208 </li>
209
209
210 </ul>
210 </ul>
211 </div>
211 </div>
212 </%def>
212 </%def>
213
213
214 <%def name="repo_group_name(repo_group_name, children_groups=None)">
214 <%def name="repo_group_name(repo_group_name, children_groups=None)">
215 <div>
215 <div>
216 <a href="${h.route_path('repo_group_home', repo_group_name=repo_group_name)}">
216 <a href="${h.route_path('repo_group_home', repo_group_name=repo_group_name)}">
217 <i class="icon-repo-group" title="${_('Repository group')}" style="font-size: 14px"></i>
217 <i class="icon-repo-group" title="${_('Repository group')}" style="font-size: 14px"></i>
218 %if children_groups:
218 %if children_groups:
219 ${h.literal(' &raquo; '.join(children_groups))}
219 ${h.literal(' &raquo; '.join(children_groups))}
220 %else:
220 %else:
221 ${repo_group_name}
221 ${repo_group_name}
222 %endif
222 %endif
223 </a>
223 </a>
224 </div>
224 </div>
225 </%def>
225 </%def>
226
226
227 <%def name="repo_group_desc(description, personal, stylify_metatags)">
227 <%def name="repo_group_desc(description, personal, stylify_metatags)">
228
228
229 <%
229 <%
230 if stylify_metatags:
230 if stylify_metatags:
231 tags, description = h.extract_metatags(description)
231 tags, description = h.extract_metatags(description)
232 %>
232 %>
233
233
234 <div class="truncate-wrap">
234 <div class="truncate-wrap">
235 % if personal:
235 % if personal:
236 <div class="metatag" tag="personal">${_('personal')}</div>
236 <div class="metatag" tag="personal">${_('personal')}</div>
237 % endif
237 % endif
238
238
239 % if stylify_metatags:
239 % if stylify_metatags:
240 % for tag_type, tag in tags:
240 % for tag_type, tag in tags:
241 ${h.style_metatag(tag_type, tag)|n}
241 ${h.style_metatag(tag_type, tag)|n}
242 % endfor
242 % endfor
243 % endif
243 % endif
244 ${description}
244 ${description}
245 </div>
245 </div>
246
246
247 </%def>
247 </%def>
248
248
249 <%def name="repo_group_actions(repo_group_id, repo_group_name, gr_count)">
249 <%def name="repo_group_actions(repo_group_id, repo_group_name, gr_count)">
250 <div class="grid_edit">
250 <div class="grid_edit">
251 <a href="${h.route_path('edit_repo_group',repo_group_name=repo_group_name)}" title="${_('Edit')}">Edit</a>
251 <a href="${h.route_path('edit_repo_group',repo_group_name=repo_group_name)}" title="${_('Edit')}">Edit</a>
252 </div>
252 </div>
253 <div class="grid_delete">
253 <div class="grid_delete">
254 ${h.secure_form(h.route_path('edit_repo_group_advanced_delete', repo_group_name=repo_group_name), request=request)}
254 ${h.secure_form(h.route_path('edit_repo_group_advanced_delete', repo_group_name=repo_group_name), request=request)}
255 <input class="btn btn-link btn-danger" id="remove_${repo_group_name}" name="remove_${repo_group_name}"
255 <input class="btn btn-link btn-danger" id="remove_${repo_group_name}" name="remove_${repo_group_name}"
256 onclick="submitConfirm(event, this, _gettext('Confirm to delete this repository group'), _gettext('Delete'), '${_ungettext('`{}` with {} repository','`{}` with {} repositories',gr_count).format(repo_group_name, gr_count)}')"
256 onclick="submitConfirm(event, this, _gettext('Confirm to delete this repository group'), _gettext('Delete'), '${_ungettext('`{}` with {} repository','`{}` with {} repositories',gr_count).format(repo_group_name, gr_count)}')"
257 type="submit" value="Delete"
257 type="submit" value="Delete"
258 >
258 >
259 ${h.end_form()}
259 ${h.end_form()}
260 </div>
260 </div>
261 </%def>
261 </%def>
262
262
263
263
264 <%def name="user_actions(user_id, username)">
264 <%def name="user_actions(user_id, username)">
265 <div class="grid_edit">
265 <div class="grid_edit">
266 <a href="${h.route_path('user_edit',user_id=user_id)}" title="${_('Edit')}">
266 <a href="${h.route_path('user_edit',user_id=user_id)}" title="${_('Edit')}">
267 ${_('Edit')}
267 ${_('Edit')}
268 </a>
268 </a>
269 </div>
269 </div>
270 <div class="grid_delete">
270 <div class="grid_delete">
271 ${h.secure_form(h.route_path('user_delete', user_id=user_id), request=request)}
271 ${h.secure_form(h.route_path('user_delete', user_id=user_id), request=request)}
272 <input class="btn btn-link btn-danger" id="remove_user_${user_id}" name="remove_user_${user_id}"
272 <input class="btn btn-link btn-danger" id="remove_user_${user_id}" name="remove_user_${user_id}"
273 onclick="submitConfirm(event, this, _gettext('Confirm to delete this user'), _gettext('Delete'), '${username}')"
273 onclick="submitConfirm(event, this, _gettext('Confirm to delete this user'), _gettext('Delete'), '${username}')"
274 type="submit" value="Delete"
274 type="submit" value="Delete"
275 >
275 >
276 ${h.end_form()}
276 ${h.end_form()}
277 </div>
277 </div>
278 </%def>
278 </%def>
279
279
280 <%def name="user_group_actions(user_group_id, user_group_name)">
280 <%def name="user_group_actions(user_group_id, user_group_name)">
281 <div class="grid_edit">
281 <div class="grid_edit">
282 <a href="${h.route_path('edit_user_group', user_group_id=user_group_id)}" title="${_('Edit')}">Edit</a>
282 <a href="${h.route_path('edit_user_group', user_group_id=user_group_id)}" title="${_('Edit')}">Edit</a>
283 </div>
283 </div>
284 <div class="grid_delete">
284 <div class="grid_delete">
285 ${h.secure_form(h.route_path('user_groups_delete', user_group_id=user_group_id), request=request)}
285 ${h.secure_form(h.route_path('user_groups_delete', user_group_id=user_group_id), request=request)}
286 <input class="btn btn-link btn-danger" id="remove_group_${user_group_id}" name="remove_group_${user_group_id}"
286 <input class="btn btn-link btn-danger" id="remove_group_${user_group_id}" name="remove_group_${user_group_id}"
287 onclick="submitConfirm(event, this, _gettext('Confirm to delete this user group'), _gettext('Delete'), '${user_group_name}')"
287 onclick="submitConfirm(event, this, _gettext('Confirm to delete this user group'), _gettext('Delete'), '${user_group_name}')"
288 type="submit" value="Delete"
288 type="submit" value="Delete"
289 >
289 >
290 ${h.end_form()}
290 ${h.end_form()}
291 </div>
291 </div>
292 </%def>
292 </%def>
293
293
294
294
295 <%def name="user_name(user_id, username)">
295 <%def name="user_name(user_id, username)">
296 ${h.link_to(h.person(username, 'username_or_name_or_email'), h.route_path('user_edit', user_id=user_id))}
296 ${h.link_to(h.person(username, 'username_or_name_or_email'), h.route_path('user_edit', user_id=user_id))}
297 </%def>
297 </%def>
298
298
299 <%def name="user_profile(username)">
299 <%def name="user_profile(username)">
300 ${base.gravatar_with_user(username, 16, tooltip=True)}
300 ${base.gravatar_with_user(username, 16, tooltip=True)}
301 </%def>
301 </%def>
302
302
303 <%def name="user_group_name(user_group_name)">
303 <%def name="user_group_name(user_group_name)">
304 <div>
304 <div>
305 <i class="icon-user-group" title="${_('User group')}"></i>
305 <i class="icon-user-group" title="${_('User group')}"></i>
306 ${h.link_to_group(user_group_name)}
306 ${h.link_to_group(user_group_name)}
307 </div>
307 </div>
308 </%def>
308 </%def>
309
309
310
310
311 ## GISTS
311 ## GISTS
312
312
313 <%def name="gist_gravatar(full_contact)">
313 <%def name="gist_gravatar(full_contact)">
314 <div class="gist_gravatar">
314 <div class="gist_gravatar">
315 ${base.gravatar(full_contact, 30)}
315 ${base.gravatar(full_contact, 30)}
316 </div>
316 </div>
317 </%def>
317 </%def>
318
318
319 <%def name="gist_access_id(gist_access_id, full_contact)">
319 <%def name="gist_access_id(gist_access_id, full_contact)">
320 <div>
320 <div>
321 <code>
321 <code>
322 <a href="${h.route_path('gist_show', gist_id=gist_access_id)}">${gist_access_id}</a>
322 <a href="${h.route_path('gist_show', gist_id=gist_access_id)}">${gist_access_id}</a>
323 </code>
323 </code>
324 </div>
324 </div>
325 </%def>
325 </%def>
326
326
327 <%def name="gist_author(full_contact, created_on, expires)">
327 <%def name="gist_author(full_contact, created_on, expires)">
328 ${base.gravatar_with_user(full_contact, 16, tooltip=True)}
328 ${base.gravatar_with_user(full_contact, 16, tooltip=True)}
329 </%def>
329 </%def>
330
330
331
331
332 <%def name="gist_created(created_on)">
332 <%def name="gist_created(created_on)">
333 <div class="created">
333 <div class="created">
334 ${h.age_component(created_on, time_is_local=True)}
334 ${h.age_component(created_on, time_is_local=True)}
335 </div>
335 </div>
336 </%def>
336 </%def>
337
337
338 <%def name="gist_expires(expires)">
338 <%def name="gist_expires(expires)">
339 <div class="created">
339 <div class="created">
340 %if expires == -1:
340 %if expires == -1:
341 ${_('never')}
341 ${_('never')}
342 %else:
342 %else:
343 ${h.age_component(h.time_to_utcdatetime(expires))}
343 ${h.age_component(h.time_to_utcdatetime(expires))}
344 %endif
344 %endif
345 </div>
345 </div>
346 </%def>
346 </%def>
347
347
348 <%def name="gist_type(gist_type)">
348 <%def name="gist_type(gist_type)">
349 %if gist_type == 'public':
349 %if gist_type == 'public':
350 <span class="tag tag-gist-public disabled">${_('Public Gist')}</span>
350 <span class="tag tag-gist-public disabled">${_('Public Gist')}</span>
351 %else:
351 %else:
352 <span class="tag tag-gist-private disabled">${_('Private Gist')}</span>
352 <span class="tag tag-gist-private disabled">${_('Private Gist')}</span>
353 %endif
353 %endif
354 </%def>
354 </%def>
355
355
356 <%def name="gist_description(gist_description)">
356 <%def name="gist_description(gist_description)">
357 ${gist_description}
357 ${gist_description}
358 </%def>
358 </%def>
359
359
360
360
361 ## PULL REQUESTS GRID RENDERERS
361 ## PULL REQUESTS GRID RENDERERS
362
362
363 <%def name="pullrequest_target_repo(repo_name)">
363 <%def name="pullrequest_target_repo(repo_name)">
364 <div class="truncate">
364 <div class="truncate">
365 ${h.link_to(repo_name,h.route_path('repo_summary',repo_name=repo_name))}
365 ${h.link_to(repo_name,h.route_path('repo_summary',repo_name=repo_name))}
366 </div>
366 </div>
367 </%def>
367 </%def>
368
368
369 <%def name="pullrequest_status(status)">
369 <%def name="pullrequest_status(status)">
370 <i class="icon-circle review-status-${status}"></i>
370 <i class="icon-circle review-status-${status}"></i>
371 </%def>
371 </%def>
372
372
373 <%def name="pullrequest_title(title, description)">
373 <%def name="pullrequest_title(title, description)">
374 ${title}
374 ${title}
375 </%def>
375 </%def>
376
376
377 <%def name="pullrequest_comments(comments_nr)">
377 <%def name="pullrequest_comments(comments_nr)">
378 <i class="icon-comment"></i> ${comments_nr}
378 <i class="icon-comment"></i> ${comments_nr}
379 </%def>
379 </%def>
380
380
381 <%def name="pullrequest_name(pull_request_id, state, is_wip, target_repo_name, short=False)">
381 <%def name="pullrequest_name(pull_request_id, state, is_wip, target_repo_name, short=False)">
382 <code>
382 <code>
383 <a href="${h.route_path('pullrequest_show',repo_name=target_repo_name,pull_request_id=pull_request_id)}">
383 <a href="${h.route_path('pullrequest_show',repo_name=target_repo_name,pull_request_id=pull_request_id)}">
384 % if short:
384 % if short:
385 !${pull_request_id}
385 !${pull_request_id}
386 % else:
386 % else:
387 ${_('Pull request !{}').format(pull_request_id)}
387 ${_('Pull request !{}').format(pull_request_id)}
388 % endif
388 % endif
389 </a>
389 </a>
390 </code>
390 </code>
391 % if state not in ['created']:
391 % if state not in ['created']:
392 <span class="tag tag-merge-state-${state} tooltip" title="Pull request state is changing">${state}</span>
392 <span class="tag tag-merge-state-${state} tooltip" title="Pull request state is changing">${state}</span>
393 % endif
393 % endif
394
394
395 % if is_wip:
395 % if is_wip:
396 <span class="tag tooltip" title="${_('Work in progress')}">wip</span>
396 <span class="tag tooltip" title="${_('Work in progress')}">wip</span>
397 % endif
397 % endif
398 </%def>
398 </%def>
399
399
400 <%def name="pullrequest_updated_on(updated_on)">
400 <%def name="pullrequest_updated_on(updated_on, pr_version=None)">
401 % if pr_version:
402 <code>v${pr_version}</code>
403 % endif
401 ${h.age_component(h.time_to_utcdatetime(updated_on))}
404 ${h.age_component(h.time_to_utcdatetime(updated_on))}
402 </%def>
405 </%def>
403
406
404 <%def name="pullrequest_author(full_contact)">
407 <%def name="pullrequest_author(full_contact)">
405 ${base.gravatar_with_user(full_contact, 16, tooltip=True)}
408 ${base.gravatar_with_user(full_contact, 16, tooltip=True)}
406 </%def>
409 </%def>
407
410
408
411
409 ## ARTIFACT RENDERERS
412 ## ARTIFACT RENDERERS
410 <%def name="repo_artifact_name(repo_name, file_uid, artifact_display_name)">
413 <%def name="repo_artifact_name(repo_name, file_uid, artifact_display_name)">
411 <a href="${h.route_path('repo_artifacts_get', repo_name=repo_name, uid=file_uid)}">
414 <a href="${h.route_path('repo_artifacts_get', repo_name=repo_name, uid=file_uid)}">
412 ${artifact_display_name or '_EMPTY_NAME_'}
415 ${artifact_display_name or '_EMPTY_NAME_'}
413 </a>
416 </a>
414 </%def>
417 </%def>
415
418
416 <%def name="repo_artifact_uid(repo_name, file_uid)">
419 <%def name="repo_artifact_uid(repo_name, file_uid)">
417 <code>${h.shorter(file_uid, size=24, prefix=True)}</code>
420 <code>${h.shorter(file_uid, size=24, prefix=True)}</code>
418 </%def>
421 </%def>
419
422
420 <%def name="repo_artifact_sha256(artifact_sha256)">
423 <%def name="repo_artifact_sha256(artifact_sha256)">
421 <div class="code">${h.shorter(artifact_sha256, 12)}</div>
424 <div class="code">${h.shorter(artifact_sha256, 12)}</div>
422 </%def>
425 </%def>
423
426
424 <%def name="repo_artifact_actions(repo_name, file_store_id, file_uid)">
427 <%def name="repo_artifact_actions(repo_name, file_store_id, file_uid)">
425 ## <div class="grid_edit">
428 ## <div class="grid_edit">
426 ## <a href="#Edit" title="${_('Edit')}">${_('Edit')}</a>
429 ## <a href="#Edit" title="${_('Edit')}">${_('Edit')}</a>
427 ## </div>
430 ## </div>
428 <div class="grid_edit">
431 <div class="grid_edit">
429 <a href="${h.route_path('repo_artifacts_info', repo_name=repo_name, uid=file_store_id)}" title="${_('Info')}">${_('Info')}</a>
432 <a href="${h.route_path('repo_artifacts_info', repo_name=repo_name, uid=file_store_id)}" title="${_('Info')}">${_('Info')}</a>
430 </div>
433 </div>
431 % if h.HasRepoPermissionAny('repository.admin')(c.repo_name):
434 % if h.HasRepoPermissionAny('repository.admin')(c.repo_name):
432 <div class="grid_delete">
435 <div class="grid_delete">
433 ${h.secure_form(h.route_path('repo_artifacts_delete', repo_name=repo_name, uid=file_store_id), request=request)}
436 ${h.secure_form(h.route_path('repo_artifacts_delete', repo_name=repo_name, uid=file_store_id), request=request)}
434 <input class="btn btn-link btn-danger" id="remove_artifact_${file_store_id}" name="remove_artifact_${file_store_id}"
437 <input class="btn btn-link btn-danger" id="remove_artifact_${file_store_id}" name="remove_artifact_${file_store_id}"
435 onclick="submitConfirm(event, this, _gettext('Confirm to delete this artifact'), _gettext('Delete'), '${file_uid}')"
438 onclick="submitConfirm(event, this, _gettext('Confirm to delete this artifact'), _gettext('Delete'), '${file_uid}')"
436 type="submit" value="${_('Delete')}"
439 type="submit" value="${_('Delete')}"
437 >
440 >
438 ${h.end_form()}
441 ${h.end_form()}
439 </div>
442 </div>
440 % endif
443 % endif
441 </%def>
444 </%def>
442
445
443 <%def name="markup_form(form_id, form_text='', help_text=None)">
446 <%def name="markup_form(form_id, form_text='', help_text=None)">
444
447
445 <div class="markup-form">
448 <div class="markup-form">
446 <div class="markup-form-area">
449 <div class="markup-form-area">
447 <div class="markup-form-area-header">
450 <div class="markup-form-area-header">
448 <ul class="nav-links clearfix">
451 <ul class="nav-links clearfix">
449 <li class="active">
452 <li class="active">
450 <a href="#edit-text" tabindex="-1" id="edit-btn_${form_id}">${_('Write')}</a>
453 <a href="#edit-text" tabindex="-1" id="edit-btn_${form_id}">${_('Write')}</a>
451 </li>
454 </li>
452 <li class="">
455 <li class="">
453 <a href="#preview-text" tabindex="-1" id="preview-btn_${form_id}">${_('Preview')}</a>
456 <a href="#preview-text" tabindex="-1" id="preview-btn_${form_id}">${_('Preview')}</a>
454 </li>
457 </li>
455 </ul>
458 </ul>
456 </div>
459 </div>
457
460
458 <div class="markup-form-area-write" style="display: block;">
461 <div class="markup-form-area-write" style="display: block;">
459 <div id="edit-container_${form_id}" style="margin-top: -1px">
462 <div id="edit-container_${form_id}" style="margin-top: -1px">
460 <textarea id="${form_id}" name="${form_id}" class="comment-block-ta ac-input">${form_text if form_text else ''}</textarea>
463 <textarea id="${form_id}" name="${form_id}" class="comment-block-ta ac-input">${form_text if form_text else ''}</textarea>
461 </div>
464 </div>
462 <div id="preview-container_${form_id}" class="clearfix" style="display: none;">
465 <div id="preview-container_${form_id}" class="clearfix" style="display: none;">
463 <div id="preview-box_${form_id}" class="preview-box"></div>
466 <div id="preview-box_${form_id}" class="preview-box"></div>
464 </div>
467 </div>
465 </div>
468 </div>
466
469
467 <div class="markup-form-area-footer">
470 <div class="markup-form-area-footer">
468 <div class="toolbar">
471 <div class="toolbar">
469 <div class="toolbar-text">
472 <div class="toolbar-text">
470 ${(_('Parsed using %s syntax') % (
473 ${(_('Parsed using %s syntax') % (
471 ('<a href="%s">%s</a>' % (h.route_url('%s_help' % c.visual.default_renderer), c.visual.default_renderer.upper())),
474 ('<a href="%s">%s</a>' % (h.route_url('%s_help' % c.visual.default_renderer), c.visual.default_renderer.upper())),
472 )
475 )
473 )|n}
476 )|n}
474 </div>
477 </div>
475 </div>
478 </div>
476 </div>
479 </div>
477 </div>
480 </div>
478
481
479 <div class="markup-form-footer">
482 <div class="markup-form-footer">
480 % if help_text:
483 % if help_text:
481 <span class="help-block">${help_text}</span>
484 <span class="help-block">${help_text}</span>
482 % endif
485 % endif
483 </div>
486 </div>
484 </div>
487 </div>
485 <script type="text/javascript">
488 <script type="text/javascript">
486 new MarkupForm('${form_id}');
489 new MarkupForm('${form_id}');
487 </script>
490 </script>
488
491
489 </%def>
492 </%def>
General Comments 0
You need to be logged in to leave comments. Login now