##// END OF EJS Templates
pull-requests: added WIP markers in display, and improved a bit ui when creating a pull-request....
marcink -
r4102:24c7dd99 default
parent child Browse files
Show More
@@ -1,761 +1,762 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2019 RhodeCode GmbH
3 # Copyright (C) 2016-2019 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
28 from pyramid.httpexceptions import HTTPFound
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 LoginRequired, NotAnonymous, CSRFRequired, \
36 from rhodecode.lib.auth import LoginRequired, NotAnonymous, CSRFRequired, \
37 HasRepoPermissionAny, HasRepoGroupPermissionAny
37 HasRepoPermissionAny, HasRepoGroupPermissionAny
38 from rhodecode.lib.channelstream import (
38 from rhodecode.lib.channelstream import (
39 channelstream_request, ChannelstreamException)
39 channelstream_request, ChannelstreamException)
40 from rhodecode.lib.utils2 import safe_int, md5, str2bool
40 from rhodecode.lib.utils2 import safe_int, md5, str2bool
41 from rhodecode.model.auth_token import AuthTokenModel
41 from rhodecode.model.auth_token import AuthTokenModel
42 from rhodecode.model.comment import CommentsModel
42 from rhodecode.model.comment import CommentsModel
43 from rhodecode.model.db import (
43 from rhodecode.model.db import (
44 IntegrityError, joinedload,
44 IntegrityError, joinedload,
45 Repository, UserEmailMap, UserApiKeys, UserFollowing,
45 Repository, UserEmailMap, UserApiKeys, UserFollowing,
46 PullRequest, UserBookmark, RepoGroup)
46 PullRequest, UserBookmark, RepoGroup)
47 from rhodecode.model.meta import Session
47 from rhodecode.model.meta import Session
48 from rhodecode.model.pull_request import PullRequestModel
48 from rhodecode.model.pull_request import PullRequestModel
49 from rhodecode.model.scm import RepoList
49 from rhodecode.model.scm import RepoList
50 from rhodecode.model.user import UserModel
50 from rhodecode.model.user import UserModel
51 from rhodecode.model.repo import RepoModel
51 from rhodecode.model.repo import RepoModel
52 from rhodecode.model.user_group import UserGroupModel
52 from rhodecode.model.user_group import UserGroupModel
53 from rhodecode.model.validation_schema.schemas import user_schema
53 from rhodecode.model.validation_schema.schemas import user_schema
54
54
55 log = logging.getLogger(__name__)
55 log = logging.getLogger(__name__)
56
56
57
57
58 class MyAccountView(BaseAppView, DataGridAppView):
58 class MyAccountView(BaseAppView, DataGridAppView):
59 ALLOW_SCOPED_TOKENS = False
59 ALLOW_SCOPED_TOKENS = False
60 """
60 """
61 This view has alternative version inside EE, if modified please take a look
61 This view has alternative version inside EE, if modified please take a look
62 in there as well.
62 in there as well.
63 """
63 """
64
64
65 def load_default_context(self):
65 def load_default_context(self):
66 c = self._get_local_tmpl_context()
66 c = self._get_local_tmpl_context()
67 c.user = c.auth_user.get_instance()
67 c.user = c.auth_user.get_instance()
68 c.allow_scoped_tokens = self.ALLOW_SCOPED_TOKENS
68 c.allow_scoped_tokens = self.ALLOW_SCOPED_TOKENS
69
69
70 return c
70 return c
71
71
72 @LoginRequired()
72 @LoginRequired()
73 @NotAnonymous()
73 @NotAnonymous()
74 @view_config(
74 @view_config(
75 route_name='my_account_profile', request_method='GET',
75 route_name='my_account_profile', request_method='GET',
76 renderer='rhodecode:templates/admin/my_account/my_account.mako')
76 renderer='rhodecode:templates/admin/my_account/my_account.mako')
77 def my_account_profile(self):
77 def my_account_profile(self):
78 c = self.load_default_context()
78 c = self.load_default_context()
79 c.active = 'profile'
79 c.active = 'profile'
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 def maybe_attach_token_scope(self, token):
167 def maybe_attach_token_scope(self, token):
168 # implemented in EE edition
168 # implemented in EE edition
169 pass
169 pass
170
170
171 @LoginRequired()
171 @LoginRequired()
172 @NotAnonymous()
172 @NotAnonymous()
173 @CSRFRequired()
173 @CSRFRequired()
174 @view_config(
174 @view_config(
175 route_name='my_account_auth_tokens_add', request_method='POST',)
175 route_name='my_account_auth_tokens_add', request_method='POST',)
176 def my_account_auth_tokens_add(self):
176 def my_account_auth_tokens_add(self):
177 _ = self.request.translate
177 _ = self.request.translate
178 c = self.load_default_context()
178 c = self.load_default_context()
179
179
180 lifetime = safe_int(self.request.POST.get('lifetime'), -1)
180 lifetime = safe_int(self.request.POST.get('lifetime'), -1)
181 description = self.request.POST.get('description')
181 description = self.request.POST.get('description')
182 role = self.request.POST.get('role')
182 role = self.request.POST.get('role')
183
183
184 token = UserModel().add_auth_token(
184 token = UserModel().add_auth_token(
185 user=c.user.user_id,
185 user=c.user.user_id,
186 lifetime_minutes=lifetime, role=role, description=description,
186 lifetime_minutes=lifetime, role=role, description=description,
187 scope_callback=self.maybe_attach_token_scope)
187 scope_callback=self.maybe_attach_token_scope)
188 token_data = token.get_api_data()
188 token_data = token.get_api_data()
189
189
190 audit_logger.store_web(
190 audit_logger.store_web(
191 'user.edit.token.add', action_data={
191 'user.edit.token.add', action_data={
192 'data': {'token': token_data, 'user': 'self'}},
192 'data': {'token': token_data, 'user': 'self'}},
193 user=self._rhodecode_user, )
193 user=self._rhodecode_user, )
194 Session().commit()
194 Session().commit()
195
195
196 h.flash(_("Auth token successfully created"), category='success')
196 h.flash(_("Auth token successfully created"), category='success')
197 return HTTPFound(h.route_path('my_account_auth_tokens'))
197 return HTTPFound(h.route_path('my_account_auth_tokens'))
198
198
199 @LoginRequired()
199 @LoginRequired()
200 @NotAnonymous()
200 @NotAnonymous()
201 @CSRFRequired()
201 @CSRFRequired()
202 @view_config(
202 @view_config(
203 route_name='my_account_auth_tokens_delete', request_method='POST')
203 route_name='my_account_auth_tokens_delete', request_method='POST')
204 def my_account_auth_tokens_delete(self):
204 def my_account_auth_tokens_delete(self):
205 _ = self.request.translate
205 _ = self.request.translate
206 c = self.load_default_context()
206 c = self.load_default_context()
207
207
208 del_auth_token = self.request.POST.get('del_auth_token')
208 del_auth_token = self.request.POST.get('del_auth_token')
209
209
210 if del_auth_token:
210 if del_auth_token:
211 token = UserApiKeys.get_or_404(del_auth_token)
211 token = UserApiKeys.get_or_404(del_auth_token)
212 token_data = token.get_api_data()
212 token_data = token.get_api_data()
213
213
214 AuthTokenModel().delete(del_auth_token, c.user.user_id)
214 AuthTokenModel().delete(del_auth_token, c.user.user_id)
215 audit_logger.store_web(
215 audit_logger.store_web(
216 'user.edit.token.delete', action_data={
216 'user.edit.token.delete', action_data={
217 'data': {'token': token_data, 'user': 'self'}},
217 'data': {'token': token_data, 'user': 'self'}},
218 user=self._rhodecode_user,)
218 user=self._rhodecode_user,)
219 Session().commit()
219 Session().commit()
220 h.flash(_("Auth token successfully deleted"), category='success')
220 h.flash(_("Auth token successfully deleted"), category='success')
221
221
222 return HTTPFound(h.route_path('my_account_auth_tokens'))
222 return HTTPFound(h.route_path('my_account_auth_tokens'))
223
223
224 @LoginRequired()
224 @LoginRequired()
225 @NotAnonymous()
225 @NotAnonymous()
226 @view_config(
226 @view_config(
227 route_name='my_account_emails', request_method='GET',
227 route_name='my_account_emails', request_method='GET',
228 renderer='rhodecode:templates/admin/my_account/my_account.mako')
228 renderer='rhodecode:templates/admin/my_account/my_account.mako')
229 def my_account_emails(self):
229 def my_account_emails(self):
230 _ = self.request.translate
230 _ = self.request.translate
231
231
232 c = self.load_default_context()
232 c = self.load_default_context()
233 c.active = 'emails'
233 c.active = 'emails'
234
234
235 c.user_email_map = UserEmailMap.query()\
235 c.user_email_map = UserEmailMap.query()\
236 .filter(UserEmailMap.user == c.user).all()
236 .filter(UserEmailMap.user == c.user).all()
237
237
238 schema = user_schema.AddEmailSchema().bind(
238 schema = user_schema.AddEmailSchema().bind(
239 username=c.user.username, user_emails=c.user.emails)
239 username=c.user.username, user_emails=c.user.emails)
240
240
241 form = forms.RcForm(schema,
241 form = forms.RcForm(schema,
242 action=h.route_path('my_account_emails_add'),
242 action=h.route_path('my_account_emails_add'),
243 buttons=(forms.buttons.save, forms.buttons.reset))
243 buttons=(forms.buttons.save, forms.buttons.reset))
244
244
245 c.form = form
245 c.form = form
246 return self._get_template_context(c)
246 return self._get_template_context(c)
247
247
248 @LoginRequired()
248 @LoginRequired()
249 @NotAnonymous()
249 @NotAnonymous()
250 @CSRFRequired()
250 @CSRFRequired()
251 @view_config(
251 @view_config(
252 route_name='my_account_emails_add', request_method='POST',
252 route_name='my_account_emails_add', request_method='POST',
253 renderer='rhodecode:templates/admin/my_account/my_account.mako')
253 renderer='rhodecode:templates/admin/my_account/my_account.mako')
254 def my_account_emails_add(self):
254 def my_account_emails_add(self):
255 _ = self.request.translate
255 _ = self.request.translate
256 c = self.load_default_context()
256 c = self.load_default_context()
257 c.active = 'emails'
257 c.active = 'emails'
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(
262 form = forms.RcForm(
263 schema, action=h.route_path('my_account_emails_add'),
263 schema, 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 controls = self.request.POST.items()
266 controls = self.request.POST.items()
267 try:
267 try:
268 valid_data = form.validate(controls)
268 valid_data = form.validate(controls)
269 UserModel().add_extra_email(c.user.user_id, valid_data['email'])
269 UserModel().add_extra_email(c.user.user_id, valid_data['email'])
270 audit_logger.store_web(
270 audit_logger.store_web(
271 'user.edit.email.add', action_data={
271 'user.edit.email.add', action_data={
272 'data': {'email': valid_data['email'], 'user': 'self'}},
272 'data': {'email': valid_data['email'], 'user': 'self'}},
273 user=self._rhodecode_user,)
273 user=self._rhodecode_user,)
274 Session().commit()
274 Session().commit()
275 except formencode.Invalid as error:
275 except formencode.Invalid as error:
276 h.flash(h.escape(error.error_dict['email']), category='error')
276 h.flash(h.escape(error.error_dict['email']), category='error')
277 except forms.ValidationFailure as e:
277 except forms.ValidationFailure as e:
278 c.user_email_map = UserEmailMap.query() \
278 c.user_email_map = UserEmailMap.query() \
279 .filter(UserEmailMap.user == c.user).all()
279 .filter(UserEmailMap.user == c.user).all()
280 c.form = e
280 c.form = e
281 return self._get_template_context(c)
281 return self._get_template_context(c)
282 except Exception:
282 except Exception:
283 log.exception("Exception adding email")
283 log.exception("Exception adding email")
284 h.flash(_('Error occurred during adding email'),
284 h.flash(_('Error occurred during adding email'),
285 category='error')
285 category='error')
286 else:
286 else:
287 h.flash(_("Successfully added email"), category='success')
287 h.flash(_("Successfully added email"), category='success')
288
288
289 raise HTTPFound(self.request.route_path('my_account_emails'))
289 raise HTTPFound(self.request.route_path('my_account_emails'))
290
290
291 @LoginRequired()
291 @LoginRequired()
292 @NotAnonymous()
292 @NotAnonymous()
293 @CSRFRequired()
293 @CSRFRequired()
294 @view_config(
294 @view_config(
295 route_name='my_account_emails_delete', request_method='POST')
295 route_name='my_account_emails_delete', request_method='POST')
296 def my_account_emails_delete(self):
296 def my_account_emails_delete(self):
297 _ = self.request.translate
297 _ = self.request.translate
298 c = self.load_default_context()
298 c = self.load_default_context()
299
299
300 del_email_id = self.request.POST.get('del_email_id')
300 del_email_id = self.request.POST.get('del_email_id')
301 if del_email_id:
301 if del_email_id:
302 email = UserEmailMap.get_or_404(del_email_id).email
302 email = UserEmailMap.get_or_404(del_email_id).email
303 UserModel().delete_extra_email(c.user.user_id, del_email_id)
303 UserModel().delete_extra_email(c.user.user_id, del_email_id)
304 audit_logger.store_web(
304 audit_logger.store_web(
305 'user.edit.email.delete', action_data={
305 'user.edit.email.delete', action_data={
306 'data': {'email': email, 'user': 'self'}},
306 'data': {'email': email, 'user': 'self'}},
307 user=self._rhodecode_user,)
307 user=self._rhodecode_user,)
308 Session().commit()
308 Session().commit()
309 h.flash(_("Email successfully deleted"),
309 h.flash(_("Email successfully deleted"),
310 category='success')
310 category='success')
311 return HTTPFound(h.route_path('my_account_emails'))
311 return HTTPFound(h.route_path('my_account_emails'))
312
312
313 @LoginRequired()
313 @LoginRequired()
314 @NotAnonymous()
314 @NotAnonymous()
315 @CSRFRequired()
315 @CSRFRequired()
316 @view_config(
316 @view_config(
317 route_name='my_account_notifications_test_channelstream',
317 route_name='my_account_notifications_test_channelstream',
318 request_method='POST', renderer='json_ext')
318 request_method='POST', renderer='json_ext')
319 def my_account_notifications_test_channelstream(self):
319 def my_account_notifications_test_channelstream(self):
320 message = 'Test message sent via Channelstream by user: {}, on {}'.format(
320 message = 'Test message sent via Channelstream by user: {}, on {}'.format(
321 self._rhodecode_user.username, datetime.datetime.now())
321 self._rhodecode_user.username, datetime.datetime.now())
322 payload = {
322 payload = {
323 # 'channel': 'broadcast',
323 # 'channel': 'broadcast',
324 'type': 'message',
324 'type': 'message',
325 'timestamp': datetime.datetime.utcnow(),
325 'timestamp': datetime.datetime.utcnow(),
326 'user': 'system',
326 'user': 'system',
327 'pm_users': [self._rhodecode_user.username],
327 'pm_users': [self._rhodecode_user.username],
328 'message': {
328 'message': {
329 'message': message,
329 'message': message,
330 'level': 'info',
330 'level': 'info',
331 'topic': '/notifications'
331 'topic': '/notifications'
332 }
332 }
333 }
333 }
334
334
335 registry = self.request.registry
335 registry = self.request.registry
336 rhodecode_plugins = getattr(registry, 'rhodecode_plugins', {})
336 rhodecode_plugins = getattr(registry, 'rhodecode_plugins', {})
337 channelstream_config = rhodecode_plugins.get('channelstream', {})
337 channelstream_config = rhodecode_plugins.get('channelstream', {})
338
338
339 try:
339 try:
340 channelstream_request(channelstream_config, [payload], '/message')
340 channelstream_request(channelstream_config, [payload], '/message')
341 except ChannelstreamException as e:
341 except ChannelstreamException as e:
342 log.exception('Failed to send channelstream data')
342 log.exception('Failed to send channelstream data')
343 return {"response": 'ERROR: {}'.format(e.__class__.__name__)}
343 return {"response": 'ERROR: {}'.format(e.__class__.__name__)}
344 return {"response": 'Channelstream data sent. '
344 return {"response": 'Channelstream data sent. '
345 'You should see a new live message now.'}
345 'You should see a new live message now.'}
346
346
347 def _load_my_repos_data(self, watched=False):
347 def _load_my_repos_data(self, watched=False):
348 if watched:
348 if watched:
349 admin = False
349 admin = False
350 follows_repos = Session().query(UserFollowing)\
350 follows_repos = Session().query(UserFollowing)\
351 .filter(UserFollowing.user_id == self._rhodecode_user.user_id)\
351 .filter(UserFollowing.user_id == self._rhodecode_user.user_id)\
352 .options(joinedload(UserFollowing.follows_repository))\
352 .options(joinedload(UserFollowing.follows_repository))\
353 .all()
353 .all()
354 repo_list = [x.follows_repository for x in follows_repos]
354 repo_list = [x.follows_repository for x in follows_repos]
355 else:
355 else:
356 admin = True
356 admin = True
357 repo_list = Repository.get_all_repos(
357 repo_list = Repository.get_all_repos(
358 user_id=self._rhodecode_user.user_id)
358 user_id=self._rhodecode_user.user_id)
359 repo_list = RepoList(repo_list, perm_set=[
359 repo_list = RepoList(repo_list, perm_set=[
360 'repository.read', 'repository.write', 'repository.admin'])
360 'repository.read', 'repository.write', 'repository.admin'])
361
361
362 repos_data = RepoModel().get_repos_as_dict(
362 repos_data = RepoModel().get_repos_as_dict(
363 repo_list=repo_list, admin=admin, short_name=False)
363 repo_list=repo_list, admin=admin, short_name=False)
364 # json used to render the grid
364 # json used to render the grid
365 return json.dumps(repos_data)
365 return json.dumps(repos_data)
366
366
367 @LoginRequired()
367 @LoginRequired()
368 @NotAnonymous()
368 @NotAnonymous()
369 @view_config(
369 @view_config(
370 route_name='my_account_repos', request_method='GET',
370 route_name='my_account_repos', request_method='GET',
371 renderer='rhodecode:templates/admin/my_account/my_account.mako')
371 renderer='rhodecode:templates/admin/my_account/my_account.mako')
372 def my_account_repos(self):
372 def my_account_repos(self):
373 c = self.load_default_context()
373 c = self.load_default_context()
374 c.active = 'repos'
374 c.active = 'repos'
375
375
376 # json used to render the grid
376 # json used to render the grid
377 c.data = self._load_my_repos_data()
377 c.data = self._load_my_repos_data()
378 return self._get_template_context(c)
378 return self._get_template_context(c)
379
379
380 @LoginRequired()
380 @LoginRequired()
381 @NotAnonymous()
381 @NotAnonymous()
382 @view_config(
382 @view_config(
383 route_name='my_account_watched', request_method='GET',
383 route_name='my_account_watched', request_method='GET',
384 renderer='rhodecode:templates/admin/my_account/my_account.mako')
384 renderer='rhodecode:templates/admin/my_account/my_account.mako')
385 def my_account_watched(self):
385 def my_account_watched(self):
386 c = self.load_default_context()
386 c = self.load_default_context()
387 c.active = 'watched'
387 c.active = 'watched'
388
388
389 # json used to render the grid
389 # json used to render the grid
390 c.data = self._load_my_repos_data(watched=True)
390 c.data = self._load_my_repos_data(watched=True)
391 return self._get_template_context(c)
391 return self._get_template_context(c)
392
392
393 @LoginRequired()
393 @LoginRequired()
394 @NotAnonymous()
394 @NotAnonymous()
395 @view_config(
395 @view_config(
396 route_name='my_account_bookmarks', request_method='GET',
396 route_name='my_account_bookmarks', request_method='GET',
397 renderer='rhodecode:templates/admin/my_account/my_account.mako')
397 renderer='rhodecode:templates/admin/my_account/my_account.mako')
398 def my_account_bookmarks(self):
398 def my_account_bookmarks(self):
399 c = self.load_default_context()
399 c = self.load_default_context()
400 c.active = 'bookmarks'
400 c.active = 'bookmarks'
401 return self._get_template_context(c)
401 return self._get_template_context(c)
402
402
403 def _process_bookmark_entry(self, entry, user_id):
403 def _process_bookmark_entry(self, entry, user_id):
404 position = safe_int(entry.get('position'))
404 position = safe_int(entry.get('position'))
405 cur_position = safe_int(entry.get('cur_position'))
405 cur_position = safe_int(entry.get('cur_position'))
406 if position is None or cur_position is None:
406 if position is None or cur_position is None:
407 return
407 return
408
408
409 # check if this is an existing entry
409 # check if this is an existing entry
410 is_new = False
410 is_new = False
411 db_entry = UserBookmark().get_by_position_for_user(cur_position, user_id)
411 db_entry = UserBookmark().get_by_position_for_user(cur_position, user_id)
412
412
413 if db_entry and str2bool(entry.get('remove')):
413 if db_entry and str2bool(entry.get('remove')):
414 log.debug('Marked bookmark %s for deletion', db_entry)
414 log.debug('Marked bookmark %s for deletion', db_entry)
415 Session().delete(db_entry)
415 Session().delete(db_entry)
416 return
416 return
417
417
418 if not db_entry:
418 if not db_entry:
419 # new
419 # new
420 db_entry = UserBookmark()
420 db_entry = UserBookmark()
421 is_new = True
421 is_new = True
422
422
423 should_save = False
423 should_save = False
424 default_redirect_url = ''
424 default_redirect_url = ''
425
425
426 # save repo
426 # save repo
427 if entry.get('bookmark_repo') and safe_int(entry.get('bookmark_repo')):
427 if entry.get('bookmark_repo') and safe_int(entry.get('bookmark_repo')):
428 repo = Repository.get(entry['bookmark_repo'])
428 repo = Repository.get(entry['bookmark_repo'])
429 perm_check = HasRepoPermissionAny(
429 perm_check = HasRepoPermissionAny(
430 'repository.read', 'repository.write', 'repository.admin')
430 'repository.read', 'repository.write', 'repository.admin')
431 if repo and perm_check(repo_name=repo.repo_name):
431 if repo and perm_check(repo_name=repo.repo_name):
432 db_entry.repository = repo
432 db_entry.repository = repo
433 should_save = True
433 should_save = True
434 default_redirect_url = '${repo_url}'
434 default_redirect_url = '${repo_url}'
435 # save repo group
435 # save repo group
436 elif entry.get('bookmark_repo_group') and safe_int(entry.get('bookmark_repo_group')):
436 elif entry.get('bookmark_repo_group') and safe_int(entry.get('bookmark_repo_group')):
437 repo_group = RepoGroup.get(entry['bookmark_repo_group'])
437 repo_group = RepoGroup.get(entry['bookmark_repo_group'])
438 perm_check = HasRepoGroupPermissionAny(
438 perm_check = HasRepoGroupPermissionAny(
439 'group.read', 'group.write', 'group.admin')
439 'group.read', 'group.write', 'group.admin')
440
440
441 if repo_group and perm_check(group_name=repo_group.group_name):
441 if repo_group and perm_check(group_name=repo_group.group_name):
442 db_entry.repository_group = repo_group
442 db_entry.repository_group = repo_group
443 should_save = True
443 should_save = True
444 default_redirect_url = '${repo_group_url}'
444 default_redirect_url = '${repo_group_url}'
445 # save generic info
445 # save generic info
446 elif entry.get('title') and entry.get('redirect_url'):
446 elif entry.get('title') and entry.get('redirect_url'):
447 should_save = True
447 should_save = True
448
448
449 if should_save:
449 if should_save:
450 # mark user and position
450 # mark user and position
451 db_entry.user_id = user_id
451 db_entry.user_id = user_id
452 db_entry.position = position
452 db_entry.position = position
453 db_entry.title = entry.get('title')
453 db_entry.title = entry.get('title')
454 db_entry.redirect_url = entry.get('redirect_url') or default_redirect_url
454 db_entry.redirect_url = entry.get('redirect_url') or default_redirect_url
455 log.debug('Saving bookmark %s, new:%s', db_entry, is_new)
455 log.debug('Saving bookmark %s, new:%s', db_entry, is_new)
456
456
457 Session().add(db_entry)
457 Session().add(db_entry)
458
458
459 @LoginRequired()
459 @LoginRequired()
460 @NotAnonymous()
460 @NotAnonymous()
461 @CSRFRequired()
461 @CSRFRequired()
462 @view_config(
462 @view_config(
463 route_name='my_account_bookmarks_update', request_method='POST')
463 route_name='my_account_bookmarks_update', request_method='POST')
464 def my_account_bookmarks_update(self):
464 def my_account_bookmarks_update(self):
465 _ = self.request.translate
465 _ = self.request.translate
466 c = self.load_default_context()
466 c = self.load_default_context()
467 c.active = 'bookmarks'
467 c.active = 'bookmarks'
468
468
469 controls = peppercorn.parse(self.request.POST.items())
469 controls = peppercorn.parse(self.request.POST.items())
470 user_id = c.user.user_id
470 user_id = c.user.user_id
471
471
472 # validate positions
472 # validate positions
473 positions = {}
473 positions = {}
474 for entry in controls.get('bookmarks', []):
474 for entry in controls.get('bookmarks', []):
475 position = safe_int(entry['position'])
475 position = safe_int(entry['position'])
476 if position is None:
476 if position is None:
477 continue
477 continue
478
478
479 if position in positions:
479 if position in positions:
480 h.flash(_("Position {} is defined twice. "
480 h.flash(_("Position {} is defined twice. "
481 "Please correct this error.").format(position), category='error')
481 "Please correct this error.").format(position), category='error')
482 return HTTPFound(h.route_path('my_account_bookmarks'))
482 return HTTPFound(h.route_path('my_account_bookmarks'))
483
483
484 entry['position'] = position
484 entry['position'] = position
485 entry['cur_position'] = safe_int(entry.get('cur_position'))
485 entry['cur_position'] = safe_int(entry.get('cur_position'))
486 positions[position] = entry
486 positions[position] = entry
487
487
488 try:
488 try:
489 for entry in positions.values():
489 for entry in positions.values():
490 self._process_bookmark_entry(entry, user_id)
490 self._process_bookmark_entry(entry, user_id)
491
491
492 Session().commit()
492 Session().commit()
493 h.flash(_("Update Bookmarks"), category='success')
493 h.flash(_("Update Bookmarks"), category='success')
494 except IntegrityError:
494 except IntegrityError:
495 h.flash(_("Failed to update bookmarks. "
495 h.flash(_("Failed to update bookmarks. "
496 "Make sure an unique position is used."), category='error')
496 "Make sure an unique position is used."), category='error')
497
497
498 return HTTPFound(h.route_path('my_account_bookmarks'))
498 return HTTPFound(h.route_path('my_account_bookmarks'))
499
499
500 @LoginRequired()
500 @LoginRequired()
501 @NotAnonymous()
501 @NotAnonymous()
502 @view_config(
502 @view_config(
503 route_name='my_account_goto_bookmark', request_method='GET',
503 route_name='my_account_goto_bookmark', request_method='GET',
504 renderer='rhodecode:templates/admin/my_account/my_account.mako')
504 renderer='rhodecode:templates/admin/my_account/my_account.mako')
505 def my_account_goto_bookmark(self):
505 def my_account_goto_bookmark(self):
506
506
507 bookmark_id = self.request.matchdict['bookmark_id']
507 bookmark_id = self.request.matchdict['bookmark_id']
508 user_bookmark = UserBookmark().query()\
508 user_bookmark = UserBookmark().query()\
509 .filter(UserBookmark.user_id == self.request.user.user_id) \
509 .filter(UserBookmark.user_id == self.request.user.user_id) \
510 .filter(UserBookmark.position == bookmark_id).scalar()
510 .filter(UserBookmark.position == bookmark_id).scalar()
511
511
512 redirect_url = h.route_path('my_account_bookmarks')
512 redirect_url = h.route_path('my_account_bookmarks')
513 if not user_bookmark:
513 if not user_bookmark:
514 raise HTTPFound(redirect_url)
514 raise HTTPFound(redirect_url)
515
515
516 # repository set
516 # repository set
517 if user_bookmark.repository:
517 if user_bookmark.repository:
518 repo_name = user_bookmark.repository.repo_name
518 repo_name = user_bookmark.repository.repo_name
519 base_redirect_url = h.route_path(
519 base_redirect_url = h.route_path(
520 'repo_summary', repo_name=repo_name)
520 'repo_summary', repo_name=repo_name)
521 if user_bookmark.redirect_url and \
521 if user_bookmark.redirect_url and \
522 '${repo_url}' in user_bookmark.redirect_url:
522 '${repo_url}' in user_bookmark.redirect_url:
523 redirect_url = string.Template(user_bookmark.redirect_url)\
523 redirect_url = string.Template(user_bookmark.redirect_url)\
524 .safe_substitute({'repo_url': base_redirect_url})
524 .safe_substitute({'repo_url': base_redirect_url})
525 else:
525 else:
526 redirect_url = base_redirect_url
526 redirect_url = base_redirect_url
527 # repository group set
527 # repository group set
528 elif user_bookmark.repository_group:
528 elif user_bookmark.repository_group:
529 repo_group_name = user_bookmark.repository_group.group_name
529 repo_group_name = user_bookmark.repository_group.group_name
530 base_redirect_url = h.route_path(
530 base_redirect_url = h.route_path(
531 'repo_group_home', repo_group_name=repo_group_name)
531 'repo_group_home', repo_group_name=repo_group_name)
532 if user_bookmark.redirect_url and \
532 if user_bookmark.redirect_url and \
533 '${repo_group_url}' in user_bookmark.redirect_url:
533 '${repo_group_url}' in user_bookmark.redirect_url:
534 redirect_url = string.Template(user_bookmark.redirect_url)\
534 redirect_url = string.Template(user_bookmark.redirect_url)\
535 .safe_substitute({'repo_group_url': base_redirect_url})
535 .safe_substitute({'repo_group_url': base_redirect_url})
536 else:
536 else:
537 redirect_url = base_redirect_url
537 redirect_url = base_redirect_url
538 # custom URL set
538 # custom URL set
539 elif user_bookmark.redirect_url:
539 elif user_bookmark.redirect_url:
540 server_url = h.route_url('home').rstrip('/')
540 server_url = h.route_url('home').rstrip('/')
541 redirect_url = string.Template(user_bookmark.redirect_url) \
541 redirect_url = string.Template(user_bookmark.redirect_url) \
542 .safe_substitute({'server_url': server_url})
542 .safe_substitute({'server_url': server_url})
543
543
544 log.debug('Redirecting bookmark %s to %s', user_bookmark, redirect_url)
544 log.debug('Redirecting bookmark %s to %s', user_bookmark, redirect_url)
545 raise HTTPFound(redirect_url)
545 raise HTTPFound(redirect_url)
546
546
547 @LoginRequired()
547 @LoginRequired()
548 @NotAnonymous()
548 @NotAnonymous()
549 @view_config(
549 @view_config(
550 route_name='my_account_perms', request_method='GET',
550 route_name='my_account_perms', request_method='GET',
551 renderer='rhodecode:templates/admin/my_account/my_account.mako')
551 renderer='rhodecode:templates/admin/my_account/my_account.mako')
552 def my_account_perms(self):
552 def my_account_perms(self):
553 c = self.load_default_context()
553 c = self.load_default_context()
554 c.active = 'perms'
554 c.active = 'perms'
555
555
556 c.perm_user = c.auth_user
556 c.perm_user = c.auth_user
557 return self._get_template_context(c)
557 return self._get_template_context(c)
558
558
559 @LoginRequired()
559 @LoginRequired()
560 @NotAnonymous()
560 @NotAnonymous()
561 @view_config(
561 @view_config(
562 route_name='my_account_notifications', request_method='GET',
562 route_name='my_account_notifications', request_method='GET',
563 renderer='rhodecode:templates/admin/my_account/my_account.mako')
563 renderer='rhodecode:templates/admin/my_account/my_account.mako')
564 def my_notifications(self):
564 def my_notifications(self):
565 c = self.load_default_context()
565 c = self.load_default_context()
566 c.active = 'notifications'
566 c.active = 'notifications'
567
567
568 return self._get_template_context(c)
568 return self._get_template_context(c)
569
569
570 @LoginRequired()
570 @LoginRequired()
571 @NotAnonymous()
571 @NotAnonymous()
572 @CSRFRequired()
572 @CSRFRequired()
573 @view_config(
573 @view_config(
574 route_name='my_account_notifications_toggle_visibility',
574 route_name='my_account_notifications_toggle_visibility',
575 request_method='POST', renderer='json_ext')
575 request_method='POST', renderer='json_ext')
576 def my_notifications_toggle_visibility(self):
576 def my_notifications_toggle_visibility(self):
577 user = self._rhodecode_db_user
577 user = self._rhodecode_db_user
578 new_status = not user.user_data.get('notification_status', True)
578 new_status = not user.user_data.get('notification_status', True)
579 user.update_userdata(notification_status=new_status)
579 user.update_userdata(notification_status=new_status)
580 Session().commit()
580 Session().commit()
581 return user.user_data['notification_status']
581 return user.user_data['notification_status']
582
582
583 @LoginRequired()
583 @LoginRequired()
584 @NotAnonymous()
584 @NotAnonymous()
585 @view_config(
585 @view_config(
586 route_name='my_account_edit',
586 route_name='my_account_edit',
587 request_method='GET',
587 request_method='GET',
588 renderer='rhodecode:templates/admin/my_account/my_account.mako')
588 renderer='rhodecode:templates/admin/my_account/my_account.mako')
589 def my_account_edit(self):
589 def my_account_edit(self):
590 c = self.load_default_context()
590 c = self.load_default_context()
591 c.active = 'profile_edit'
591 c.active = 'profile_edit'
592 c.extern_type = c.user.extern_type
592 c.extern_type = c.user.extern_type
593 c.extern_name = c.user.extern_name
593 c.extern_name = c.user.extern_name
594
594
595 schema = user_schema.UserProfileSchema().bind(
595 schema = user_schema.UserProfileSchema().bind(
596 username=c.user.username, user_emails=c.user.emails)
596 username=c.user.username, user_emails=c.user.emails)
597 appstruct = {
597 appstruct = {
598 'username': c.user.username,
598 'username': c.user.username,
599 'email': c.user.email,
599 'email': c.user.email,
600 'firstname': c.user.firstname,
600 'firstname': c.user.firstname,
601 'lastname': c.user.lastname,
601 'lastname': c.user.lastname,
602 'description': c.user.description,
602 'description': c.user.description,
603 }
603 }
604 c.form = forms.RcForm(
604 c.form = forms.RcForm(
605 schema, appstruct=appstruct,
605 schema, appstruct=appstruct,
606 action=h.route_path('my_account_update'),
606 action=h.route_path('my_account_update'),
607 buttons=(forms.buttons.save, forms.buttons.reset))
607 buttons=(forms.buttons.save, forms.buttons.reset))
608
608
609 return self._get_template_context(c)
609 return self._get_template_context(c)
610
610
611 @LoginRequired()
611 @LoginRequired()
612 @NotAnonymous()
612 @NotAnonymous()
613 @CSRFRequired()
613 @CSRFRequired()
614 @view_config(
614 @view_config(
615 route_name='my_account_update',
615 route_name='my_account_update',
616 request_method='POST',
616 request_method='POST',
617 renderer='rhodecode:templates/admin/my_account/my_account.mako')
617 renderer='rhodecode:templates/admin/my_account/my_account.mako')
618 def my_account_update(self):
618 def my_account_update(self):
619 _ = self.request.translate
619 _ = self.request.translate
620 c = self.load_default_context()
620 c = self.load_default_context()
621 c.active = 'profile_edit'
621 c.active = 'profile_edit'
622 c.perm_user = c.auth_user
622 c.perm_user = c.auth_user
623 c.extern_type = c.user.extern_type
623 c.extern_type = c.user.extern_type
624 c.extern_name = c.user.extern_name
624 c.extern_name = c.user.extern_name
625
625
626 schema = user_schema.UserProfileSchema().bind(
626 schema = user_schema.UserProfileSchema().bind(
627 username=c.user.username, user_emails=c.user.emails)
627 username=c.user.username, user_emails=c.user.emails)
628 form = forms.RcForm(
628 form = forms.RcForm(
629 schema, buttons=(forms.buttons.save, forms.buttons.reset))
629 schema, buttons=(forms.buttons.save, forms.buttons.reset))
630
630
631 controls = self.request.POST.items()
631 controls = self.request.POST.items()
632 try:
632 try:
633 valid_data = form.validate(controls)
633 valid_data = form.validate(controls)
634 skip_attrs = ['admin', 'active', 'extern_type', 'extern_name',
634 skip_attrs = ['admin', 'active', 'extern_type', 'extern_name',
635 'new_password', 'password_confirmation']
635 'new_password', 'password_confirmation']
636 if c.extern_type != "rhodecode":
636 if c.extern_type != "rhodecode":
637 # forbid updating username for external accounts
637 # forbid updating username for external accounts
638 skip_attrs.append('username')
638 skip_attrs.append('username')
639 old_email = c.user.email
639 old_email = c.user.email
640 UserModel().update_user(
640 UserModel().update_user(
641 self._rhodecode_user.user_id, skip_attrs=skip_attrs,
641 self._rhodecode_user.user_id, skip_attrs=skip_attrs,
642 **valid_data)
642 **valid_data)
643 if old_email != valid_data['email']:
643 if old_email != valid_data['email']:
644 old = UserEmailMap.query() \
644 old = UserEmailMap.query() \
645 .filter(UserEmailMap.user == c.user).filter(UserEmailMap.email == valid_data['email']).first()
645 .filter(UserEmailMap.user == c.user).filter(UserEmailMap.email == valid_data['email']).first()
646 old.email = old_email
646 old.email = old_email
647 h.flash(_('Your account was updated successfully'), category='success')
647 h.flash(_('Your account was updated successfully'), category='success')
648 Session().commit()
648 Session().commit()
649 except forms.ValidationFailure as e:
649 except forms.ValidationFailure as e:
650 c.form = e
650 c.form = e
651 return self._get_template_context(c)
651 return self._get_template_context(c)
652 except Exception:
652 except Exception:
653 log.exception("Exception updating user")
653 log.exception("Exception updating user")
654 h.flash(_('Error occurred during update of user'),
654 h.flash(_('Error occurred during update of user'),
655 category='error')
655 category='error')
656 raise HTTPFound(h.route_path('my_account_profile'))
656 raise HTTPFound(h.route_path('my_account_profile'))
657
657
658 def _get_pull_requests_list(self, statuses):
658 def _get_pull_requests_list(self, statuses):
659 draw, start, limit = self._extract_chunk(self.request)
659 draw, start, limit = self._extract_chunk(self.request)
660 search_q, order_by, order_dir = self._extract_ordering(self.request)
660 search_q, order_by, order_dir = self._extract_ordering(self.request)
661 _render = self.request.get_partial_renderer(
661 _render = self.request.get_partial_renderer(
662 'rhodecode:templates/data_table/_dt_elements.mako')
662 'rhodecode:templates/data_table/_dt_elements.mako')
663
663
664 pull_requests = PullRequestModel().get_im_participating_in(
664 pull_requests = PullRequestModel().get_im_participating_in(
665 user_id=self._rhodecode_user.user_id,
665 user_id=self._rhodecode_user.user_id,
666 statuses=statuses,
666 statuses=statuses,
667 offset=start, length=limit, order_by=order_by,
667 offset=start, length=limit, order_by=order_by,
668 order_dir=order_dir)
668 order_dir=order_dir)
669
669
670 pull_requests_total_count = PullRequestModel().count_im_participating_in(
670 pull_requests_total_count = PullRequestModel().count_im_participating_in(
671 user_id=self._rhodecode_user.user_id, statuses=statuses)
671 user_id=self._rhodecode_user.user_id, statuses=statuses)
672
672
673 data = []
673 data = []
674 comments_model = CommentsModel()
674 comments_model = CommentsModel()
675 for pr in pull_requests:
675 for pr in pull_requests:
676 repo_id = pr.target_repo_id
676 repo_id = pr.target_repo_id
677 comments = comments_model.get_all_comments(
677 comments = comments_model.get_all_comments(
678 repo_id, pull_request=pr)
678 repo_id, pull_request=pr)
679 owned = pr.user_id == self._rhodecode_user.user_id
679 owned = pr.user_id == self._rhodecode_user.user_id
680
680
681 data.append({
681 data.append({
682 'target_repo': _render('pullrequest_target_repo',
682 'target_repo': _render('pullrequest_target_repo',
683 pr.target_repo.repo_name),
683 pr.target_repo.repo_name),
684 'name': _render('pullrequest_name',
684 'name': _render('pullrequest_name',
685 pr.pull_request_id, pr.target_repo.repo_name,
685 pr.pull_request_id, pr.work_in_progress,
686 pr.target_repo.repo_name,
686 short=True),
687 short=True),
687 'name_raw': pr.pull_request_id,
688 'name_raw': pr.pull_request_id,
688 'status': _render('pullrequest_status',
689 'status': _render('pullrequest_status',
689 pr.calculated_review_status()),
690 pr.calculated_review_status()),
690 'title': _render('pullrequest_title', pr.title, pr.description),
691 'title': _render('pullrequest_title', pr.title, pr.description),
691 'description': h.escape(pr.description),
692 'description': h.escape(pr.description),
692 'updated_on': _render('pullrequest_updated_on',
693 'updated_on': _render('pullrequest_updated_on',
693 h.datetime_to_time(pr.updated_on)),
694 h.datetime_to_time(pr.updated_on)),
694 'updated_on_raw': h.datetime_to_time(pr.updated_on),
695 'updated_on_raw': h.datetime_to_time(pr.updated_on),
695 'created_on': _render('pullrequest_updated_on',
696 'created_on': _render('pullrequest_updated_on',
696 h.datetime_to_time(pr.created_on)),
697 h.datetime_to_time(pr.created_on)),
697 'created_on_raw': h.datetime_to_time(pr.created_on),
698 'created_on_raw': h.datetime_to_time(pr.created_on),
698 'state': pr.pull_request_state,
699 'state': pr.pull_request_state,
699 'author': _render('pullrequest_author',
700 'author': _render('pullrequest_author',
700 pr.author.full_contact, ),
701 pr.author.full_contact, ),
701 'author_raw': pr.author.full_name,
702 'author_raw': pr.author.full_name,
702 'comments': _render('pullrequest_comments', len(comments)),
703 'comments': _render('pullrequest_comments', len(comments)),
703 'comments_raw': len(comments),
704 'comments_raw': len(comments),
704 'closed': pr.is_closed(),
705 'closed': pr.is_closed(),
705 'owned': owned
706 'owned': owned
706 })
707 })
707
708
708 # json used to render the grid
709 # json used to render the grid
709 data = ({
710 data = ({
710 'draw': draw,
711 'draw': draw,
711 'data': data,
712 'data': data,
712 'recordsTotal': pull_requests_total_count,
713 'recordsTotal': pull_requests_total_count,
713 'recordsFiltered': pull_requests_total_count,
714 'recordsFiltered': pull_requests_total_count,
714 })
715 })
715 return data
716 return data
716
717
717 @LoginRequired()
718 @LoginRequired()
718 @NotAnonymous()
719 @NotAnonymous()
719 @view_config(
720 @view_config(
720 route_name='my_account_pullrequests',
721 route_name='my_account_pullrequests',
721 request_method='GET',
722 request_method='GET',
722 renderer='rhodecode:templates/admin/my_account/my_account.mako')
723 renderer='rhodecode:templates/admin/my_account/my_account.mako')
723 def my_account_pullrequests(self):
724 def my_account_pullrequests(self):
724 c = self.load_default_context()
725 c = self.load_default_context()
725 c.active = 'pullrequests'
726 c.active = 'pullrequests'
726 req_get = self.request.GET
727 req_get = self.request.GET
727
728
728 c.closed = str2bool(req_get.get('pr_show_closed'))
729 c.closed = str2bool(req_get.get('pr_show_closed'))
729
730
730 return self._get_template_context(c)
731 return self._get_template_context(c)
731
732
732 @LoginRequired()
733 @LoginRequired()
733 @NotAnonymous()
734 @NotAnonymous()
734 @view_config(
735 @view_config(
735 route_name='my_account_pullrequests_data',
736 route_name='my_account_pullrequests_data',
736 request_method='GET', renderer='json_ext')
737 request_method='GET', renderer='json_ext')
737 def my_account_pullrequests_data(self):
738 def my_account_pullrequests_data(self):
738 self.load_default_context()
739 self.load_default_context()
739 req_get = self.request.GET
740 req_get = self.request.GET
740 closed = str2bool(req_get.get('closed'))
741 closed = str2bool(req_get.get('closed'))
741
742
742 statuses = [PullRequest.STATUS_NEW, PullRequest.STATUS_OPEN]
743 statuses = [PullRequest.STATUS_NEW, PullRequest.STATUS_OPEN]
743 if closed:
744 if closed:
744 statuses += [PullRequest.STATUS_CLOSED]
745 statuses += [PullRequest.STATUS_CLOSED]
745
746
746 data = self._get_pull_requests_list(statuses=statuses)
747 data = self._get_pull_requests_list(statuses=statuses)
747 return data
748 return data
748
749
749 @LoginRequired()
750 @LoginRequired()
750 @NotAnonymous()
751 @NotAnonymous()
751 @view_config(
752 @view_config(
752 route_name='my_account_user_group_membership',
753 route_name='my_account_user_group_membership',
753 request_method='GET',
754 request_method='GET',
754 renderer='rhodecode:templates/admin/my_account/my_account.mako')
755 renderer='rhodecode:templates/admin/my_account/my_account.mako')
755 def my_account_user_group_membership(self):
756 def my_account_user_group_membership(self):
756 c = self.load_default_context()
757 c = self.load_default_context()
757 c.active = 'user_group_membership'
758 c.active = 'user_group_membership'
758 groups = [UserGroupModel.get_user_groups_as_dict(group.users_group)
759 groups = [UserGroupModel.get_user_groups_as_dict(group.users_group)
759 for group in self._rhodecode_db_user.group_member]
760 for group in self._rhodecode_db_user.group_member]
760 c.user_groups = json.dumps(groups)
761 c.user_groups = json.dumps(groups)
761 return self._get_template_context(c)
762 return self._get_template_context(c)
@@ -1,1481 +1,1482 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2019 RhodeCode GmbH
3 # Copyright (C) 2011-2019 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)
28 HTTPFound, HTTPNotFound, HTTPForbidden, HTTPBadRequest)
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.ext_json import json
37 from rhodecode.lib.ext_json import json
38 from rhodecode.lib.auth import (
38 from rhodecode.lib.auth import (
39 LoginRequired, HasRepoPermissionAny, HasRepoPermissionAnyDecorator,
39 LoginRequired, HasRepoPermissionAny, HasRepoPermissionAnyDecorator,
40 NotAnonymous, CSRFRequired)
40 NotAnonymous, CSRFRequired)
41 from rhodecode.lib.utils2 import str2bool, safe_str, safe_unicode
41 from rhodecode.lib.utils2 import str2bool, safe_str, safe_unicode
42 from rhodecode.lib.vcs.backends.base import EmptyCommit, UpdateFailureReason
42 from rhodecode.lib.vcs.backends.base import EmptyCommit, UpdateFailureReason
43 from rhodecode.lib.vcs.exceptions import (CommitDoesNotExistError,
43 from rhodecode.lib.vcs.exceptions import (CommitDoesNotExistError,
44 RepositoryRequirementError, EmptyRepositoryError)
44 RepositoryRequirementError, EmptyRepositoryError)
45 from rhodecode.model.changeset_status import ChangesetStatusModel
45 from rhodecode.model.changeset_status import ChangesetStatusModel
46 from rhodecode.model.comment import CommentsModel
46 from rhodecode.model.comment import CommentsModel
47 from rhodecode.model.db import (func, or_, PullRequest, PullRequestVersion,
47 from rhodecode.model.db import (func, or_, PullRequest, PullRequestVersion,
48 ChangesetComment, ChangesetStatus, Repository)
48 ChangesetComment, ChangesetStatus, Repository)
49 from rhodecode.model.forms import PullRequestForm
49 from rhodecode.model.forms import PullRequestForm
50 from rhodecode.model.meta import Session
50 from rhodecode.model.meta import Session
51 from rhodecode.model.pull_request import PullRequestModel, MergeCheck
51 from rhodecode.model.pull_request import PullRequestModel, MergeCheck
52 from rhodecode.model.scm import ScmModel
52 from rhodecode.model.scm import ScmModel
53
53
54 log = logging.getLogger(__name__)
54 log = logging.getLogger(__name__)
55
55
56
56
57 class RepoPullRequestsView(RepoAppView, DataGridAppView):
57 class RepoPullRequestsView(RepoAppView, DataGridAppView):
58
58
59 def load_default_context(self):
59 def load_default_context(self):
60 c = self._get_local_tmpl_context(include_app_defaults=True)
60 c = self._get_local_tmpl_context(include_app_defaults=True)
61 c.REVIEW_STATUS_APPROVED = ChangesetStatus.STATUS_APPROVED
61 c.REVIEW_STATUS_APPROVED = ChangesetStatus.STATUS_APPROVED
62 c.REVIEW_STATUS_REJECTED = ChangesetStatus.STATUS_REJECTED
62 c.REVIEW_STATUS_REJECTED = ChangesetStatus.STATUS_REJECTED
63 # backward compat., we use for OLD PRs a plain renderer
63 # backward compat., we use for OLD PRs a plain renderer
64 c.renderer = 'plain'
64 c.renderer = 'plain'
65 return c
65 return c
66
66
67 def _get_pull_requests_list(
67 def _get_pull_requests_list(
68 self, repo_name, source, filter_type, opened_by, statuses):
68 self, repo_name, source, filter_type, opened_by, statuses):
69
69
70 draw, start, limit = self._extract_chunk(self.request)
70 draw, start, limit = self._extract_chunk(self.request)
71 search_q, order_by, order_dir = self._extract_ordering(self.request)
71 search_q, order_by, order_dir = self._extract_ordering(self.request)
72 _render = self.request.get_partial_renderer(
72 _render = self.request.get_partial_renderer(
73 'rhodecode:templates/data_table/_dt_elements.mako')
73 'rhodecode:templates/data_table/_dt_elements.mako')
74
74
75 # pagination
75 # pagination
76
76
77 if filter_type == 'awaiting_review':
77 if filter_type == 'awaiting_review':
78 pull_requests = PullRequestModel().get_awaiting_review(
78 pull_requests = PullRequestModel().get_awaiting_review(
79 repo_name, search_q=search_q, source=source, opened_by=opened_by,
79 repo_name, search_q=search_q, source=source, opened_by=opened_by,
80 statuses=statuses, offset=start, length=limit,
80 statuses=statuses, offset=start, length=limit,
81 order_by=order_by, order_dir=order_dir)
81 order_by=order_by, order_dir=order_dir)
82 pull_requests_total_count = PullRequestModel().count_awaiting_review(
82 pull_requests_total_count = PullRequestModel().count_awaiting_review(
83 repo_name, search_q=search_q, source=source, statuses=statuses,
83 repo_name, search_q=search_q, source=source, statuses=statuses,
84 opened_by=opened_by)
84 opened_by=opened_by)
85 elif filter_type == 'awaiting_my_review':
85 elif filter_type == 'awaiting_my_review':
86 pull_requests = PullRequestModel().get_awaiting_my_review(
86 pull_requests = PullRequestModel().get_awaiting_my_review(
87 repo_name, search_q=search_q, source=source, opened_by=opened_by,
87 repo_name, search_q=search_q, source=source, opened_by=opened_by,
88 user_id=self._rhodecode_user.user_id, statuses=statuses,
88 user_id=self._rhodecode_user.user_id, statuses=statuses,
89 offset=start, length=limit, order_by=order_by,
89 offset=start, length=limit, order_by=order_by,
90 order_dir=order_dir)
90 order_dir=order_dir)
91 pull_requests_total_count = PullRequestModel().count_awaiting_my_review(
91 pull_requests_total_count = PullRequestModel().count_awaiting_my_review(
92 repo_name, search_q=search_q, source=source, user_id=self._rhodecode_user.user_id,
92 repo_name, search_q=search_q, source=source, user_id=self._rhodecode_user.user_id,
93 statuses=statuses, opened_by=opened_by)
93 statuses=statuses, opened_by=opened_by)
94 else:
94 else:
95 pull_requests = PullRequestModel().get_all(
95 pull_requests = PullRequestModel().get_all(
96 repo_name, search_q=search_q, source=source, opened_by=opened_by,
96 repo_name, search_q=search_q, source=source, opened_by=opened_by,
97 statuses=statuses, offset=start, length=limit,
97 statuses=statuses, offset=start, length=limit,
98 order_by=order_by, order_dir=order_dir)
98 order_by=order_by, order_dir=order_dir)
99 pull_requests_total_count = PullRequestModel().count_all(
99 pull_requests_total_count = PullRequestModel().count_all(
100 repo_name, search_q=search_q, source=source, statuses=statuses,
100 repo_name, search_q=search_q, source=source, statuses=statuses,
101 opened_by=opened_by)
101 opened_by=opened_by)
102
102
103 data = []
103 data = []
104 comments_model = CommentsModel()
104 comments_model = CommentsModel()
105 for pr in pull_requests:
105 for pr in pull_requests:
106 comments = comments_model.get_all_comments(
106 comments = comments_model.get_all_comments(
107 self.db_repo.repo_id, pull_request=pr)
107 self.db_repo.repo_id, pull_request=pr)
108
108
109 data.append({
109 data.append({
110 'name': _render('pullrequest_name',
110 'name': _render('pullrequest_name',
111 pr.pull_request_id, pr.target_repo.repo_name),
111 pr.pull_request_id, pr.work_in_progress,
112 pr.target_repo.repo_name),
112 'name_raw': pr.pull_request_id,
113 'name_raw': pr.pull_request_id,
113 'status': _render('pullrequest_status',
114 'status': _render('pullrequest_status',
114 pr.calculated_review_status()),
115 pr.calculated_review_status()),
115 'title': _render('pullrequest_title', pr.title, pr.description),
116 'title': _render('pullrequest_title', pr.title, pr.description),
116 'description': h.escape(pr.description),
117 'description': h.escape(pr.description),
117 'updated_on': _render('pullrequest_updated_on',
118 'updated_on': _render('pullrequest_updated_on',
118 h.datetime_to_time(pr.updated_on)),
119 h.datetime_to_time(pr.updated_on)),
119 'updated_on_raw': h.datetime_to_time(pr.updated_on),
120 'updated_on_raw': h.datetime_to_time(pr.updated_on),
120 'created_on': _render('pullrequest_updated_on',
121 'created_on': _render('pullrequest_updated_on',
121 h.datetime_to_time(pr.created_on)),
122 h.datetime_to_time(pr.created_on)),
122 'created_on_raw': h.datetime_to_time(pr.created_on),
123 'created_on_raw': h.datetime_to_time(pr.created_on),
123 'state': pr.pull_request_state,
124 'state': pr.pull_request_state,
124 'author': _render('pullrequest_author',
125 'author': _render('pullrequest_author',
125 pr.author.full_contact, ),
126 pr.author.full_contact, ),
126 'author_raw': pr.author.full_name,
127 'author_raw': pr.author.full_name,
127 'comments': _render('pullrequest_comments', len(comments)),
128 'comments': _render('pullrequest_comments', len(comments)),
128 'comments_raw': len(comments),
129 'comments_raw': len(comments),
129 'closed': pr.is_closed(),
130 'closed': pr.is_closed(),
130 })
131 })
131
132
132 data = ({
133 data = ({
133 'draw': draw,
134 'draw': draw,
134 'data': data,
135 'data': data,
135 'recordsTotal': pull_requests_total_count,
136 'recordsTotal': pull_requests_total_count,
136 'recordsFiltered': pull_requests_total_count,
137 'recordsFiltered': pull_requests_total_count,
137 })
138 })
138 return data
139 return data
139
140
140 @LoginRequired()
141 @LoginRequired()
141 @HasRepoPermissionAnyDecorator(
142 @HasRepoPermissionAnyDecorator(
142 'repository.read', 'repository.write', 'repository.admin')
143 'repository.read', 'repository.write', 'repository.admin')
143 @view_config(
144 @view_config(
144 route_name='pullrequest_show_all', request_method='GET',
145 route_name='pullrequest_show_all', request_method='GET',
145 renderer='rhodecode:templates/pullrequests/pullrequests.mako')
146 renderer='rhodecode:templates/pullrequests/pullrequests.mako')
146 def pull_request_list(self):
147 def pull_request_list(self):
147 c = self.load_default_context()
148 c = self.load_default_context()
148
149
149 req_get = self.request.GET
150 req_get = self.request.GET
150 c.source = str2bool(req_get.get('source'))
151 c.source = str2bool(req_get.get('source'))
151 c.closed = str2bool(req_get.get('closed'))
152 c.closed = str2bool(req_get.get('closed'))
152 c.my = str2bool(req_get.get('my'))
153 c.my = str2bool(req_get.get('my'))
153 c.awaiting_review = str2bool(req_get.get('awaiting_review'))
154 c.awaiting_review = str2bool(req_get.get('awaiting_review'))
154 c.awaiting_my_review = str2bool(req_get.get('awaiting_my_review'))
155 c.awaiting_my_review = str2bool(req_get.get('awaiting_my_review'))
155
156
156 c.active = 'open'
157 c.active = 'open'
157 if c.my:
158 if c.my:
158 c.active = 'my'
159 c.active = 'my'
159 if c.closed:
160 if c.closed:
160 c.active = 'closed'
161 c.active = 'closed'
161 if c.awaiting_review and not c.source:
162 if c.awaiting_review and not c.source:
162 c.active = 'awaiting'
163 c.active = 'awaiting'
163 if c.source and not c.awaiting_review:
164 if c.source and not c.awaiting_review:
164 c.active = 'source'
165 c.active = 'source'
165 if c.awaiting_my_review:
166 if c.awaiting_my_review:
166 c.active = 'awaiting_my'
167 c.active = 'awaiting_my'
167
168
168 return self._get_template_context(c)
169 return self._get_template_context(c)
169
170
170 @LoginRequired()
171 @LoginRequired()
171 @HasRepoPermissionAnyDecorator(
172 @HasRepoPermissionAnyDecorator(
172 'repository.read', 'repository.write', 'repository.admin')
173 'repository.read', 'repository.write', 'repository.admin')
173 @view_config(
174 @view_config(
174 route_name='pullrequest_show_all_data', request_method='GET',
175 route_name='pullrequest_show_all_data', request_method='GET',
175 renderer='json_ext', xhr=True)
176 renderer='json_ext', xhr=True)
176 def pull_request_list_data(self):
177 def pull_request_list_data(self):
177 self.load_default_context()
178 self.load_default_context()
178
179
179 # additional filters
180 # additional filters
180 req_get = self.request.GET
181 req_get = self.request.GET
181 source = str2bool(req_get.get('source'))
182 source = str2bool(req_get.get('source'))
182 closed = str2bool(req_get.get('closed'))
183 closed = str2bool(req_get.get('closed'))
183 my = str2bool(req_get.get('my'))
184 my = str2bool(req_get.get('my'))
184 awaiting_review = str2bool(req_get.get('awaiting_review'))
185 awaiting_review = str2bool(req_get.get('awaiting_review'))
185 awaiting_my_review = str2bool(req_get.get('awaiting_my_review'))
186 awaiting_my_review = str2bool(req_get.get('awaiting_my_review'))
186
187
187 filter_type = 'awaiting_review' if awaiting_review \
188 filter_type = 'awaiting_review' if awaiting_review \
188 else 'awaiting_my_review' if awaiting_my_review \
189 else 'awaiting_my_review' if awaiting_my_review \
189 else None
190 else None
190
191
191 opened_by = None
192 opened_by = None
192 if my:
193 if my:
193 opened_by = [self._rhodecode_user.user_id]
194 opened_by = [self._rhodecode_user.user_id]
194
195
195 statuses = [PullRequest.STATUS_NEW, PullRequest.STATUS_OPEN]
196 statuses = [PullRequest.STATUS_NEW, PullRequest.STATUS_OPEN]
196 if closed:
197 if closed:
197 statuses = [PullRequest.STATUS_CLOSED]
198 statuses = [PullRequest.STATUS_CLOSED]
198
199
199 data = self._get_pull_requests_list(
200 data = self._get_pull_requests_list(
200 repo_name=self.db_repo_name, source=source,
201 repo_name=self.db_repo_name, source=source,
201 filter_type=filter_type, opened_by=opened_by, statuses=statuses)
202 filter_type=filter_type, opened_by=opened_by, statuses=statuses)
202
203
203 return data
204 return data
204
205
205 def _is_diff_cache_enabled(self, target_repo):
206 def _is_diff_cache_enabled(self, target_repo):
206 caching_enabled = self._get_general_setting(
207 caching_enabled = self._get_general_setting(
207 target_repo, 'rhodecode_diff_cache')
208 target_repo, 'rhodecode_diff_cache')
208 log.debug('Diff caching enabled: %s', caching_enabled)
209 log.debug('Diff caching enabled: %s', caching_enabled)
209 return caching_enabled
210 return caching_enabled
210
211
211 def _get_diffset(self, source_repo_name, source_repo,
212 def _get_diffset(self, source_repo_name, source_repo,
212 source_ref_id, target_ref_id,
213 source_ref_id, target_ref_id,
213 target_commit, source_commit, diff_limit, file_limit,
214 target_commit, source_commit, diff_limit, file_limit,
214 fulldiff, hide_whitespace_changes, diff_context):
215 fulldiff, hide_whitespace_changes, diff_context):
215
216
216 vcs_diff = PullRequestModel().get_diff(
217 vcs_diff = PullRequestModel().get_diff(
217 source_repo, source_ref_id, target_ref_id,
218 source_repo, source_ref_id, target_ref_id,
218 hide_whitespace_changes, diff_context)
219 hide_whitespace_changes, diff_context)
219
220
220 diff_processor = diffs.DiffProcessor(
221 diff_processor = diffs.DiffProcessor(
221 vcs_diff, format='newdiff', diff_limit=diff_limit,
222 vcs_diff, format='newdiff', diff_limit=diff_limit,
222 file_limit=file_limit, show_full_diff=fulldiff)
223 file_limit=file_limit, show_full_diff=fulldiff)
223
224
224 _parsed = diff_processor.prepare()
225 _parsed = diff_processor.prepare()
225
226
226 diffset = codeblocks.DiffSet(
227 diffset = codeblocks.DiffSet(
227 repo_name=self.db_repo_name,
228 repo_name=self.db_repo_name,
228 source_repo_name=source_repo_name,
229 source_repo_name=source_repo_name,
229 source_node_getter=codeblocks.diffset_node_getter(target_commit),
230 source_node_getter=codeblocks.diffset_node_getter(target_commit),
230 target_node_getter=codeblocks.diffset_node_getter(source_commit),
231 target_node_getter=codeblocks.diffset_node_getter(source_commit),
231 )
232 )
232 diffset = self.path_filter.render_patchset_filtered(
233 diffset = self.path_filter.render_patchset_filtered(
233 diffset, _parsed, target_commit.raw_id, source_commit.raw_id)
234 diffset, _parsed, target_commit.raw_id, source_commit.raw_id)
234
235
235 return diffset
236 return diffset
236
237
237 def _get_range_diffset(self, source_scm, source_repo,
238 def _get_range_diffset(self, source_scm, source_repo,
238 commit1, commit2, diff_limit, file_limit,
239 commit1, commit2, diff_limit, file_limit,
239 fulldiff, hide_whitespace_changes, diff_context):
240 fulldiff, hide_whitespace_changes, diff_context):
240 vcs_diff = source_scm.get_diff(
241 vcs_diff = source_scm.get_diff(
241 commit1, commit2,
242 commit1, commit2,
242 ignore_whitespace=hide_whitespace_changes,
243 ignore_whitespace=hide_whitespace_changes,
243 context=diff_context)
244 context=diff_context)
244
245
245 diff_processor = diffs.DiffProcessor(
246 diff_processor = diffs.DiffProcessor(
246 vcs_diff, format='newdiff', diff_limit=diff_limit,
247 vcs_diff, format='newdiff', diff_limit=diff_limit,
247 file_limit=file_limit, show_full_diff=fulldiff)
248 file_limit=file_limit, show_full_diff=fulldiff)
248
249
249 _parsed = diff_processor.prepare()
250 _parsed = diff_processor.prepare()
250
251
251 diffset = codeblocks.DiffSet(
252 diffset = codeblocks.DiffSet(
252 repo_name=source_repo.repo_name,
253 repo_name=source_repo.repo_name,
253 source_node_getter=codeblocks.diffset_node_getter(commit1),
254 source_node_getter=codeblocks.diffset_node_getter(commit1),
254 target_node_getter=codeblocks.diffset_node_getter(commit2))
255 target_node_getter=codeblocks.diffset_node_getter(commit2))
255
256
256 diffset = self.path_filter.render_patchset_filtered(
257 diffset = self.path_filter.render_patchset_filtered(
257 diffset, _parsed, commit1.raw_id, commit2.raw_id)
258 diffset, _parsed, commit1.raw_id, commit2.raw_id)
258
259
259 return diffset
260 return diffset
260
261
261 @LoginRequired()
262 @LoginRequired()
262 @HasRepoPermissionAnyDecorator(
263 @HasRepoPermissionAnyDecorator(
263 'repository.read', 'repository.write', 'repository.admin')
264 'repository.read', 'repository.write', 'repository.admin')
264 @view_config(
265 @view_config(
265 route_name='pullrequest_show', request_method='GET',
266 route_name='pullrequest_show', request_method='GET',
266 renderer='rhodecode:templates/pullrequests/pullrequest_show.mako')
267 renderer='rhodecode:templates/pullrequests/pullrequest_show.mako')
267 def pull_request_show(self):
268 def pull_request_show(self):
268 _ = self.request.translate
269 _ = self.request.translate
269 c = self.load_default_context()
270 c = self.load_default_context()
270
271
271 pull_request = PullRequest.get_or_404(
272 pull_request = PullRequest.get_or_404(
272 self.request.matchdict['pull_request_id'])
273 self.request.matchdict['pull_request_id'])
273 pull_request_id = pull_request.pull_request_id
274 pull_request_id = pull_request.pull_request_id
274
275
275 if pull_request.pull_request_state != PullRequest.STATE_CREATED:
276 if pull_request.pull_request_state != PullRequest.STATE_CREATED:
276 log.debug('show: forbidden because pull request is in state %s',
277 log.debug('show: forbidden because pull request is in state %s',
277 pull_request.pull_request_state)
278 pull_request.pull_request_state)
278 msg = _(u'Cannot show pull requests in state other than `{}`. '
279 msg = _(u'Cannot show pull requests in state other than `{}`. '
279 u'Current state is: `{}`').format(PullRequest.STATE_CREATED,
280 u'Current state is: `{}`').format(PullRequest.STATE_CREATED,
280 pull_request.pull_request_state)
281 pull_request.pull_request_state)
281 h.flash(msg, category='error')
282 h.flash(msg, category='error')
282 raise HTTPFound(h.route_path('pullrequest_show_all',
283 raise HTTPFound(h.route_path('pullrequest_show_all',
283 repo_name=self.db_repo_name))
284 repo_name=self.db_repo_name))
284
285
285 version = self.request.GET.get('version')
286 version = self.request.GET.get('version')
286 from_version = self.request.GET.get('from_version') or version
287 from_version = self.request.GET.get('from_version') or version
287 merge_checks = self.request.GET.get('merge_checks')
288 merge_checks = self.request.GET.get('merge_checks')
288 c.fulldiff = str2bool(self.request.GET.get('fulldiff'))
289 c.fulldiff = str2bool(self.request.GET.get('fulldiff'))
289
290
290 # fetch global flags of ignore ws or context lines
291 # fetch global flags of ignore ws or context lines
291 diff_context = diffs.get_diff_context(self.request)
292 diff_context = diffs.get_diff_context(self.request)
292 hide_whitespace_changes = diffs.get_diff_whitespace_flag(self.request)
293 hide_whitespace_changes = diffs.get_diff_whitespace_flag(self.request)
293
294
294 force_refresh = str2bool(self.request.GET.get('force_refresh'))
295 force_refresh = str2bool(self.request.GET.get('force_refresh'))
295
296
296 (pull_request_latest,
297 (pull_request_latest,
297 pull_request_at_ver,
298 pull_request_at_ver,
298 pull_request_display_obj,
299 pull_request_display_obj,
299 at_version) = PullRequestModel().get_pr_version(
300 at_version) = PullRequestModel().get_pr_version(
300 pull_request_id, version=version)
301 pull_request_id, version=version)
301 pr_closed = pull_request_latest.is_closed()
302 pr_closed = pull_request_latest.is_closed()
302
303
303 if pr_closed and (version or from_version):
304 if pr_closed and (version or from_version):
304 # not allow to browse versions
305 # not allow to browse versions
305 raise HTTPFound(h.route_path(
306 raise HTTPFound(h.route_path(
306 'pullrequest_show', repo_name=self.db_repo_name,
307 'pullrequest_show', repo_name=self.db_repo_name,
307 pull_request_id=pull_request_id))
308 pull_request_id=pull_request_id))
308
309
309 versions = pull_request_display_obj.versions()
310 versions = pull_request_display_obj.versions()
310 # used to store per-commit range diffs
311 # used to store per-commit range diffs
311 c.changes = collections.OrderedDict()
312 c.changes = collections.OrderedDict()
312 c.range_diff_on = self.request.GET.get('range-diff') == "1"
313 c.range_diff_on = self.request.GET.get('range-diff') == "1"
313
314
314 c.at_version = at_version
315 c.at_version = at_version
315 c.at_version_num = (at_version
316 c.at_version_num = (at_version
316 if at_version and at_version != 'latest'
317 if at_version and at_version != 'latest'
317 else None)
318 else None)
318 c.at_version_pos = ChangesetComment.get_index_from_version(
319 c.at_version_pos = ChangesetComment.get_index_from_version(
319 c.at_version_num, versions)
320 c.at_version_num, versions)
320
321
321 (prev_pull_request_latest,
322 (prev_pull_request_latest,
322 prev_pull_request_at_ver,
323 prev_pull_request_at_ver,
323 prev_pull_request_display_obj,
324 prev_pull_request_display_obj,
324 prev_at_version) = PullRequestModel().get_pr_version(
325 prev_at_version) = PullRequestModel().get_pr_version(
325 pull_request_id, version=from_version)
326 pull_request_id, version=from_version)
326
327
327 c.from_version = prev_at_version
328 c.from_version = prev_at_version
328 c.from_version_num = (prev_at_version
329 c.from_version_num = (prev_at_version
329 if prev_at_version and prev_at_version != 'latest'
330 if prev_at_version and prev_at_version != 'latest'
330 else None)
331 else None)
331 c.from_version_pos = ChangesetComment.get_index_from_version(
332 c.from_version_pos = ChangesetComment.get_index_from_version(
332 c.from_version_num, versions)
333 c.from_version_num, versions)
333
334
334 # define if we're in COMPARE mode or VIEW at version mode
335 # define if we're in COMPARE mode or VIEW at version mode
335 compare = at_version != prev_at_version
336 compare = at_version != prev_at_version
336
337
337 # pull_requests repo_name we opened it against
338 # pull_requests repo_name we opened it against
338 # ie. target_repo must match
339 # ie. target_repo must match
339 if self.db_repo_name != pull_request_at_ver.target_repo.repo_name:
340 if self.db_repo_name != pull_request_at_ver.target_repo.repo_name:
340 raise HTTPNotFound()
341 raise HTTPNotFound()
341
342
342 c.shadow_clone_url = PullRequestModel().get_shadow_clone_url(
343 c.shadow_clone_url = PullRequestModel().get_shadow_clone_url(
343 pull_request_at_ver)
344 pull_request_at_ver)
344
345
345 c.pull_request = pull_request_display_obj
346 c.pull_request = pull_request_display_obj
346 c.renderer = pull_request_at_ver.description_renderer or c.renderer
347 c.renderer = pull_request_at_ver.description_renderer or c.renderer
347 c.pull_request_latest = pull_request_latest
348 c.pull_request_latest = pull_request_latest
348
349
349 if compare or (at_version and not at_version == 'latest'):
350 if compare or (at_version and not at_version == 'latest'):
350 c.allowed_to_change_status = False
351 c.allowed_to_change_status = False
351 c.allowed_to_update = False
352 c.allowed_to_update = False
352 c.allowed_to_merge = False
353 c.allowed_to_merge = False
353 c.allowed_to_delete = False
354 c.allowed_to_delete = False
354 c.allowed_to_comment = False
355 c.allowed_to_comment = False
355 c.allowed_to_close = False
356 c.allowed_to_close = False
356 else:
357 else:
357 can_change_status = PullRequestModel().check_user_change_status(
358 can_change_status = PullRequestModel().check_user_change_status(
358 pull_request_at_ver, self._rhodecode_user)
359 pull_request_at_ver, self._rhodecode_user)
359 c.allowed_to_change_status = can_change_status and not pr_closed
360 c.allowed_to_change_status = can_change_status and not pr_closed
360
361
361 c.allowed_to_update = PullRequestModel().check_user_update(
362 c.allowed_to_update = PullRequestModel().check_user_update(
362 pull_request_latest, self._rhodecode_user) and not pr_closed
363 pull_request_latest, self._rhodecode_user) and not pr_closed
363 c.allowed_to_merge = PullRequestModel().check_user_merge(
364 c.allowed_to_merge = PullRequestModel().check_user_merge(
364 pull_request_latest, self._rhodecode_user) and not pr_closed
365 pull_request_latest, self._rhodecode_user) and not pr_closed
365 c.allowed_to_delete = PullRequestModel().check_user_delete(
366 c.allowed_to_delete = PullRequestModel().check_user_delete(
366 pull_request_latest, self._rhodecode_user) and not pr_closed
367 pull_request_latest, self._rhodecode_user) and not pr_closed
367 c.allowed_to_comment = not pr_closed
368 c.allowed_to_comment = not pr_closed
368 c.allowed_to_close = c.allowed_to_merge and not pr_closed
369 c.allowed_to_close = c.allowed_to_merge and not pr_closed
369
370
370 c.forbid_adding_reviewers = False
371 c.forbid_adding_reviewers = False
371 c.forbid_author_to_review = False
372 c.forbid_author_to_review = False
372 c.forbid_commit_author_to_review = False
373 c.forbid_commit_author_to_review = False
373
374
374 if pull_request_latest.reviewer_data and \
375 if pull_request_latest.reviewer_data and \
375 'rules' in pull_request_latest.reviewer_data:
376 'rules' in pull_request_latest.reviewer_data:
376 rules = pull_request_latest.reviewer_data['rules'] or {}
377 rules = pull_request_latest.reviewer_data['rules'] or {}
377 try:
378 try:
378 c.forbid_adding_reviewers = rules.get(
379 c.forbid_adding_reviewers = rules.get(
379 'forbid_adding_reviewers')
380 'forbid_adding_reviewers')
380 c.forbid_author_to_review = rules.get(
381 c.forbid_author_to_review = rules.get(
381 'forbid_author_to_review')
382 'forbid_author_to_review')
382 c.forbid_commit_author_to_review = rules.get(
383 c.forbid_commit_author_to_review = rules.get(
383 'forbid_commit_author_to_review')
384 'forbid_commit_author_to_review')
384 except Exception:
385 except Exception:
385 pass
386 pass
386
387
387 # check merge capabilities
388 # check merge capabilities
388 _merge_check = MergeCheck.validate(
389 _merge_check = MergeCheck.validate(
389 pull_request_latest, auth_user=self._rhodecode_user,
390 pull_request_latest, auth_user=self._rhodecode_user,
390 translator=self.request.translate,
391 translator=self.request.translate,
391 force_shadow_repo_refresh=force_refresh)
392 force_shadow_repo_refresh=force_refresh)
392 c.pr_merge_errors = _merge_check.error_details
393 c.pr_merge_errors = _merge_check.error_details
393 c.pr_merge_possible = not _merge_check.failed
394 c.pr_merge_possible = not _merge_check.failed
394 c.pr_merge_message = _merge_check.merge_msg
395 c.pr_merge_message = _merge_check.merge_msg
395
396
396 c.pr_merge_info = MergeCheck.get_merge_conditions(
397 c.pr_merge_info = MergeCheck.get_merge_conditions(
397 pull_request_latest, translator=self.request.translate)
398 pull_request_latest, translator=self.request.translate)
398
399
399 c.pull_request_review_status = _merge_check.review_status
400 c.pull_request_review_status = _merge_check.review_status
400 if merge_checks:
401 if merge_checks:
401 self.request.override_renderer = \
402 self.request.override_renderer = \
402 'rhodecode:templates/pullrequests/pullrequest_merge_checks.mako'
403 'rhodecode:templates/pullrequests/pullrequest_merge_checks.mako'
403 return self._get_template_context(c)
404 return self._get_template_context(c)
404
405
405 comments_model = CommentsModel()
406 comments_model = CommentsModel()
406
407
407 # reviewers and statuses
408 # reviewers and statuses
408 c.pull_request_reviewers = pull_request_at_ver.reviewers_statuses()
409 c.pull_request_reviewers = pull_request_at_ver.reviewers_statuses()
409 allowed_reviewers = [x[0].user_id for x in c.pull_request_reviewers]
410 allowed_reviewers = [x[0].user_id for x in c.pull_request_reviewers]
410
411
411 # GENERAL COMMENTS with versions #
412 # GENERAL COMMENTS with versions #
412 q = comments_model._all_general_comments_of_pull_request(pull_request_latest)
413 q = comments_model._all_general_comments_of_pull_request(pull_request_latest)
413 q = q.order_by(ChangesetComment.comment_id.asc())
414 q = q.order_by(ChangesetComment.comment_id.asc())
414 general_comments = q
415 general_comments = q
415
416
416 # pick comments we want to render at current version
417 # pick comments we want to render at current version
417 c.comment_versions = comments_model.aggregate_comments(
418 c.comment_versions = comments_model.aggregate_comments(
418 general_comments, versions, c.at_version_num)
419 general_comments, versions, c.at_version_num)
419 c.comments = c.comment_versions[c.at_version_num]['until']
420 c.comments = c.comment_versions[c.at_version_num]['until']
420
421
421 # INLINE COMMENTS with versions #
422 # INLINE COMMENTS with versions #
422 q = comments_model._all_inline_comments_of_pull_request(pull_request_latest)
423 q = comments_model._all_inline_comments_of_pull_request(pull_request_latest)
423 q = q.order_by(ChangesetComment.comment_id.asc())
424 q = q.order_by(ChangesetComment.comment_id.asc())
424 inline_comments = q
425 inline_comments = q
425
426
426 c.inline_versions = comments_model.aggregate_comments(
427 c.inline_versions = comments_model.aggregate_comments(
427 inline_comments, versions, c.at_version_num, inline=True)
428 inline_comments, versions, c.at_version_num, inline=True)
428
429
429 # TODOs
430 # TODOs
430 c.unresolved_comments = CommentsModel() \
431 c.unresolved_comments = CommentsModel() \
431 .get_pull_request_unresolved_todos(pull_request)
432 .get_pull_request_unresolved_todos(pull_request)
432 c.resolved_comments = CommentsModel() \
433 c.resolved_comments = CommentsModel() \
433 .get_pull_request_resolved_todos(pull_request)
434 .get_pull_request_resolved_todos(pull_request)
434
435
435 # inject latest version
436 # inject latest version
436 latest_ver = PullRequest.get_pr_display_object(
437 latest_ver = PullRequest.get_pr_display_object(
437 pull_request_latest, pull_request_latest)
438 pull_request_latest, pull_request_latest)
438
439
439 c.versions = versions + [latest_ver]
440 c.versions = versions + [latest_ver]
440
441
441 # if we use version, then do not show later comments
442 # if we use version, then do not show later comments
442 # than current version
443 # than current version
443 display_inline_comments = collections.defaultdict(
444 display_inline_comments = collections.defaultdict(
444 lambda: collections.defaultdict(list))
445 lambda: collections.defaultdict(list))
445 for co in inline_comments:
446 for co in inline_comments:
446 if c.at_version_num:
447 if c.at_version_num:
447 # pick comments that are at least UPTO given version, so we
448 # pick comments that are at least UPTO given version, so we
448 # don't render comments for higher version
449 # don't render comments for higher version
449 should_render = co.pull_request_version_id and \
450 should_render = co.pull_request_version_id and \
450 co.pull_request_version_id <= c.at_version_num
451 co.pull_request_version_id <= c.at_version_num
451 else:
452 else:
452 # showing all, for 'latest'
453 # showing all, for 'latest'
453 should_render = True
454 should_render = True
454
455
455 if should_render:
456 if should_render:
456 display_inline_comments[co.f_path][co.line_no].append(co)
457 display_inline_comments[co.f_path][co.line_no].append(co)
457
458
458 # load diff data into template context, if we use compare mode then
459 # load diff data into template context, if we use compare mode then
459 # diff is calculated based on changes between versions of PR
460 # diff is calculated based on changes between versions of PR
460
461
461 source_repo = pull_request_at_ver.source_repo
462 source_repo = pull_request_at_ver.source_repo
462 source_ref_id = pull_request_at_ver.source_ref_parts.commit_id
463 source_ref_id = pull_request_at_ver.source_ref_parts.commit_id
463
464
464 target_repo = pull_request_at_ver.target_repo
465 target_repo = pull_request_at_ver.target_repo
465 target_ref_id = pull_request_at_ver.target_ref_parts.commit_id
466 target_ref_id = pull_request_at_ver.target_ref_parts.commit_id
466
467
467 if compare:
468 if compare:
468 # in compare switch the diff base to latest commit from prev version
469 # in compare switch the diff base to latest commit from prev version
469 target_ref_id = prev_pull_request_display_obj.revisions[0]
470 target_ref_id = prev_pull_request_display_obj.revisions[0]
470
471
471 # despite opening commits for bookmarks/branches/tags, we always
472 # despite opening commits for bookmarks/branches/tags, we always
472 # convert this to rev to prevent changes after bookmark or branch change
473 # convert this to rev to prevent changes after bookmark or branch change
473 c.source_ref_type = 'rev'
474 c.source_ref_type = 'rev'
474 c.source_ref = source_ref_id
475 c.source_ref = source_ref_id
475
476
476 c.target_ref_type = 'rev'
477 c.target_ref_type = 'rev'
477 c.target_ref = target_ref_id
478 c.target_ref = target_ref_id
478
479
479 c.source_repo = source_repo
480 c.source_repo = source_repo
480 c.target_repo = target_repo
481 c.target_repo = target_repo
481
482
482 c.commit_ranges = []
483 c.commit_ranges = []
483 source_commit = EmptyCommit()
484 source_commit = EmptyCommit()
484 target_commit = EmptyCommit()
485 target_commit = EmptyCommit()
485 c.missing_requirements = False
486 c.missing_requirements = False
486
487
487 source_scm = source_repo.scm_instance()
488 source_scm = source_repo.scm_instance()
488 target_scm = target_repo.scm_instance()
489 target_scm = target_repo.scm_instance()
489
490
490 shadow_scm = None
491 shadow_scm = None
491 try:
492 try:
492 shadow_scm = pull_request_latest.get_shadow_repo()
493 shadow_scm = pull_request_latest.get_shadow_repo()
493 except Exception:
494 except Exception:
494 log.debug('Failed to get shadow repo', exc_info=True)
495 log.debug('Failed to get shadow repo', exc_info=True)
495 # try first the existing source_repo, and then shadow
496 # try first the existing source_repo, and then shadow
496 # repo if we can obtain one
497 # repo if we can obtain one
497 commits_source_repo = source_scm or shadow_scm
498 commits_source_repo = source_scm or shadow_scm
498
499
499 c.commits_source_repo = commits_source_repo
500 c.commits_source_repo = commits_source_repo
500 c.ancestor = None # set it to None, to hide it from PR view
501 c.ancestor = None # set it to None, to hide it from PR view
501
502
502 # empty version means latest, so we keep this to prevent
503 # empty version means latest, so we keep this to prevent
503 # double caching
504 # double caching
504 version_normalized = version or 'latest'
505 version_normalized = version or 'latest'
505 from_version_normalized = from_version or 'latest'
506 from_version_normalized = from_version or 'latest'
506
507
507 cache_path = self.rhodecode_vcs_repo.get_create_shadow_cache_pr_path(target_repo)
508 cache_path = self.rhodecode_vcs_repo.get_create_shadow_cache_pr_path(target_repo)
508 cache_file_path = diff_cache_exist(
509 cache_file_path = diff_cache_exist(
509 cache_path, 'pull_request', pull_request_id, version_normalized,
510 cache_path, 'pull_request', pull_request_id, version_normalized,
510 from_version_normalized, source_ref_id, target_ref_id,
511 from_version_normalized, source_ref_id, target_ref_id,
511 hide_whitespace_changes, diff_context, c.fulldiff)
512 hide_whitespace_changes, diff_context, c.fulldiff)
512
513
513 caching_enabled = self._is_diff_cache_enabled(c.target_repo)
514 caching_enabled = self._is_diff_cache_enabled(c.target_repo)
514 force_recache = self.get_recache_flag()
515 force_recache = self.get_recache_flag()
515
516
516 cached_diff = None
517 cached_diff = None
517 if caching_enabled:
518 if caching_enabled:
518 cached_diff = load_cached_diff(cache_file_path)
519 cached_diff = load_cached_diff(cache_file_path)
519
520
520 has_proper_commit_cache = (
521 has_proper_commit_cache = (
521 cached_diff and cached_diff.get('commits')
522 cached_diff and cached_diff.get('commits')
522 and len(cached_diff.get('commits', [])) == 5
523 and len(cached_diff.get('commits', [])) == 5
523 and cached_diff.get('commits')[0]
524 and cached_diff.get('commits')[0]
524 and cached_diff.get('commits')[3])
525 and cached_diff.get('commits')[3])
525
526
526 if not force_recache and not c.range_diff_on and has_proper_commit_cache:
527 if not force_recache and not c.range_diff_on and has_proper_commit_cache:
527 diff_commit_cache = \
528 diff_commit_cache = \
528 (ancestor_commit, commit_cache, missing_requirements,
529 (ancestor_commit, commit_cache, missing_requirements,
529 source_commit, target_commit) = cached_diff['commits']
530 source_commit, target_commit) = cached_diff['commits']
530 else:
531 else:
531 diff_commit_cache = \
532 diff_commit_cache = \
532 (ancestor_commit, commit_cache, missing_requirements,
533 (ancestor_commit, commit_cache, missing_requirements,
533 source_commit, target_commit) = self.get_commits(
534 source_commit, target_commit) = self.get_commits(
534 commits_source_repo,
535 commits_source_repo,
535 pull_request_at_ver,
536 pull_request_at_ver,
536 source_commit,
537 source_commit,
537 source_ref_id,
538 source_ref_id,
538 source_scm,
539 source_scm,
539 target_commit,
540 target_commit,
540 target_ref_id,
541 target_ref_id,
541 target_scm)
542 target_scm)
542
543
543 # register our commit range
544 # register our commit range
544 for comm in commit_cache.values():
545 for comm in commit_cache.values():
545 c.commit_ranges.append(comm)
546 c.commit_ranges.append(comm)
546
547
547 c.missing_requirements = missing_requirements
548 c.missing_requirements = missing_requirements
548 c.ancestor_commit = ancestor_commit
549 c.ancestor_commit = ancestor_commit
549 c.statuses = source_repo.statuses(
550 c.statuses = source_repo.statuses(
550 [x.raw_id for x in c.commit_ranges])
551 [x.raw_id for x in c.commit_ranges])
551
552
552 # auto collapse if we have more than limit
553 # auto collapse if we have more than limit
553 collapse_limit = diffs.DiffProcessor._collapse_commits_over
554 collapse_limit = diffs.DiffProcessor._collapse_commits_over
554 c.collapse_all_commits = len(c.commit_ranges) > collapse_limit
555 c.collapse_all_commits = len(c.commit_ranges) > collapse_limit
555 c.compare_mode = compare
556 c.compare_mode = compare
556
557
557 # diff_limit is the old behavior, will cut off the whole diff
558 # diff_limit is the old behavior, will cut off the whole diff
558 # if the limit is applied otherwise will just hide the
559 # if the limit is applied otherwise will just hide the
559 # big files from the front-end
560 # big files from the front-end
560 diff_limit = c.visual.cut_off_limit_diff
561 diff_limit = c.visual.cut_off_limit_diff
561 file_limit = c.visual.cut_off_limit_file
562 file_limit = c.visual.cut_off_limit_file
562
563
563 c.missing_commits = False
564 c.missing_commits = False
564 if (c.missing_requirements
565 if (c.missing_requirements
565 or isinstance(source_commit, EmptyCommit)
566 or isinstance(source_commit, EmptyCommit)
566 or source_commit == target_commit):
567 or source_commit == target_commit):
567
568
568 c.missing_commits = True
569 c.missing_commits = True
569 else:
570 else:
570 c.inline_comments = display_inline_comments
571 c.inline_comments = display_inline_comments
571
572
572 has_proper_diff_cache = cached_diff and cached_diff.get('commits')
573 has_proper_diff_cache = cached_diff and cached_diff.get('commits')
573 if not force_recache and has_proper_diff_cache:
574 if not force_recache and has_proper_diff_cache:
574 c.diffset = cached_diff['diff']
575 c.diffset = cached_diff['diff']
575 (ancestor_commit, commit_cache, missing_requirements,
576 (ancestor_commit, commit_cache, missing_requirements,
576 source_commit, target_commit) = cached_diff['commits']
577 source_commit, target_commit) = cached_diff['commits']
577 else:
578 else:
578 c.diffset = self._get_diffset(
579 c.diffset = self._get_diffset(
579 c.source_repo.repo_name, commits_source_repo,
580 c.source_repo.repo_name, commits_source_repo,
580 source_ref_id, target_ref_id,
581 source_ref_id, target_ref_id,
581 target_commit, source_commit,
582 target_commit, source_commit,
582 diff_limit, file_limit, c.fulldiff,
583 diff_limit, file_limit, c.fulldiff,
583 hide_whitespace_changes, diff_context)
584 hide_whitespace_changes, diff_context)
584
585
585 # save cached diff
586 # save cached diff
586 if caching_enabled:
587 if caching_enabled:
587 cache_diff(cache_file_path, c.diffset, diff_commit_cache)
588 cache_diff(cache_file_path, c.diffset, diff_commit_cache)
588
589
589 c.limited_diff = c.diffset.limited_diff
590 c.limited_diff = c.diffset.limited_diff
590
591
591 # calculate removed files that are bound to comments
592 # calculate removed files that are bound to comments
592 comment_deleted_files = [
593 comment_deleted_files = [
593 fname for fname in display_inline_comments
594 fname for fname in display_inline_comments
594 if fname not in c.diffset.file_stats]
595 if fname not in c.diffset.file_stats]
595
596
596 c.deleted_files_comments = collections.defaultdict(dict)
597 c.deleted_files_comments = collections.defaultdict(dict)
597 for fname, per_line_comments in display_inline_comments.items():
598 for fname, per_line_comments in display_inline_comments.items():
598 if fname in comment_deleted_files:
599 if fname in comment_deleted_files:
599 c.deleted_files_comments[fname]['stats'] = 0
600 c.deleted_files_comments[fname]['stats'] = 0
600 c.deleted_files_comments[fname]['comments'] = list()
601 c.deleted_files_comments[fname]['comments'] = list()
601 for lno, comments in per_line_comments.items():
602 for lno, comments in per_line_comments.items():
602 c.deleted_files_comments[fname]['comments'].extend(comments)
603 c.deleted_files_comments[fname]['comments'].extend(comments)
603
604
604 # maybe calculate the range diff
605 # maybe calculate the range diff
605 if c.range_diff_on:
606 if c.range_diff_on:
606 # TODO(marcink): set whitespace/context
607 # TODO(marcink): set whitespace/context
607 context_lcl = 3
608 context_lcl = 3
608 ign_whitespace_lcl = False
609 ign_whitespace_lcl = False
609
610
610 for commit in c.commit_ranges:
611 for commit in c.commit_ranges:
611 commit2 = commit
612 commit2 = commit
612 commit1 = commit.first_parent
613 commit1 = commit.first_parent
613
614
614 range_diff_cache_file_path = diff_cache_exist(
615 range_diff_cache_file_path = diff_cache_exist(
615 cache_path, 'diff', commit.raw_id,
616 cache_path, 'diff', commit.raw_id,
616 ign_whitespace_lcl, context_lcl, c.fulldiff)
617 ign_whitespace_lcl, context_lcl, c.fulldiff)
617
618
618 cached_diff = None
619 cached_diff = None
619 if caching_enabled:
620 if caching_enabled:
620 cached_diff = load_cached_diff(range_diff_cache_file_path)
621 cached_diff = load_cached_diff(range_diff_cache_file_path)
621
622
622 has_proper_diff_cache = cached_diff and cached_diff.get('diff')
623 has_proper_diff_cache = cached_diff and cached_diff.get('diff')
623 if not force_recache and has_proper_diff_cache:
624 if not force_recache and has_proper_diff_cache:
624 diffset = cached_diff['diff']
625 diffset = cached_diff['diff']
625 else:
626 else:
626 diffset = self._get_range_diffset(
627 diffset = self._get_range_diffset(
627 source_scm, source_repo,
628 source_scm, source_repo,
628 commit1, commit2, diff_limit, file_limit,
629 commit1, commit2, diff_limit, file_limit,
629 c.fulldiff, ign_whitespace_lcl, context_lcl
630 c.fulldiff, ign_whitespace_lcl, context_lcl
630 )
631 )
631
632
632 # save cached diff
633 # save cached diff
633 if caching_enabled:
634 if caching_enabled:
634 cache_diff(range_diff_cache_file_path, diffset, None)
635 cache_diff(range_diff_cache_file_path, diffset, None)
635
636
636 c.changes[commit.raw_id] = diffset
637 c.changes[commit.raw_id] = diffset
637
638
638 # this is a hack to properly display links, when creating PR, the
639 # this is a hack to properly display links, when creating PR, the
639 # compare view and others uses different notation, and
640 # compare view and others uses different notation, and
640 # compare_commits.mako renders links based on the target_repo.
641 # compare_commits.mako renders links based on the target_repo.
641 # We need to swap that here to generate it properly on the html side
642 # We need to swap that here to generate it properly on the html side
642 c.target_repo = c.source_repo
643 c.target_repo = c.source_repo
643
644
644 c.commit_statuses = ChangesetStatus.STATUSES
645 c.commit_statuses = ChangesetStatus.STATUSES
645
646
646 c.show_version_changes = not pr_closed
647 c.show_version_changes = not pr_closed
647 if c.show_version_changes:
648 if c.show_version_changes:
648 cur_obj = pull_request_at_ver
649 cur_obj = pull_request_at_ver
649 prev_obj = prev_pull_request_at_ver
650 prev_obj = prev_pull_request_at_ver
650
651
651 old_commit_ids = prev_obj.revisions
652 old_commit_ids = prev_obj.revisions
652 new_commit_ids = cur_obj.revisions
653 new_commit_ids = cur_obj.revisions
653 commit_changes = PullRequestModel()._calculate_commit_id_changes(
654 commit_changes = PullRequestModel()._calculate_commit_id_changes(
654 old_commit_ids, new_commit_ids)
655 old_commit_ids, new_commit_ids)
655 c.commit_changes_summary = commit_changes
656 c.commit_changes_summary = commit_changes
656
657
657 # calculate the diff for commits between versions
658 # calculate the diff for commits between versions
658 c.commit_changes = []
659 c.commit_changes = []
659 mark = lambda cs, fw: list(
660 mark = lambda cs, fw: list(
660 h.itertools.izip_longest([], cs, fillvalue=fw))
661 h.itertools.izip_longest([], cs, fillvalue=fw))
661 for c_type, raw_id in mark(commit_changes.added, 'a') \
662 for c_type, raw_id in mark(commit_changes.added, 'a') \
662 + mark(commit_changes.removed, 'r') \
663 + mark(commit_changes.removed, 'r') \
663 + mark(commit_changes.common, 'c'):
664 + mark(commit_changes.common, 'c'):
664
665
665 if raw_id in commit_cache:
666 if raw_id in commit_cache:
666 commit = commit_cache[raw_id]
667 commit = commit_cache[raw_id]
667 else:
668 else:
668 try:
669 try:
669 commit = commits_source_repo.get_commit(raw_id)
670 commit = commits_source_repo.get_commit(raw_id)
670 except CommitDoesNotExistError:
671 except CommitDoesNotExistError:
671 # in case we fail extracting still use "dummy" commit
672 # in case we fail extracting still use "dummy" commit
672 # for display in commit diff
673 # for display in commit diff
673 commit = h.AttributeDict(
674 commit = h.AttributeDict(
674 {'raw_id': raw_id,
675 {'raw_id': raw_id,
675 'message': 'EMPTY or MISSING COMMIT'})
676 'message': 'EMPTY or MISSING COMMIT'})
676 c.commit_changes.append([c_type, commit])
677 c.commit_changes.append([c_type, commit])
677
678
678 # current user review statuses for each version
679 # current user review statuses for each version
679 c.review_versions = {}
680 c.review_versions = {}
680 if self._rhodecode_user.user_id in allowed_reviewers:
681 if self._rhodecode_user.user_id in allowed_reviewers:
681 for co in general_comments:
682 for co in general_comments:
682 if co.author.user_id == self._rhodecode_user.user_id:
683 if co.author.user_id == self._rhodecode_user.user_id:
683 status = co.status_change
684 status = co.status_change
684 if status:
685 if status:
685 _ver_pr = status[0].comment.pull_request_version_id
686 _ver_pr = status[0].comment.pull_request_version_id
686 c.review_versions[_ver_pr] = status[0]
687 c.review_versions[_ver_pr] = status[0]
687
688
688 return self._get_template_context(c)
689 return self._get_template_context(c)
689
690
690 def get_commits(
691 def get_commits(
691 self, commits_source_repo, pull_request_at_ver, source_commit,
692 self, commits_source_repo, pull_request_at_ver, source_commit,
692 source_ref_id, source_scm, target_commit, target_ref_id, target_scm):
693 source_ref_id, source_scm, target_commit, target_ref_id, target_scm):
693 commit_cache = collections.OrderedDict()
694 commit_cache = collections.OrderedDict()
694 missing_requirements = False
695 missing_requirements = False
695 try:
696 try:
696 pre_load = ["author", "date", "message", "branch", "parents"]
697 pre_load = ["author", "date", "message", "branch", "parents"]
697 show_revs = pull_request_at_ver.revisions
698 show_revs = pull_request_at_ver.revisions
698 for rev in show_revs:
699 for rev in show_revs:
699 comm = commits_source_repo.get_commit(
700 comm = commits_source_repo.get_commit(
700 commit_id=rev, pre_load=pre_load)
701 commit_id=rev, pre_load=pre_load)
701 commit_cache[comm.raw_id] = comm
702 commit_cache[comm.raw_id] = comm
702
703
703 # Order here matters, we first need to get target, and then
704 # Order here matters, we first need to get target, and then
704 # the source
705 # the source
705 target_commit = commits_source_repo.get_commit(
706 target_commit = commits_source_repo.get_commit(
706 commit_id=safe_str(target_ref_id))
707 commit_id=safe_str(target_ref_id))
707
708
708 source_commit = commits_source_repo.get_commit(
709 source_commit = commits_source_repo.get_commit(
709 commit_id=safe_str(source_ref_id))
710 commit_id=safe_str(source_ref_id))
710 except CommitDoesNotExistError:
711 except CommitDoesNotExistError:
711 log.warning(
712 log.warning(
712 'Failed to get commit from `{}` repo'.format(
713 'Failed to get commit from `{}` repo'.format(
713 commits_source_repo), exc_info=True)
714 commits_source_repo), exc_info=True)
714 except RepositoryRequirementError:
715 except RepositoryRequirementError:
715 log.warning(
716 log.warning(
716 'Failed to get all required data from repo', exc_info=True)
717 'Failed to get all required data from repo', exc_info=True)
717 missing_requirements = True
718 missing_requirements = True
718 ancestor_commit = None
719 ancestor_commit = None
719 try:
720 try:
720 ancestor_id = source_scm.get_common_ancestor(
721 ancestor_id = source_scm.get_common_ancestor(
721 source_commit.raw_id, target_commit.raw_id, target_scm)
722 source_commit.raw_id, target_commit.raw_id, target_scm)
722 ancestor_commit = source_scm.get_commit(ancestor_id)
723 ancestor_commit = source_scm.get_commit(ancestor_id)
723 except Exception:
724 except Exception:
724 ancestor_commit = None
725 ancestor_commit = None
725 return ancestor_commit, commit_cache, missing_requirements, source_commit, target_commit
726 return ancestor_commit, commit_cache, missing_requirements, source_commit, target_commit
726
727
727 def assure_not_empty_repo(self):
728 def assure_not_empty_repo(self):
728 _ = self.request.translate
729 _ = self.request.translate
729
730
730 try:
731 try:
731 self.db_repo.scm_instance().get_commit()
732 self.db_repo.scm_instance().get_commit()
732 except EmptyRepositoryError:
733 except EmptyRepositoryError:
733 h.flash(h.literal(_('There are no commits yet')),
734 h.flash(h.literal(_('There are no commits yet')),
734 category='warning')
735 category='warning')
735 raise HTTPFound(
736 raise HTTPFound(
736 h.route_path('repo_summary', repo_name=self.db_repo.repo_name))
737 h.route_path('repo_summary', repo_name=self.db_repo.repo_name))
737
738
738 @LoginRequired()
739 @LoginRequired()
739 @NotAnonymous()
740 @NotAnonymous()
740 @HasRepoPermissionAnyDecorator(
741 @HasRepoPermissionAnyDecorator(
741 'repository.read', 'repository.write', 'repository.admin')
742 'repository.read', 'repository.write', 'repository.admin')
742 @view_config(
743 @view_config(
743 route_name='pullrequest_new', request_method='GET',
744 route_name='pullrequest_new', request_method='GET',
744 renderer='rhodecode:templates/pullrequests/pullrequest.mako')
745 renderer='rhodecode:templates/pullrequests/pullrequest.mako')
745 def pull_request_new(self):
746 def pull_request_new(self):
746 _ = self.request.translate
747 _ = self.request.translate
747 c = self.load_default_context()
748 c = self.load_default_context()
748
749
749 self.assure_not_empty_repo()
750 self.assure_not_empty_repo()
750 source_repo = self.db_repo
751 source_repo = self.db_repo
751
752
752 commit_id = self.request.GET.get('commit')
753 commit_id = self.request.GET.get('commit')
753 branch_ref = self.request.GET.get('branch')
754 branch_ref = self.request.GET.get('branch')
754 bookmark_ref = self.request.GET.get('bookmark')
755 bookmark_ref = self.request.GET.get('bookmark')
755
756
756 try:
757 try:
757 source_repo_data = PullRequestModel().generate_repo_data(
758 source_repo_data = PullRequestModel().generate_repo_data(
758 source_repo, commit_id=commit_id,
759 source_repo, commit_id=commit_id,
759 branch=branch_ref, bookmark=bookmark_ref,
760 branch=branch_ref, bookmark=bookmark_ref,
760 translator=self.request.translate)
761 translator=self.request.translate)
761 except CommitDoesNotExistError as e:
762 except CommitDoesNotExistError as e:
762 log.exception(e)
763 log.exception(e)
763 h.flash(_('Commit does not exist'), 'error')
764 h.flash(_('Commit does not exist'), 'error')
764 raise HTTPFound(
765 raise HTTPFound(
765 h.route_path('pullrequest_new', repo_name=source_repo.repo_name))
766 h.route_path('pullrequest_new', repo_name=source_repo.repo_name))
766
767
767 default_target_repo = source_repo
768 default_target_repo = source_repo
768
769
769 if source_repo.parent and c.has_origin_repo_read_perm:
770 if source_repo.parent and c.has_origin_repo_read_perm:
770 parent_vcs_obj = source_repo.parent.scm_instance()
771 parent_vcs_obj = source_repo.parent.scm_instance()
771 if parent_vcs_obj and not parent_vcs_obj.is_empty():
772 if parent_vcs_obj and not parent_vcs_obj.is_empty():
772 # change default if we have a parent repo
773 # change default if we have a parent repo
773 default_target_repo = source_repo.parent
774 default_target_repo = source_repo.parent
774
775
775 target_repo_data = PullRequestModel().generate_repo_data(
776 target_repo_data = PullRequestModel().generate_repo_data(
776 default_target_repo, translator=self.request.translate)
777 default_target_repo, translator=self.request.translate)
777
778
778 selected_source_ref = source_repo_data['refs']['selected_ref']
779 selected_source_ref = source_repo_data['refs']['selected_ref']
779 title_source_ref = ''
780 title_source_ref = ''
780 if selected_source_ref:
781 if selected_source_ref:
781 title_source_ref = selected_source_ref.split(':', 2)[1]
782 title_source_ref = selected_source_ref.split(':', 2)[1]
782 c.default_title = PullRequestModel().generate_pullrequest_title(
783 c.default_title = PullRequestModel().generate_pullrequest_title(
783 source=source_repo.repo_name,
784 source=source_repo.repo_name,
784 source_ref=title_source_ref,
785 source_ref=title_source_ref,
785 target=default_target_repo.repo_name
786 target=default_target_repo.repo_name
786 )
787 )
787
788
788 c.default_repo_data = {
789 c.default_repo_data = {
789 'source_repo_name': source_repo.repo_name,
790 'source_repo_name': source_repo.repo_name,
790 'source_refs_json': json.dumps(source_repo_data),
791 'source_refs_json': json.dumps(source_repo_data),
791 'target_repo_name': default_target_repo.repo_name,
792 'target_repo_name': default_target_repo.repo_name,
792 'target_refs_json': json.dumps(target_repo_data),
793 'target_refs_json': json.dumps(target_repo_data),
793 }
794 }
794 c.default_source_ref = selected_source_ref
795 c.default_source_ref = selected_source_ref
795
796
796 return self._get_template_context(c)
797 return self._get_template_context(c)
797
798
798 @LoginRequired()
799 @LoginRequired()
799 @NotAnonymous()
800 @NotAnonymous()
800 @HasRepoPermissionAnyDecorator(
801 @HasRepoPermissionAnyDecorator(
801 'repository.read', 'repository.write', 'repository.admin')
802 'repository.read', 'repository.write', 'repository.admin')
802 @view_config(
803 @view_config(
803 route_name='pullrequest_repo_refs', request_method='GET',
804 route_name='pullrequest_repo_refs', request_method='GET',
804 renderer='json_ext', xhr=True)
805 renderer='json_ext', xhr=True)
805 def pull_request_repo_refs(self):
806 def pull_request_repo_refs(self):
806 self.load_default_context()
807 self.load_default_context()
807 target_repo_name = self.request.matchdict['target_repo_name']
808 target_repo_name = self.request.matchdict['target_repo_name']
808 repo = Repository.get_by_repo_name(target_repo_name)
809 repo = Repository.get_by_repo_name(target_repo_name)
809 if not repo:
810 if not repo:
810 raise HTTPNotFound()
811 raise HTTPNotFound()
811
812
812 target_perm = HasRepoPermissionAny(
813 target_perm = HasRepoPermissionAny(
813 'repository.read', 'repository.write', 'repository.admin')(
814 'repository.read', 'repository.write', 'repository.admin')(
814 target_repo_name)
815 target_repo_name)
815 if not target_perm:
816 if not target_perm:
816 raise HTTPNotFound()
817 raise HTTPNotFound()
817
818
818 return PullRequestModel().generate_repo_data(
819 return PullRequestModel().generate_repo_data(
819 repo, translator=self.request.translate)
820 repo, translator=self.request.translate)
820
821
821 @LoginRequired()
822 @LoginRequired()
822 @NotAnonymous()
823 @NotAnonymous()
823 @HasRepoPermissionAnyDecorator(
824 @HasRepoPermissionAnyDecorator(
824 'repository.read', 'repository.write', 'repository.admin')
825 'repository.read', 'repository.write', 'repository.admin')
825 @view_config(
826 @view_config(
826 route_name='pullrequest_repo_targets', request_method='GET',
827 route_name='pullrequest_repo_targets', request_method='GET',
827 renderer='json_ext', xhr=True)
828 renderer='json_ext', xhr=True)
828 def pullrequest_repo_targets(self):
829 def pullrequest_repo_targets(self):
829 _ = self.request.translate
830 _ = self.request.translate
830 filter_query = self.request.GET.get('query')
831 filter_query = self.request.GET.get('query')
831
832
832 # get the parents
833 # get the parents
833 parent_target_repos = []
834 parent_target_repos = []
834 if self.db_repo.parent:
835 if self.db_repo.parent:
835 parents_query = Repository.query() \
836 parents_query = Repository.query() \
836 .order_by(func.length(Repository.repo_name)) \
837 .order_by(func.length(Repository.repo_name)) \
837 .filter(Repository.fork_id == self.db_repo.parent.repo_id)
838 .filter(Repository.fork_id == self.db_repo.parent.repo_id)
838
839
839 if filter_query:
840 if filter_query:
840 ilike_expression = u'%{}%'.format(safe_unicode(filter_query))
841 ilike_expression = u'%{}%'.format(safe_unicode(filter_query))
841 parents_query = parents_query.filter(
842 parents_query = parents_query.filter(
842 Repository.repo_name.ilike(ilike_expression))
843 Repository.repo_name.ilike(ilike_expression))
843 parents = parents_query.limit(20).all()
844 parents = parents_query.limit(20).all()
844
845
845 for parent in parents:
846 for parent in parents:
846 parent_vcs_obj = parent.scm_instance()
847 parent_vcs_obj = parent.scm_instance()
847 if parent_vcs_obj and not parent_vcs_obj.is_empty():
848 if parent_vcs_obj and not parent_vcs_obj.is_empty():
848 parent_target_repos.append(parent)
849 parent_target_repos.append(parent)
849
850
850 # get other forks, and repo itself
851 # get other forks, and repo itself
851 query = Repository.query() \
852 query = Repository.query() \
852 .order_by(func.length(Repository.repo_name)) \
853 .order_by(func.length(Repository.repo_name)) \
853 .filter(
854 .filter(
854 or_(Repository.repo_id == self.db_repo.repo_id, # repo itself
855 or_(Repository.repo_id == self.db_repo.repo_id, # repo itself
855 Repository.fork_id == self.db_repo.repo_id) # forks of this repo
856 Repository.fork_id == self.db_repo.repo_id) # forks of this repo
856 ) \
857 ) \
857 .filter(~Repository.repo_id.in_([x.repo_id for x in parent_target_repos]))
858 .filter(~Repository.repo_id.in_([x.repo_id for x in parent_target_repos]))
858
859
859 if filter_query:
860 if filter_query:
860 ilike_expression = u'%{}%'.format(safe_unicode(filter_query))
861 ilike_expression = u'%{}%'.format(safe_unicode(filter_query))
861 query = query.filter(Repository.repo_name.ilike(ilike_expression))
862 query = query.filter(Repository.repo_name.ilike(ilike_expression))
862
863
863 limit = max(20 - len(parent_target_repos), 5) # not less then 5
864 limit = max(20 - len(parent_target_repos), 5) # not less then 5
864 target_repos = query.limit(limit).all()
865 target_repos = query.limit(limit).all()
865
866
866 all_target_repos = target_repos + parent_target_repos
867 all_target_repos = target_repos + parent_target_repos
867
868
868 repos = []
869 repos = []
869 # This checks permissions to the repositories
870 # This checks permissions to the repositories
870 for obj in ScmModel().get_repos(all_target_repos):
871 for obj in ScmModel().get_repos(all_target_repos):
871 repos.append({
872 repos.append({
872 'id': obj['name'],
873 'id': obj['name'],
873 'text': obj['name'],
874 'text': obj['name'],
874 'type': 'repo',
875 'type': 'repo',
875 'repo_id': obj['dbrepo']['repo_id'],
876 'repo_id': obj['dbrepo']['repo_id'],
876 'repo_type': obj['dbrepo']['repo_type'],
877 'repo_type': obj['dbrepo']['repo_type'],
877 'private': obj['dbrepo']['private'],
878 'private': obj['dbrepo']['private'],
878
879
879 })
880 })
880
881
881 data = {
882 data = {
882 'more': False,
883 'more': False,
883 'results': [{
884 'results': [{
884 'text': _('Repositories'),
885 'text': _('Repositories'),
885 'children': repos
886 'children': repos
886 }] if repos else []
887 }] if repos else []
887 }
888 }
888 return data
889 return data
889
890
890 @LoginRequired()
891 @LoginRequired()
891 @NotAnonymous()
892 @NotAnonymous()
892 @HasRepoPermissionAnyDecorator(
893 @HasRepoPermissionAnyDecorator(
893 'repository.read', 'repository.write', 'repository.admin')
894 'repository.read', 'repository.write', 'repository.admin')
894 @CSRFRequired()
895 @CSRFRequired()
895 @view_config(
896 @view_config(
896 route_name='pullrequest_create', request_method='POST',
897 route_name='pullrequest_create', request_method='POST',
897 renderer=None)
898 renderer=None)
898 def pull_request_create(self):
899 def pull_request_create(self):
899 _ = self.request.translate
900 _ = self.request.translate
900 self.assure_not_empty_repo()
901 self.assure_not_empty_repo()
901 self.load_default_context()
902 self.load_default_context()
902
903
903 controls = peppercorn.parse(self.request.POST.items())
904 controls = peppercorn.parse(self.request.POST.items())
904
905
905 try:
906 try:
906 form = PullRequestForm(
907 form = PullRequestForm(
907 self.request.translate, self.db_repo.repo_id)()
908 self.request.translate, self.db_repo.repo_id)()
908 _form = form.to_python(controls)
909 _form = form.to_python(controls)
909 except formencode.Invalid as errors:
910 except formencode.Invalid as errors:
910 if errors.error_dict.get('revisions'):
911 if errors.error_dict.get('revisions'):
911 msg = 'Revisions: %s' % errors.error_dict['revisions']
912 msg = 'Revisions: %s' % errors.error_dict['revisions']
912 elif errors.error_dict.get('pullrequest_title'):
913 elif errors.error_dict.get('pullrequest_title'):
913 msg = errors.error_dict.get('pullrequest_title')
914 msg = errors.error_dict.get('pullrequest_title')
914 else:
915 else:
915 msg = _('Error creating pull request: {}').format(errors)
916 msg = _('Error creating pull request: {}').format(errors)
916 log.exception(msg)
917 log.exception(msg)
917 h.flash(msg, 'error')
918 h.flash(msg, 'error')
918
919
919 # would rather just go back to form ...
920 # would rather just go back to form ...
920 raise HTTPFound(
921 raise HTTPFound(
921 h.route_path('pullrequest_new', repo_name=self.db_repo_name))
922 h.route_path('pullrequest_new', repo_name=self.db_repo_name))
922
923
923 source_repo = _form['source_repo']
924 source_repo = _form['source_repo']
924 source_ref = _form['source_ref']
925 source_ref = _form['source_ref']
925 target_repo = _form['target_repo']
926 target_repo = _form['target_repo']
926 target_ref = _form['target_ref']
927 target_ref = _form['target_ref']
927 commit_ids = _form['revisions'][::-1]
928 commit_ids = _form['revisions'][::-1]
928
929
929 # find the ancestor for this pr
930 # find the ancestor for this pr
930 source_db_repo = Repository.get_by_repo_name(_form['source_repo'])
931 source_db_repo = Repository.get_by_repo_name(_form['source_repo'])
931 target_db_repo = Repository.get_by_repo_name(_form['target_repo'])
932 target_db_repo = Repository.get_by_repo_name(_form['target_repo'])
932
933
933 if not (source_db_repo or target_db_repo):
934 if not (source_db_repo or target_db_repo):
934 h.flash(_('source_repo or target repo not found'), category='error')
935 h.flash(_('source_repo or target repo not found'), category='error')
935 raise HTTPFound(
936 raise HTTPFound(
936 h.route_path('pullrequest_new', repo_name=self.db_repo_name))
937 h.route_path('pullrequest_new', repo_name=self.db_repo_name))
937
938
938 # re-check permissions again here
939 # re-check permissions again here
939 # source_repo we must have read permissions
940 # source_repo we must have read permissions
940
941
941 source_perm = HasRepoPermissionAny(
942 source_perm = HasRepoPermissionAny(
942 'repository.read', 'repository.write', 'repository.admin')(
943 'repository.read', 'repository.write', 'repository.admin')(
943 source_db_repo.repo_name)
944 source_db_repo.repo_name)
944 if not source_perm:
945 if not source_perm:
945 msg = _('Not Enough permissions to source repo `{}`.'.format(
946 msg = _('Not Enough permissions to source repo `{}`.'.format(
946 source_db_repo.repo_name))
947 source_db_repo.repo_name))
947 h.flash(msg, category='error')
948 h.flash(msg, category='error')
948 # copy the args back to redirect
949 # copy the args back to redirect
949 org_query = self.request.GET.mixed()
950 org_query = self.request.GET.mixed()
950 raise HTTPFound(
951 raise HTTPFound(
951 h.route_path('pullrequest_new', repo_name=self.db_repo_name,
952 h.route_path('pullrequest_new', repo_name=self.db_repo_name,
952 _query=org_query))
953 _query=org_query))
953
954
954 # target repo we must have read permissions, and also later on
955 # target repo we must have read permissions, and also later on
955 # we want to check branch permissions here
956 # we want to check branch permissions here
956 target_perm = HasRepoPermissionAny(
957 target_perm = HasRepoPermissionAny(
957 'repository.read', 'repository.write', 'repository.admin')(
958 'repository.read', 'repository.write', 'repository.admin')(
958 target_db_repo.repo_name)
959 target_db_repo.repo_name)
959 if not target_perm:
960 if not target_perm:
960 msg = _('Not Enough permissions to target repo `{}`.'.format(
961 msg = _('Not Enough permissions to target repo `{}`.'.format(
961 target_db_repo.repo_name))
962 target_db_repo.repo_name))
962 h.flash(msg, category='error')
963 h.flash(msg, category='error')
963 # copy the args back to redirect
964 # copy the args back to redirect
964 org_query = self.request.GET.mixed()
965 org_query = self.request.GET.mixed()
965 raise HTTPFound(
966 raise HTTPFound(
966 h.route_path('pullrequest_new', repo_name=self.db_repo_name,
967 h.route_path('pullrequest_new', repo_name=self.db_repo_name,
967 _query=org_query))
968 _query=org_query))
968
969
969 source_scm = source_db_repo.scm_instance()
970 source_scm = source_db_repo.scm_instance()
970 target_scm = target_db_repo.scm_instance()
971 target_scm = target_db_repo.scm_instance()
971
972
972 source_commit = source_scm.get_commit(source_ref.split(':')[-1])
973 source_commit = source_scm.get_commit(source_ref.split(':')[-1])
973 target_commit = target_scm.get_commit(target_ref.split(':')[-1])
974 target_commit = target_scm.get_commit(target_ref.split(':')[-1])
974
975
975 ancestor = source_scm.get_common_ancestor(
976 ancestor = source_scm.get_common_ancestor(
976 source_commit.raw_id, target_commit.raw_id, target_scm)
977 source_commit.raw_id, target_commit.raw_id, target_scm)
977
978
978 # recalculate target ref based on ancestor
979 # recalculate target ref based on ancestor
979 target_ref_type, target_ref_name, __ = _form['target_ref'].split(':')
980 target_ref_type, target_ref_name, __ = _form['target_ref'].split(':')
980 target_ref = ':'.join((target_ref_type, target_ref_name, ancestor))
981 target_ref = ':'.join((target_ref_type, target_ref_name, ancestor))
981
982
982 get_default_reviewers_data, validate_default_reviewers = \
983 get_default_reviewers_data, validate_default_reviewers = \
983 PullRequestModel().get_reviewer_functions()
984 PullRequestModel().get_reviewer_functions()
984
985
985 # recalculate reviewers logic, to make sure we can validate this
986 # recalculate reviewers logic, to make sure we can validate this
986 reviewer_rules = get_default_reviewers_data(
987 reviewer_rules = get_default_reviewers_data(
987 self._rhodecode_db_user, source_db_repo,
988 self._rhodecode_db_user, source_db_repo,
988 source_commit, target_db_repo, target_commit)
989 source_commit, target_db_repo, target_commit)
989
990
990 given_reviewers = _form['review_members']
991 given_reviewers = _form['review_members']
991 reviewers = validate_default_reviewers(
992 reviewers = validate_default_reviewers(
992 given_reviewers, reviewer_rules)
993 given_reviewers, reviewer_rules)
993
994
994 pullrequest_title = _form['pullrequest_title']
995 pullrequest_title = _form['pullrequest_title']
995 title_source_ref = source_ref.split(':', 2)[1]
996 title_source_ref = source_ref.split(':', 2)[1]
996 if not pullrequest_title:
997 if not pullrequest_title:
997 pullrequest_title = PullRequestModel().generate_pullrequest_title(
998 pullrequest_title = PullRequestModel().generate_pullrequest_title(
998 source=source_repo,
999 source=source_repo,
999 source_ref=title_source_ref,
1000 source_ref=title_source_ref,
1000 target=target_repo
1001 target=target_repo
1001 )
1002 )
1002
1003
1003 description = _form['pullrequest_desc']
1004 description = _form['pullrequest_desc']
1004 description_renderer = _form['description_renderer']
1005 description_renderer = _form['description_renderer']
1005
1006
1006 try:
1007 try:
1007 pull_request = PullRequestModel().create(
1008 pull_request = PullRequestModel().create(
1008 created_by=self._rhodecode_user.user_id,
1009 created_by=self._rhodecode_user.user_id,
1009 source_repo=source_repo,
1010 source_repo=source_repo,
1010 source_ref=source_ref,
1011 source_ref=source_ref,
1011 target_repo=target_repo,
1012 target_repo=target_repo,
1012 target_ref=target_ref,
1013 target_ref=target_ref,
1013 revisions=commit_ids,
1014 revisions=commit_ids,
1014 reviewers=reviewers,
1015 reviewers=reviewers,
1015 title=pullrequest_title,
1016 title=pullrequest_title,
1016 description=description,
1017 description=description,
1017 description_renderer=description_renderer,
1018 description_renderer=description_renderer,
1018 reviewer_data=reviewer_rules,
1019 reviewer_data=reviewer_rules,
1019 auth_user=self._rhodecode_user
1020 auth_user=self._rhodecode_user
1020 )
1021 )
1021 Session().commit()
1022 Session().commit()
1022
1023
1023 h.flash(_('Successfully opened new pull request'),
1024 h.flash(_('Successfully opened new pull request'),
1024 category='success')
1025 category='success')
1025 except Exception:
1026 except Exception:
1026 msg = _('Error occurred during creation of this pull request.')
1027 msg = _('Error occurred during creation of this pull request.')
1027 log.exception(msg)
1028 log.exception(msg)
1028 h.flash(msg, category='error')
1029 h.flash(msg, category='error')
1029
1030
1030 # copy the args back to redirect
1031 # copy the args back to redirect
1031 org_query = self.request.GET.mixed()
1032 org_query = self.request.GET.mixed()
1032 raise HTTPFound(
1033 raise HTTPFound(
1033 h.route_path('pullrequest_new', repo_name=self.db_repo_name,
1034 h.route_path('pullrequest_new', repo_name=self.db_repo_name,
1034 _query=org_query))
1035 _query=org_query))
1035
1036
1036 raise HTTPFound(
1037 raise HTTPFound(
1037 h.route_path('pullrequest_show', repo_name=target_repo,
1038 h.route_path('pullrequest_show', repo_name=target_repo,
1038 pull_request_id=pull_request.pull_request_id))
1039 pull_request_id=pull_request.pull_request_id))
1039
1040
1040 @LoginRequired()
1041 @LoginRequired()
1041 @NotAnonymous()
1042 @NotAnonymous()
1042 @HasRepoPermissionAnyDecorator(
1043 @HasRepoPermissionAnyDecorator(
1043 'repository.read', 'repository.write', 'repository.admin')
1044 'repository.read', 'repository.write', 'repository.admin')
1044 @CSRFRequired()
1045 @CSRFRequired()
1045 @view_config(
1046 @view_config(
1046 route_name='pullrequest_update', request_method='POST',
1047 route_name='pullrequest_update', request_method='POST',
1047 renderer='json_ext')
1048 renderer='json_ext')
1048 def pull_request_update(self):
1049 def pull_request_update(self):
1049 pull_request = PullRequest.get_or_404(
1050 pull_request = PullRequest.get_or_404(
1050 self.request.matchdict['pull_request_id'])
1051 self.request.matchdict['pull_request_id'])
1051 _ = self.request.translate
1052 _ = self.request.translate
1052
1053
1053 self.load_default_context()
1054 self.load_default_context()
1054 redirect_url = None
1055 redirect_url = None
1055
1056
1056 if pull_request.is_closed():
1057 if pull_request.is_closed():
1057 log.debug('update: forbidden because pull request is closed')
1058 log.debug('update: forbidden because pull request is closed')
1058 msg = _(u'Cannot update closed pull requests.')
1059 msg = _(u'Cannot update closed pull requests.')
1059 h.flash(msg, category='error')
1060 h.flash(msg, category='error')
1060 return {'response': True,
1061 return {'response': True,
1061 'redirect_url': redirect_url}
1062 'redirect_url': redirect_url}
1062
1063
1063 if pull_request.pull_request_state != PullRequest.STATE_CREATED:
1064 if pull_request.pull_request_state != PullRequest.STATE_CREATED:
1064 log.debug('update: forbidden because pull request is in state %s',
1065 log.debug('update: forbidden because pull request is in state %s',
1065 pull_request.pull_request_state)
1066 pull_request.pull_request_state)
1066 msg = _(u'Cannot update pull requests in state other than `{}`. '
1067 msg = _(u'Cannot update pull requests in state other than `{}`. '
1067 u'Current state is: `{}`').format(PullRequest.STATE_CREATED,
1068 u'Current state is: `{}`').format(PullRequest.STATE_CREATED,
1068 pull_request.pull_request_state)
1069 pull_request.pull_request_state)
1069 h.flash(msg, category='error')
1070 h.flash(msg, category='error')
1070 return {'response': True,
1071 return {'response': True,
1071 'redirect_url': redirect_url}
1072 'redirect_url': redirect_url}
1072
1073
1073 # only owner or admin can update it
1074 # only owner or admin can update it
1074 allowed_to_update = PullRequestModel().check_user_update(
1075 allowed_to_update = PullRequestModel().check_user_update(
1075 pull_request, self._rhodecode_user)
1076 pull_request, self._rhodecode_user)
1076 if allowed_to_update:
1077 if allowed_to_update:
1077 controls = peppercorn.parse(self.request.POST.items())
1078 controls = peppercorn.parse(self.request.POST.items())
1078 force_refresh = str2bool(self.request.POST.get('force_refresh'))
1079 force_refresh = str2bool(self.request.POST.get('force_refresh'))
1079
1080
1080 if 'review_members' in controls:
1081 if 'review_members' in controls:
1081 self._update_reviewers(
1082 self._update_reviewers(
1082 pull_request, controls['review_members'],
1083 pull_request, controls['review_members'],
1083 pull_request.reviewer_data)
1084 pull_request.reviewer_data)
1084 elif str2bool(self.request.POST.get('update_commits', 'false')):
1085 elif str2bool(self.request.POST.get('update_commits', 'false')):
1085 self._update_commits(pull_request)
1086 self._update_commits(pull_request)
1086 if force_refresh:
1087 if force_refresh:
1087 redirect_url = h.route_path(
1088 redirect_url = h.route_path(
1088 'pullrequest_show', repo_name=self.db_repo_name,
1089 'pullrequest_show', repo_name=self.db_repo_name,
1089 pull_request_id=pull_request.pull_request_id,
1090 pull_request_id=pull_request.pull_request_id,
1090 _query={"force_refresh": 1})
1091 _query={"force_refresh": 1})
1091 elif str2bool(self.request.POST.get('edit_pull_request', 'false')):
1092 elif str2bool(self.request.POST.get('edit_pull_request', 'false')):
1092 self._edit_pull_request(pull_request)
1093 self._edit_pull_request(pull_request)
1093 else:
1094 else:
1094 raise HTTPBadRequest()
1095 raise HTTPBadRequest()
1095
1096
1096 return {'response': True,
1097 return {'response': True,
1097 'redirect_url': redirect_url}
1098 'redirect_url': redirect_url}
1098 raise HTTPForbidden()
1099 raise HTTPForbidden()
1099
1100
1100 def _edit_pull_request(self, pull_request):
1101 def _edit_pull_request(self, pull_request):
1101 _ = self.request.translate
1102 _ = self.request.translate
1102
1103
1103 try:
1104 try:
1104 PullRequestModel().edit(
1105 PullRequestModel().edit(
1105 pull_request,
1106 pull_request,
1106 self.request.POST.get('title'),
1107 self.request.POST.get('title'),
1107 self.request.POST.get('description'),
1108 self.request.POST.get('description'),
1108 self.request.POST.get('description_renderer'),
1109 self.request.POST.get('description_renderer'),
1109 self._rhodecode_user)
1110 self._rhodecode_user)
1110 except ValueError:
1111 except ValueError:
1111 msg = _(u'Cannot update closed pull requests.')
1112 msg = _(u'Cannot update closed pull requests.')
1112 h.flash(msg, category='error')
1113 h.flash(msg, category='error')
1113 return
1114 return
1114 else:
1115 else:
1115 Session().commit()
1116 Session().commit()
1116
1117
1117 msg = _(u'Pull request title & description updated.')
1118 msg = _(u'Pull request title & description updated.')
1118 h.flash(msg, category='success')
1119 h.flash(msg, category='success')
1119 return
1120 return
1120
1121
1121 def _update_commits(self, pull_request):
1122 def _update_commits(self, pull_request):
1122 _ = self.request.translate
1123 _ = self.request.translate
1123
1124
1124 with pull_request.set_state(PullRequest.STATE_UPDATING):
1125 with pull_request.set_state(PullRequest.STATE_UPDATING):
1125 resp = PullRequestModel().update_commits(pull_request)
1126 resp = PullRequestModel().update_commits(pull_request)
1126
1127
1127 if resp.executed:
1128 if resp.executed:
1128
1129
1129 if resp.target_changed and resp.source_changed:
1130 if resp.target_changed and resp.source_changed:
1130 changed = 'target and source repositories'
1131 changed = 'target and source repositories'
1131 elif resp.target_changed and not resp.source_changed:
1132 elif resp.target_changed and not resp.source_changed:
1132 changed = 'target repository'
1133 changed = 'target repository'
1133 elif not resp.target_changed and resp.source_changed:
1134 elif not resp.target_changed and resp.source_changed:
1134 changed = 'source repository'
1135 changed = 'source repository'
1135 else:
1136 else:
1136 changed = 'nothing'
1137 changed = 'nothing'
1137
1138
1138 msg = _(u'Pull request updated to "{source_commit_id}" with '
1139 msg = _(u'Pull request updated to "{source_commit_id}" with '
1139 u'{count_added} added, {count_removed} removed commits. '
1140 u'{count_added} added, {count_removed} removed commits. '
1140 u'Source of changes: {change_source}')
1141 u'Source of changes: {change_source}')
1141 msg = msg.format(
1142 msg = msg.format(
1142 source_commit_id=pull_request.source_ref_parts.commit_id,
1143 source_commit_id=pull_request.source_ref_parts.commit_id,
1143 count_added=len(resp.changes.added),
1144 count_added=len(resp.changes.added),
1144 count_removed=len(resp.changes.removed),
1145 count_removed=len(resp.changes.removed),
1145 change_source=changed)
1146 change_source=changed)
1146 h.flash(msg, category='success')
1147 h.flash(msg, category='success')
1147
1148
1148 channel = '/repo${}$/pr/{}'.format(
1149 channel = '/repo${}$/pr/{}'.format(
1149 pull_request.target_repo.repo_name, pull_request.pull_request_id)
1150 pull_request.target_repo.repo_name, pull_request.pull_request_id)
1150 message = msg + (
1151 message = msg + (
1151 ' - <a onclick="window.location.reload()">'
1152 ' - <a onclick="window.location.reload()">'
1152 '<strong>{}</strong></a>'.format(_('Reload page')))
1153 '<strong>{}</strong></a>'.format(_('Reload page')))
1153 channelstream.post_message(
1154 channelstream.post_message(
1154 channel, message, self._rhodecode_user.username,
1155 channel, message, self._rhodecode_user.username,
1155 registry=self.request.registry)
1156 registry=self.request.registry)
1156 else:
1157 else:
1157 msg = PullRequestModel.UPDATE_STATUS_MESSAGES[resp.reason]
1158 msg = PullRequestModel.UPDATE_STATUS_MESSAGES[resp.reason]
1158 warning_reasons = [
1159 warning_reasons = [
1159 UpdateFailureReason.NO_CHANGE,
1160 UpdateFailureReason.NO_CHANGE,
1160 UpdateFailureReason.WRONG_REF_TYPE,
1161 UpdateFailureReason.WRONG_REF_TYPE,
1161 ]
1162 ]
1162 category = 'warning' if resp.reason in warning_reasons else 'error'
1163 category = 'warning' if resp.reason in warning_reasons else 'error'
1163 h.flash(msg, category=category)
1164 h.flash(msg, category=category)
1164
1165
1165 @LoginRequired()
1166 @LoginRequired()
1166 @NotAnonymous()
1167 @NotAnonymous()
1167 @HasRepoPermissionAnyDecorator(
1168 @HasRepoPermissionAnyDecorator(
1168 'repository.read', 'repository.write', 'repository.admin')
1169 'repository.read', 'repository.write', 'repository.admin')
1169 @CSRFRequired()
1170 @CSRFRequired()
1170 @view_config(
1171 @view_config(
1171 route_name='pullrequest_merge', request_method='POST',
1172 route_name='pullrequest_merge', request_method='POST',
1172 renderer='json_ext')
1173 renderer='json_ext')
1173 def pull_request_merge(self):
1174 def pull_request_merge(self):
1174 """
1175 """
1175 Merge will perform a server-side merge of the specified
1176 Merge will perform a server-side merge of the specified
1176 pull request, if the pull request is approved and mergeable.
1177 pull request, if the pull request is approved and mergeable.
1177 After successful merging, the pull request is automatically
1178 After successful merging, the pull request is automatically
1178 closed, with a relevant comment.
1179 closed, with a relevant comment.
1179 """
1180 """
1180 pull_request = PullRequest.get_or_404(
1181 pull_request = PullRequest.get_or_404(
1181 self.request.matchdict['pull_request_id'])
1182 self.request.matchdict['pull_request_id'])
1182 _ = self.request.translate
1183 _ = self.request.translate
1183
1184
1184 if pull_request.pull_request_state != PullRequest.STATE_CREATED:
1185 if pull_request.pull_request_state != PullRequest.STATE_CREATED:
1185 log.debug('show: forbidden because pull request is in state %s',
1186 log.debug('show: forbidden because pull request is in state %s',
1186 pull_request.pull_request_state)
1187 pull_request.pull_request_state)
1187 msg = _(u'Cannot merge pull requests in state other than `{}`. '
1188 msg = _(u'Cannot merge pull requests in state other than `{}`. '
1188 u'Current state is: `{}`').format(PullRequest.STATE_CREATED,
1189 u'Current state is: `{}`').format(PullRequest.STATE_CREATED,
1189 pull_request.pull_request_state)
1190 pull_request.pull_request_state)
1190 h.flash(msg, category='error')
1191 h.flash(msg, category='error')
1191 raise HTTPFound(
1192 raise HTTPFound(
1192 h.route_path('pullrequest_show',
1193 h.route_path('pullrequest_show',
1193 repo_name=pull_request.target_repo.repo_name,
1194 repo_name=pull_request.target_repo.repo_name,
1194 pull_request_id=pull_request.pull_request_id))
1195 pull_request_id=pull_request.pull_request_id))
1195
1196
1196 self.load_default_context()
1197 self.load_default_context()
1197
1198
1198 with pull_request.set_state(PullRequest.STATE_UPDATING):
1199 with pull_request.set_state(PullRequest.STATE_UPDATING):
1199 check = MergeCheck.validate(
1200 check = MergeCheck.validate(
1200 pull_request, auth_user=self._rhodecode_user,
1201 pull_request, auth_user=self._rhodecode_user,
1201 translator=self.request.translate)
1202 translator=self.request.translate)
1202 merge_possible = not check.failed
1203 merge_possible = not check.failed
1203
1204
1204 for err_type, error_msg in check.errors:
1205 for err_type, error_msg in check.errors:
1205 h.flash(error_msg, category=err_type)
1206 h.flash(error_msg, category=err_type)
1206
1207
1207 if merge_possible:
1208 if merge_possible:
1208 log.debug("Pre-conditions checked, trying to merge.")
1209 log.debug("Pre-conditions checked, trying to merge.")
1209 extras = vcs_operation_context(
1210 extras = vcs_operation_context(
1210 self.request.environ, repo_name=pull_request.target_repo.repo_name,
1211 self.request.environ, repo_name=pull_request.target_repo.repo_name,
1211 username=self._rhodecode_db_user.username, action='push',
1212 username=self._rhodecode_db_user.username, action='push',
1212 scm=pull_request.target_repo.repo_type)
1213 scm=pull_request.target_repo.repo_type)
1213 with pull_request.set_state(PullRequest.STATE_UPDATING):
1214 with pull_request.set_state(PullRequest.STATE_UPDATING):
1214 self._merge_pull_request(
1215 self._merge_pull_request(
1215 pull_request, self._rhodecode_db_user, extras)
1216 pull_request, self._rhodecode_db_user, extras)
1216 else:
1217 else:
1217 log.debug("Pre-conditions failed, NOT merging.")
1218 log.debug("Pre-conditions failed, NOT merging.")
1218
1219
1219 raise HTTPFound(
1220 raise HTTPFound(
1220 h.route_path('pullrequest_show',
1221 h.route_path('pullrequest_show',
1221 repo_name=pull_request.target_repo.repo_name,
1222 repo_name=pull_request.target_repo.repo_name,
1222 pull_request_id=pull_request.pull_request_id))
1223 pull_request_id=pull_request.pull_request_id))
1223
1224
1224 def _merge_pull_request(self, pull_request, user, extras):
1225 def _merge_pull_request(self, pull_request, user, extras):
1225 _ = self.request.translate
1226 _ = self.request.translate
1226 merge_resp = PullRequestModel().merge_repo(pull_request, user, extras=extras)
1227 merge_resp = PullRequestModel().merge_repo(pull_request, user, extras=extras)
1227
1228
1228 if merge_resp.executed:
1229 if merge_resp.executed:
1229 log.debug("The merge was successful, closing the pull request.")
1230 log.debug("The merge was successful, closing the pull request.")
1230 PullRequestModel().close_pull_request(
1231 PullRequestModel().close_pull_request(
1231 pull_request.pull_request_id, user)
1232 pull_request.pull_request_id, user)
1232 Session().commit()
1233 Session().commit()
1233 msg = _('Pull request was successfully merged and closed.')
1234 msg = _('Pull request was successfully merged and closed.')
1234 h.flash(msg, category='success')
1235 h.flash(msg, category='success')
1235 else:
1236 else:
1236 log.debug(
1237 log.debug(
1237 "The merge was not successful. Merge response: %s", merge_resp)
1238 "The merge was not successful. Merge response: %s", merge_resp)
1238 msg = merge_resp.merge_status_message
1239 msg = merge_resp.merge_status_message
1239 h.flash(msg, category='error')
1240 h.flash(msg, category='error')
1240
1241
1241 def _update_reviewers(self, pull_request, review_members, reviewer_rules):
1242 def _update_reviewers(self, pull_request, review_members, reviewer_rules):
1242 _ = self.request.translate
1243 _ = self.request.translate
1243
1244
1244 get_default_reviewers_data, validate_default_reviewers = \
1245 get_default_reviewers_data, validate_default_reviewers = \
1245 PullRequestModel().get_reviewer_functions()
1246 PullRequestModel().get_reviewer_functions()
1246
1247
1247 try:
1248 try:
1248 reviewers = validate_default_reviewers(review_members, reviewer_rules)
1249 reviewers = validate_default_reviewers(review_members, reviewer_rules)
1249 except ValueError as e:
1250 except ValueError as e:
1250 log.error('Reviewers Validation: {}'.format(e))
1251 log.error('Reviewers Validation: {}'.format(e))
1251 h.flash(e, category='error')
1252 h.flash(e, category='error')
1252 return
1253 return
1253
1254
1254 old_calculated_status = pull_request.calculated_review_status()
1255 old_calculated_status = pull_request.calculated_review_status()
1255 PullRequestModel().update_reviewers(
1256 PullRequestModel().update_reviewers(
1256 pull_request, reviewers, self._rhodecode_user)
1257 pull_request, reviewers, self._rhodecode_user)
1257 h.flash(_('Pull request reviewers updated.'), category='success')
1258 h.flash(_('Pull request reviewers updated.'), category='success')
1258 Session().commit()
1259 Session().commit()
1259
1260
1260 # trigger status changed if change in reviewers changes the status
1261 # trigger status changed if change in reviewers changes the status
1261 calculated_status = pull_request.calculated_review_status()
1262 calculated_status = pull_request.calculated_review_status()
1262 if old_calculated_status != calculated_status:
1263 if old_calculated_status != calculated_status:
1263 PullRequestModel().trigger_pull_request_hook(
1264 PullRequestModel().trigger_pull_request_hook(
1264 pull_request, self._rhodecode_user, 'review_status_change',
1265 pull_request, self._rhodecode_user, 'review_status_change',
1265 data={'status': calculated_status})
1266 data={'status': calculated_status})
1266
1267
1267 @LoginRequired()
1268 @LoginRequired()
1268 @NotAnonymous()
1269 @NotAnonymous()
1269 @HasRepoPermissionAnyDecorator(
1270 @HasRepoPermissionAnyDecorator(
1270 'repository.read', 'repository.write', 'repository.admin')
1271 'repository.read', 'repository.write', 'repository.admin')
1271 @CSRFRequired()
1272 @CSRFRequired()
1272 @view_config(
1273 @view_config(
1273 route_name='pullrequest_delete', request_method='POST',
1274 route_name='pullrequest_delete', request_method='POST',
1274 renderer='json_ext')
1275 renderer='json_ext')
1275 def pull_request_delete(self):
1276 def pull_request_delete(self):
1276 _ = self.request.translate
1277 _ = self.request.translate
1277
1278
1278 pull_request = PullRequest.get_or_404(
1279 pull_request = PullRequest.get_or_404(
1279 self.request.matchdict['pull_request_id'])
1280 self.request.matchdict['pull_request_id'])
1280 self.load_default_context()
1281 self.load_default_context()
1281
1282
1282 pr_closed = pull_request.is_closed()
1283 pr_closed = pull_request.is_closed()
1283 allowed_to_delete = PullRequestModel().check_user_delete(
1284 allowed_to_delete = PullRequestModel().check_user_delete(
1284 pull_request, self._rhodecode_user) and not pr_closed
1285 pull_request, self._rhodecode_user) and not pr_closed
1285
1286
1286 # only owner can delete it !
1287 # only owner can delete it !
1287 if allowed_to_delete:
1288 if allowed_to_delete:
1288 PullRequestModel().delete(pull_request, self._rhodecode_user)
1289 PullRequestModel().delete(pull_request, self._rhodecode_user)
1289 Session().commit()
1290 Session().commit()
1290 h.flash(_('Successfully deleted pull request'),
1291 h.flash(_('Successfully deleted pull request'),
1291 category='success')
1292 category='success')
1292 raise HTTPFound(h.route_path('pullrequest_show_all',
1293 raise HTTPFound(h.route_path('pullrequest_show_all',
1293 repo_name=self.db_repo_name))
1294 repo_name=self.db_repo_name))
1294
1295
1295 log.warning('user %s tried to delete pull request without access',
1296 log.warning('user %s tried to delete pull request without access',
1296 self._rhodecode_user)
1297 self._rhodecode_user)
1297 raise HTTPNotFound()
1298 raise HTTPNotFound()
1298
1299
1299 @LoginRequired()
1300 @LoginRequired()
1300 @NotAnonymous()
1301 @NotAnonymous()
1301 @HasRepoPermissionAnyDecorator(
1302 @HasRepoPermissionAnyDecorator(
1302 'repository.read', 'repository.write', 'repository.admin')
1303 'repository.read', 'repository.write', 'repository.admin')
1303 @CSRFRequired()
1304 @CSRFRequired()
1304 @view_config(
1305 @view_config(
1305 route_name='pullrequest_comment_create', request_method='POST',
1306 route_name='pullrequest_comment_create', request_method='POST',
1306 renderer='json_ext')
1307 renderer='json_ext')
1307 def pull_request_comment_create(self):
1308 def pull_request_comment_create(self):
1308 _ = self.request.translate
1309 _ = self.request.translate
1309
1310
1310 pull_request = PullRequest.get_or_404(
1311 pull_request = PullRequest.get_or_404(
1311 self.request.matchdict['pull_request_id'])
1312 self.request.matchdict['pull_request_id'])
1312 pull_request_id = pull_request.pull_request_id
1313 pull_request_id = pull_request.pull_request_id
1313
1314
1314 if pull_request.is_closed():
1315 if pull_request.is_closed():
1315 log.debug('comment: forbidden because pull request is closed')
1316 log.debug('comment: forbidden because pull request is closed')
1316 raise HTTPForbidden()
1317 raise HTTPForbidden()
1317
1318
1318 allowed_to_comment = PullRequestModel().check_user_comment(
1319 allowed_to_comment = PullRequestModel().check_user_comment(
1319 pull_request, self._rhodecode_user)
1320 pull_request, self._rhodecode_user)
1320 if not allowed_to_comment:
1321 if not allowed_to_comment:
1321 log.debug(
1322 log.debug(
1322 'comment: forbidden because pull request is from forbidden repo')
1323 'comment: forbidden because pull request is from forbidden repo')
1323 raise HTTPForbidden()
1324 raise HTTPForbidden()
1324
1325
1325 c = self.load_default_context()
1326 c = self.load_default_context()
1326
1327
1327 status = self.request.POST.get('changeset_status', None)
1328 status = self.request.POST.get('changeset_status', None)
1328 text = self.request.POST.get('text')
1329 text = self.request.POST.get('text')
1329 comment_type = self.request.POST.get('comment_type')
1330 comment_type = self.request.POST.get('comment_type')
1330 resolves_comment_id = self.request.POST.get('resolves_comment_id', None)
1331 resolves_comment_id = self.request.POST.get('resolves_comment_id', None)
1331 close_pull_request = self.request.POST.get('close_pull_request')
1332 close_pull_request = self.request.POST.get('close_pull_request')
1332
1333
1333 # the logic here should work like following, if we submit close
1334 # the logic here should work like following, if we submit close
1334 # pr comment, use `close_pull_request_with_comment` function
1335 # pr comment, use `close_pull_request_with_comment` function
1335 # else handle regular comment logic
1336 # else handle regular comment logic
1336
1337
1337 if close_pull_request:
1338 if close_pull_request:
1338 # only owner or admin or person with write permissions
1339 # only owner or admin or person with write permissions
1339 allowed_to_close = PullRequestModel().check_user_update(
1340 allowed_to_close = PullRequestModel().check_user_update(
1340 pull_request, self._rhodecode_user)
1341 pull_request, self._rhodecode_user)
1341 if not allowed_to_close:
1342 if not allowed_to_close:
1342 log.debug('comment: forbidden because not allowed to close '
1343 log.debug('comment: forbidden because not allowed to close '
1343 'pull request %s', pull_request_id)
1344 'pull request %s', pull_request_id)
1344 raise HTTPForbidden()
1345 raise HTTPForbidden()
1345
1346
1346 # This also triggers `review_status_change`
1347 # This also triggers `review_status_change`
1347 comment, status = PullRequestModel().close_pull_request_with_comment(
1348 comment, status = PullRequestModel().close_pull_request_with_comment(
1348 pull_request, self._rhodecode_user, self.db_repo, message=text,
1349 pull_request, self._rhodecode_user, self.db_repo, message=text,
1349 auth_user=self._rhodecode_user)
1350 auth_user=self._rhodecode_user)
1350 Session().flush()
1351 Session().flush()
1351
1352
1352 PullRequestModel().trigger_pull_request_hook(
1353 PullRequestModel().trigger_pull_request_hook(
1353 pull_request, self._rhodecode_user, 'comment',
1354 pull_request, self._rhodecode_user, 'comment',
1354 data={'comment': comment})
1355 data={'comment': comment})
1355
1356
1356 else:
1357 else:
1357 # regular comment case, could be inline, or one with status.
1358 # regular comment case, could be inline, or one with status.
1358 # for that one we check also permissions
1359 # for that one we check also permissions
1359
1360
1360 allowed_to_change_status = PullRequestModel().check_user_change_status(
1361 allowed_to_change_status = PullRequestModel().check_user_change_status(
1361 pull_request, self._rhodecode_user)
1362 pull_request, self._rhodecode_user)
1362
1363
1363 if status and allowed_to_change_status:
1364 if status and allowed_to_change_status:
1364 message = (_('Status change %(transition_icon)s %(status)s')
1365 message = (_('Status change %(transition_icon)s %(status)s')
1365 % {'transition_icon': '>',
1366 % {'transition_icon': '>',
1366 'status': ChangesetStatus.get_status_lbl(status)})
1367 'status': ChangesetStatus.get_status_lbl(status)})
1367 text = text or message
1368 text = text or message
1368
1369
1369 comment = CommentsModel().create(
1370 comment = CommentsModel().create(
1370 text=text,
1371 text=text,
1371 repo=self.db_repo.repo_id,
1372 repo=self.db_repo.repo_id,
1372 user=self._rhodecode_user.user_id,
1373 user=self._rhodecode_user.user_id,
1373 pull_request=pull_request,
1374 pull_request=pull_request,
1374 f_path=self.request.POST.get('f_path'),
1375 f_path=self.request.POST.get('f_path'),
1375 line_no=self.request.POST.get('line'),
1376 line_no=self.request.POST.get('line'),
1376 status_change=(ChangesetStatus.get_status_lbl(status)
1377 status_change=(ChangesetStatus.get_status_lbl(status)
1377 if status and allowed_to_change_status else None),
1378 if status and allowed_to_change_status else None),
1378 status_change_type=(status
1379 status_change_type=(status
1379 if status and allowed_to_change_status else None),
1380 if status and allowed_to_change_status else None),
1380 comment_type=comment_type,
1381 comment_type=comment_type,
1381 resolves_comment_id=resolves_comment_id,
1382 resolves_comment_id=resolves_comment_id,
1382 auth_user=self._rhodecode_user
1383 auth_user=self._rhodecode_user
1383 )
1384 )
1384
1385
1385 if allowed_to_change_status:
1386 if allowed_to_change_status:
1386 # calculate old status before we change it
1387 # calculate old status before we change it
1387 old_calculated_status = pull_request.calculated_review_status()
1388 old_calculated_status = pull_request.calculated_review_status()
1388
1389
1389 # get status if set !
1390 # get status if set !
1390 if status:
1391 if status:
1391 ChangesetStatusModel().set_status(
1392 ChangesetStatusModel().set_status(
1392 self.db_repo.repo_id,
1393 self.db_repo.repo_id,
1393 status,
1394 status,
1394 self._rhodecode_user.user_id,
1395 self._rhodecode_user.user_id,
1395 comment,
1396 comment,
1396 pull_request=pull_request
1397 pull_request=pull_request
1397 )
1398 )
1398
1399
1399 Session().flush()
1400 Session().flush()
1400 # this is somehow required to get access to some relationship
1401 # this is somehow required to get access to some relationship
1401 # loaded on comment
1402 # loaded on comment
1402 Session().refresh(comment)
1403 Session().refresh(comment)
1403
1404
1404 PullRequestModel().trigger_pull_request_hook(
1405 PullRequestModel().trigger_pull_request_hook(
1405 pull_request, self._rhodecode_user, 'comment',
1406 pull_request, self._rhodecode_user, 'comment',
1406 data={'comment': comment})
1407 data={'comment': comment})
1407
1408
1408 # we now calculate the status of pull request, and based on that
1409 # we now calculate the status of pull request, and based on that
1409 # calculation we set the commits status
1410 # calculation we set the commits status
1410 calculated_status = pull_request.calculated_review_status()
1411 calculated_status = pull_request.calculated_review_status()
1411 if old_calculated_status != calculated_status:
1412 if old_calculated_status != calculated_status:
1412 PullRequestModel().trigger_pull_request_hook(
1413 PullRequestModel().trigger_pull_request_hook(
1413 pull_request, self._rhodecode_user, 'review_status_change',
1414 pull_request, self._rhodecode_user, 'review_status_change',
1414 data={'status': calculated_status})
1415 data={'status': calculated_status})
1415
1416
1416 Session().commit()
1417 Session().commit()
1417
1418
1418 data = {
1419 data = {
1419 'target_id': h.safeid(h.safe_unicode(
1420 'target_id': h.safeid(h.safe_unicode(
1420 self.request.POST.get('f_path'))),
1421 self.request.POST.get('f_path'))),
1421 }
1422 }
1422 if comment:
1423 if comment:
1423 c.co = comment
1424 c.co = comment
1424 rendered_comment = render(
1425 rendered_comment = render(
1425 'rhodecode:templates/changeset/changeset_comment_block.mako',
1426 'rhodecode:templates/changeset/changeset_comment_block.mako',
1426 self._get_template_context(c), self.request)
1427 self._get_template_context(c), self.request)
1427
1428
1428 data.update(comment.get_dict())
1429 data.update(comment.get_dict())
1429 data.update({'rendered_text': rendered_comment})
1430 data.update({'rendered_text': rendered_comment})
1430
1431
1431 return data
1432 return data
1432
1433
1433 @LoginRequired()
1434 @LoginRequired()
1434 @NotAnonymous()
1435 @NotAnonymous()
1435 @HasRepoPermissionAnyDecorator(
1436 @HasRepoPermissionAnyDecorator(
1436 'repository.read', 'repository.write', 'repository.admin')
1437 'repository.read', 'repository.write', 'repository.admin')
1437 @CSRFRequired()
1438 @CSRFRequired()
1438 @view_config(
1439 @view_config(
1439 route_name='pullrequest_comment_delete', request_method='POST',
1440 route_name='pullrequest_comment_delete', request_method='POST',
1440 renderer='json_ext')
1441 renderer='json_ext')
1441 def pull_request_comment_delete(self):
1442 def pull_request_comment_delete(self):
1442 pull_request = PullRequest.get_or_404(
1443 pull_request = PullRequest.get_or_404(
1443 self.request.matchdict['pull_request_id'])
1444 self.request.matchdict['pull_request_id'])
1444
1445
1445 comment = ChangesetComment.get_or_404(
1446 comment = ChangesetComment.get_or_404(
1446 self.request.matchdict['comment_id'])
1447 self.request.matchdict['comment_id'])
1447 comment_id = comment.comment_id
1448 comment_id = comment.comment_id
1448
1449
1449 if pull_request.is_closed():
1450 if pull_request.is_closed():
1450 log.debug('comment: forbidden because pull request is closed')
1451 log.debug('comment: forbidden because pull request is closed')
1451 raise HTTPForbidden()
1452 raise HTTPForbidden()
1452
1453
1453 if not comment:
1454 if not comment:
1454 log.debug('Comment with id:%s not found, skipping', comment_id)
1455 log.debug('Comment with id:%s not found, skipping', comment_id)
1455 # comment already deleted in another call probably
1456 # comment already deleted in another call probably
1456 return True
1457 return True
1457
1458
1458 if comment.pull_request.is_closed():
1459 if comment.pull_request.is_closed():
1459 # don't allow deleting comments on closed pull request
1460 # don't allow deleting comments on closed pull request
1460 raise HTTPForbidden()
1461 raise HTTPForbidden()
1461
1462
1462 is_repo_admin = h.HasRepoPermissionAny('repository.admin')(self.db_repo_name)
1463 is_repo_admin = h.HasRepoPermissionAny('repository.admin')(self.db_repo_name)
1463 super_admin = h.HasPermissionAny('hg.admin')()
1464 super_admin = h.HasPermissionAny('hg.admin')()
1464 comment_owner = comment.author.user_id == self._rhodecode_user.user_id
1465 comment_owner = comment.author.user_id == self._rhodecode_user.user_id
1465 is_repo_comment = comment.repo.repo_name == self.db_repo_name
1466 is_repo_comment = comment.repo.repo_name == self.db_repo_name
1466 comment_repo_admin = is_repo_admin and is_repo_comment
1467 comment_repo_admin = is_repo_admin and is_repo_comment
1467
1468
1468 if super_admin or comment_owner or comment_repo_admin:
1469 if super_admin or comment_owner or comment_repo_admin:
1469 old_calculated_status = comment.pull_request.calculated_review_status()
1470 old_calculated_status = comment.pull_request.calculated_review_status()
1470 CommentsModel().delete(comment=comment, auth_user=self._rhodecode_user)
1471 CommentsModel().delete(comment=comment, auth_user=self._rhodecode_user)
1471 Session().commit()
1472 Session().commit()
1472 calculated_status = comment.pull_request.calculated_review_status()
1473 calculated_status = comment.pull_request.calculated_review_status()
1473 if old_calculated_status != calculated_status:
1474 if old_calculated_status != calculated_status:
1474 PullRequestModel().trigger_pull_request_hook(
1475 PullRequestModel().trigger_pull_request_hook(
1475 comment.pull_request, self._rhodecode_user, 'review_status_change',
1476 comment.pull_request, self._rhodecode_user, 'review_status_change',
1476 data={'status': calculated_status})
1477 data={'status': calculated_status})
1477 return True
1478 return True
1478 else:
1479 else:
1479 log.warning('No permissions for user %s to delete comment_id: %s',
1480 log.warning('No permissions for user %s to delete comment_id: %s',
1480 self._rhodecode_db_user, comment_id)
1481 self._rhodecode_db_user, comment_id)
1481 raise HTTPNotFound()
1482 raise HTTPNotFound()
@@ -1,470 +1,474 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 ${h.submit('remove_%s' % repo_name,_('Delete'),class_="btn btn-link btn-danger",
179 ${h.submit('remove_%s' % repo_name,_('Delete'),class_="btn btn-link btn-danger",
180 onclick="return confirm('"+_('Confirm to delete this repository: %s') % repo_name+"');")}
180 onclick="return confirm('"+_('Confirm to delete this repository: %s') % repo_name+"');")}
181 ${h.end_form()}
181 ${h.end_form()}
182 </div>
182 </div>
183 </div>
183 </div>
184 </%def>
184 </%def>
185
185
186 <%def name="repo_state(repo_state)">
186 <%def name="repo_state(repo_state)">
187 <div>
187 <div>
188 %if repo_state == 'repo_state_pending':
188 %if repo_state == 'repo_state_pending':
189 <div class="tag tag4">${_('Creating')}</div>
189 <div class="tag tag4">${_('Creating')}</div>
190 %elif repo_state == 'repo_state_created':
190 %elif repo_state == 'repo_state_created':
191 <div class="tag tag1">${_('Created')}</div>
191 <div class="tag tag1">${_('Created')}</div>
192 %else:
192 %else:
193 <div class="tag alert2" title="${h.tooltip(repo_state)}">invalid</div>
193 <div class="tag alert2" title="${h.tooltip(repo_state)}">invalid</div>
194 %endif
194 %endif
195 </div>
195 </div>
196 </%def>
196 </%def>
197
197
198
198
199 ## REPO GROUP RENDERERS
199 ## REPO GROUP RENDERERS
200 <%def name="quick_repo_group_menu(repo_group_name)">
200 <%def name="quick_repo_group_menu(repo_group_name)">
201 <i class="icon-more"></i>
201 <i class="icon-more"></i>
202 <div class="menu_items_container hidden">
202 <div class="menu_items_container hidden">
203 <ul class="menu_items">
203 <ul class="menu_items">
204 <li>
204 <li>
205 <a href="${h.route_path('repo_group_home', repo_group_name=repo_group_name)}">${_('Summary')}</a>
205 <a href="${h.route_path('repo_group_home', repo_group_name=repo_group_name)}">${_('Summary')}</a>
206 </li>
206 </li>
207
207
208 </ul>
208 </ul>
209 </div>
209 </div>
210 </%def>
210 </%def>
211
211
212 <%def name="repo_group_name(repo_group_name, children_groups=None)">
212 <%def name="repo_group_name(repo_group_name, children_groups=None)">
213 <div>
213 <div>
214 <a href="${h.route_path('repo_group_home', repo_group_name=repo_group_name)}">
214 <a href="${h.route_path('repo_group_home', repo_group_name=repo_group_name)}">
215 <i class="icon-repo-group" title="${_('Repository group')}" style="font-size: 14px"></i>
215 <i class="icon-repo-group" title="${_('Repository group')}" style="font-size: 14px"></i>
216 %if children_groups:
216 %if children_groups:
217 ${h.literal(' &raquo; '.join(children_groups))}
217 ${h.literal(' &raquo; '.join(children_groups))}
218 %else:
218 %else:
219 ${repo_group_name}
219 ${repo_group_name}
220 %endif
220 %endif
221 </a>
221 </a>
222 </div>
222 </div>
223 </%def>
223 </%def>
224
224
225 <%def name="repo_group_desc(description, personal, stylify_metatags)">
225 <%def name="repo_group_desc(description, personal, stylify_metatags)">
226
226
227 <%
227 <%
228 if stylify_metatags:
228 if stylify_metatags:
229 tags, description = h.extract_metatags(description)
229 tags, description = h.extract_metatags(description)
230 %>
230 %>
231
231
232 <div class="truncate-wrap">
232 <div class="truncate-wrap">
233 % if personal:
233 % if personal:
234 <div class="metatag" tag="personal">${_('personal')}</div>
234 <div class="metatag" tag="personal">${_('personal')}</div>
235 % endif
235 % endif
236
236
237 % if stylify_metatags:
237 % if stylify_metatags:
238 % for tag_type, tag in tags:
238 % for tag_type, tag in tags:
239 ${h.style_metatag(tag_type, tag)|n}
239 ${h.style_metatag(tag_type, tag)|n}
240 % endfor
240 % endfor
241 % endif
241 % endif
242 ${description}
242 ${description}
243 </div>
243 </div>
244
244
245 </%def>
245 </%def>
246
246
247 <%def name="repo_group_actions(repo_group_id, repo_group_name, gr_count)">
247 <%def name="repo_group_actions(repo_group_id, repo_group_name, gr_count)">
248 <div class="grid_edit">
248 <div class="grid_edit">
249 <a href="${h.route_path('edit_repo_group',repo_group_name=repo_group_name)}" title="${_('Edit')}">Edit</a>
249 <a href="${h.route_path('edit_repo_group',repo_group_name=repo_group_name)}" title="${_('Edit')}">Edit</a>
250 </div>
250 </div>
251 <div class="grid_delete">
251 <div class="grid_delete">
252 ${h.secure_form(h.route_path('edit_repo_group_advanced_delete', repo_group_name=repo_group_name), request=request)}
252 ${h.secure_form(h.route_path('edit_repo_group_advanced_delete', repo_group_name=repo_group_name), request=request)}
253 ${h.submit('remove_%s' % repo_group_name,_('Delete'),class_="btn btn-link btn-danger",
253 ${h.submit('remove_%s' % repo_group_name,_('Delete'),class_="btn btn-link btn-danger",
254 onclick="return confirm('"+_ungettext('Confirm to delete this group: %s with %s repository','Confirm to delete this group: %s with %s repositories',gr_count) % (repo_group_name, gr_count)+"');")}
254 onclick="return confirm('"+_ungettext('Confirm to delete this group: %s with %s repository','Confirm to delete this group: %s with %s repositories',gr_count) % (repo_group_name, gr_count)+"');")}
255 ${h.end_form()}
255 ${h.end_form()}
256 </div>
256 </div>
257 </%def>
257 </%def>
258
258
259
259
260 <%def name="user_actions(user_id, username)">
260 <%def name="user_actions(user_id, username)">
261 <div class="grid_edit">
261 <div class="grid_edit">
262 <a href="${h.route_path('user_edit',user_id=user_id)}" title="${_('Edit')}">
262 <a href="${h.route_path('user_edit',user_id=user_id)}" title="${_('Edit')}">
263 ${_('Edit')}
263 ${_('Edit')}
264 </a>
264 </a>
265 </div>
265 </div>
266 <div class="grid_delete">
266 <div class="grid_delete">
267 ${h.secure_form(h.route_path('user_delete', user_id=user_id), request=request)}
267 ${h.secure_form(h.route_path('user_delete', user_id=user_id), request=request)}
268 ${h.submit('remove_',_('Delete'),id="remove_user_%s" % user_id, class_="btn btn-link btn-danger",
268 ${h.submit('remove_',_('Delete'),id="remove_user_%s" % user_id, class_="btn btn-link btn-danger",
269 onclick="return confirm('"+_('Confirm to delete this user: %s') % username+"');")}
269 onclick="return confirm('"+_('Confirm to delete this user: %s') % username+"');")}
270 ${h.end_form()}
270 ${h.end_form()}
271 </div>
271 </div>
272 </%def>
272 </%def>
273
273
274 <%def name="user_group_actions(user_group_id, user_group_name)">
274 <%def name="user_group_actions(user_group_id, user_group_name)">
275 <div class="grid_edit">
275 <div class="grid_edit">
276 <a href="${h.route_path('edit_user_group', user_group_id=user_group_id)}" title="${_('Edit')}">Edit</a>
276 <a href="${h.route_path('edit_user_group', user_group_id=user_group_id)}" title="${_('Edit')}">Edit</a>
277 </div>
277 </div>
278 <div class="grid_delete">
278 <div class="grid_delete">
279 ${h.secure_form(h.route_path('user_groups_delete', user_group_id=user_group_id), request=request)}
279 ${h.secure_form(h.route_path('user_groups_delete', user_group_id=user_group_id), request=request)}
280 ${h.submit('remove_',_('Delete'),id="remove_group_%s" % user_group_id, class_="btn btn-link btn-danger",
280 ${h.submit('remove_',_('Delete'),id="remove_group_%s" % user_group_id, class_="btn btn-link btn-danger",
281 onclick="return confirm('"+_('Confirm to delete this user group: %s') % user_group_name+"');")}
281 onclick="return confirm('"+_('Confirm to delete this user group: %s') % user_group_name+"');")}
282 ${h.end_form()}
282 ${h.end_form()}
283 </div>
283 </div>
284 </%def>
284 </%def>
285
285
286
286
287 <%def name="user_name(user_id, username)">
287 <%def name="user_name(user_id, username)">
288 ${h.link_to(h.person(username, 'username_or_name_or_email'), h.route_path('user_edit', user_id=user_id))}
288 ${h.link_to(h.person(username, 'username_or_name_or_email'), h.route_path('user_edit', user_id=user_id))}
289 </%def>
289 </%def>
290
290
291 <%def name="user_profile(username)">
291 <%def name="user_profile(username)">
292 ${base.gravatar_with_user(username, 16, tooltip=True)}
292 ${base.gravatar_with_user(username, 16, tooltip=True)}
293 </%def>
293 </%def>
294
294
295 <%def name="user_group_name(user_group_name)">
295 <%def name="user_group_name(user_group_name)">
296 <div>
296 <div>
297 <i class="icon-user-group" title="${_('User group')}"></i>
297 <i class="icon-user-group" title="${_('User group')}"></i>
298 ${h.link_to_group(user_group_name)}
298 ${h.link_to_group(user_group_name)}
299 </div>
299 </div>
300 </%def>
300 </%def>
301
301
302
302
303 ## GISTS
303 ## GISTS
304
304
305 <%def name="gist_gravatar(full_contact)">
305 <%def name="gist_gravatar(full_contact)">
306 <div class="gist_gravatar">
306 <div class="gist_gravatar">
307 ${base.gravatar(full_contact, 30)}
307 ${base.gravatar(full_contact, 30)}
308 </div>
308 </div>
309 </%def>
309 </%def>
310
310
311 <%def name="gist_access_id(gist_access_id, full_contact)">
311 <%def name="gist_access_id(gist_access_id, full_contact)">
312 <div>
312 <div>
313 <b>
313 <b>
314 <a href="${h.route_path('gist_show', gist_id=gist_access_id)}">gist: ${gist_access_id}</a>
314 <a href="${h.route_path('gist_show', gist_id=gist_access_id)}">gist: ${gist_access_id}</a>
315 </b>
315 </b>
316 </div>
316 </div>
317 </%def>
317 </%def>
318
318
319 <%def name="gist_author(full_contact, created_on, expires)">
319 <%def name="gist_author(full_contact, created_on, expires)">
320 ${base.gravatar_with_user(full_contact, 16, tooltip=True)}
320 ${base.gravatar_with_user(full_contact, 16, tooltip=True)}
321 </%def>
321 </%def>
322
322
323
323
324 <%def name="gist_created(created_on)">
324 <%def name="gist_created(created_on)">
325 <div class="created">
325 <div class="created">
326 ${h.age_component(created_on, time_is_local=True)}
326 ${h.age_component(created_on, time_is_local=True)}
327 </div>
327 </div>
328 </%def>
328 </%def>
329
329
330 <%def name="gist_expires(expires)">
330 <%def name="gist_expires(expires)">
331 <div class="created">
331 <div class="created">
332 %if expires == -1:
332 %if expires == -1:
333 ${_('never')}
333 ${_('never')}
334 %else:
334 %else:
335 ${h.age_component(h.time_to_utcdatetime(expires))}
335 ${h.age_component(h.time_to_utcdatetime(expires))}
336 %endif
336 %endif
337 </div>
337 </div>
338 </%def>
338 </%def>
339
339
340 <%def name="gist_type(gist_type)">
340 <%def name="gist_type(gist_type)">
341 %if gist_type == 'public':
341 %if gist_type == 'public':
342 <span class="tag tag-gist-public disabled">${_('Public Gist')}</span>
342 <span class="tag tag-gist-public disabled">${_('Public Gist')}</span>
343 %else:
343 %else:
344 <span class="tag tag-gist-private disabled">${_('Private Gist')}</span>
344 <span class="tag tag-gist-private disabled">${_('Private Gist')}</span>
345 %endif
345 %endif
346 </%def>
346 </%def>
347
347
348 <%def name="gist_description(gist_description)">
348 <%def name="gist_description(gist_description)">
349 ${gist_description}
349 ${gist_description}
350 </%def>
350 </%def>
351
351
352
352
353 ## PULL REQUESTS GRID RENDERERS
353 ## PULL REQUESTS GRID RENDERERS
354
354
355 <%def name="pullrequest_target_repo(repo_name)">
355 <%def name="pullrequest_target_repo(repo_name)">
356 <div class="truncate">
356 <div class="truncate">
357 ${h.link_to(repo_name,h.route_path('repo_summary',repo_name=repo_name))}
357 ${h.link_to(repo_name,h.route_path('repo_summary',repo_name=repo_name))}
358 </div>
358 </div>
359 </%def>
359 </%def>
360
360
361 <%def name="pullrequest_status(status)">
361 <%def name="pullrequest_status(status)">
362 <i class="icon-circle review-status-${status}"></i>
362 <i class="icon-circle review-status-${status}"></i>
363 </%def>
363 </%def>
364
364
365 <%def name="pullrequest_title(title, description)">
365 <%def name="pullrequest_title(title, description)">
366 ${title}
366 ${title}
367 </%def>
367 </%def>
368
368
369 <%def name="pullrequest_comments(comments_nr)">
369 <%def name="pullrequest_comments(comments_nr)">
370 <i class="icon-comment"></i> ${comments_nr}
370 <i class="icon-comment"></i> ${comments_nr}
371 </%def>
371 </%def>
372
372
373 <%def name="pullrequest_name(pull_request_id, target_repo_name, short=False)">
373 <%def name="pullrequest_name(pull_request_id, is_wip, target_repo_name, short=False)">
374 <a href="${h.route_path('pullrequest_show',repo_name=target_repo_name,pull_request_id=pull_request_id)}">
374 <a href="${h.route_path('pullrequest_show',repo_name=target_repo_name,pull_request_id=pull_request_id)}">
375 % if is_wip:
376 <span class="tag tooltip" title="${_('Work in progress')}">wip</span>
377 % endif
378
375 % if short:
379 % if short:
376 !${pull_request_id}
380 !${pull_request_id}
377 % else:
381 % else:
378 ${_('Pull request !{}').format(pull_request_id)}
382 ${_('Pull request !{}').format(pull_request_id)}
379 % endif
383 % endif
380 </a>
384 </a>
381 </%def>
385 </%def>
382
386
383 <%def name="pullrequest_updated_on(updated_on)">
387 <%def name="pullrequest_updated_on(updated_on)">
384 ${h.age_component(h.time_to_utcdatetime(updated_on))}
388 ${h.age_component(h.time_to_utcdatetime(updated_on))}
385 </%def>
389 </%def>
386
390
387 <%def name="pullrequest_author(full_contact)">
391 <%def name="pullrequest_author(full_contact)">
388 ${base.gravatar_with_user(full_contact, 16, tooltip=True)}
392 ${base.gravatar_with_user(full_contact, 16, tooltip=True)}
389 </%def>
393 </%def>
390
394
391
395
392 ## ARTIFACT RENDERERS
396 ## ARTIFACT RENDERERS
393 <%def name="repo_artifact_name(repo_name, file_uid, artifact_display_name)">
397 <%def name="repo_artifact_name(repo_name, file_uid, artifact_display_name)">
394 <a href="${h.route_path('repo_artifacts_get', repo_name=repo_name, uid=file_uid)}">
398 <a href="${h.route_path('repo_artifacts_get', repo_name=repo_name, uid=file_uid)}">
395 ${artifact_display_name or '_EMPTY_NAME_'}
399 ${artifact_display_name or '_EMPTY_NAME_'}
396 </a>
400 </a>
397 </%def>
401 </%def>
398
402
399 <%def name="repo_artifact_uid(repo_name, file_uid)">
403 <%def name="repo_artifact_uid(repo_name, file_uid)">
400 <code>${h.shorter(file_uid, size=24, prefix=True)}</code>
404 <code>${h.shorter(file_uid, size=24, prefix=True)}</code>
401 </%def>
405 </%def>
402
406
403 <%def name="repo_artifact_sha256(artifact_sha256)">
407 <%def name="repo_artifact_sha256(artifact_sha256)">
404 <div class="code">${h.shorter(artifact_sha256, 12)}</div>
408 <div class="code">${h.shorter(artifact_sha256, 12)}</div>
405 </%def>
409 </%def>
406
410
407 <%def name="repo_artifact_actions(repo_name, file_store_id, file_uid)">
411 <%def name="repo_artifact_actions(repo_name, file_store_id, file_uid)">
408 ## <div class="grid_edit">
412 ## <div class="grid_edit">
409 ## <a href="#Edit" title="${_('Edit')}">${_('Edit')}</a>
413 ## <a href="#Edit" title="${_('Edit')}">${_('Edit')}</a>
410 ## </div>
414 ## </div>
411 <div class="grid_edit">
415 <div class="grid_edit">
412 <a href="${h.route_path('repo_artifacts_info', repo_name=repo_name, uid=file_store_id)}" title="${_('Info')}">${_('Info')}</a>
416 <a href="${h.route_path('repo_artifacts_info', repo_name=repo_name, uid=file_store_id)}" title="${_('Info')}">${_('Info')}</a>
413 </div>
417 </div>
414 % if h.HasRepoPermissionAny('repository.admin')(c.repo_name):
418 % if h.HasRepoPermissionAny('repository.admin')(c.repo_name):
415 <div class="grid_delete">
419 <div class="grid_delete">
416 ${h.secure_form(h.route_path('repo_artifacts_delete', repo_name=repo_name, uid=file_store_id), request=request)}
420 ${h.secure_form(h.route_path('repo_artifacts_delete', repo_name=repo_name, uid=file_store_id), request=request)}
417 ${h.submit('remove_',_('Delete'),id="remove_artifact_%s" % file_store_id, class_="btn btn-link btn-danger",
421 ${h.submit('remove_',_('Delete'),id="remove_artifact_%s" % file_store_id, class_="btn btn-link btn-danger",
418 onclick="return confirm('"+_('Confirm to delete this artifact: %s') % file_uid+"');")}
422 onclick="return confirm('"+_('Confirm to delete this artifact: %s') % file_uid+"');")}
419 ${h.end_form()}
423 ${h.end_form()}
420 </div>
424 </div>
421 % endif
425 % endif
422 </%def>
426 </%def>
423
427
424 <%def name="markup_form(form_id, form_text='', help_text=None)">
428 <%def name="markup_form(form_id, form_text='', help_text=None)">
425
429
426 <div class="markup-form">
430 <div class="markup-form">
427 <div class="markup-form-area">
431 <div class="markup-form-area">
428 <div class="markup-form-area-header">
432 <div class="markup-form-area-header">
429 <ul class="nav-links clearfix">
433 <ul class="nav-links clearfix">
430 <li class="active">
434 <li class="active">
431 <a href="#edit-text" tabindex="-1" id="edit-btn_${form_id}">${_('Write')}</a>
435 <a href="#edit-text" tabindex="-1" id="edit-btn_${form_id}">${_('Write')}</a>
432 </li>
436 </li>
433 <li class="">
437 <li class="">
434 <a href="#preview-text" tabindex="-1" id="preview-btn_${form_id}">${_('Preview')}</a>
438 <a href="#preview-text" tabindex="-1" id="preview-btn_${form_id}">${_('Preview')}</a>
435 </li>
439 </li>
436 </ul>
440 </ul>
437 </div>
441 </div>
438
442
439 <div class="markup-form-area-write" style="display: block;">
443 <div class="markup-form-area-write" style="display: block;">
440 <div id="edit-container_${form_id}">
444 <div id="edit-container_${form_id}">
441 <textarea id="${form_id}" name="${form_id}" class="comment-block-ta ac-input">${form_text if form_text else ''}</textarea>
445 <textarea id="${form_id}" name="${form_id}" class="comment-block-ta ac-input">${form_text if form_text else ''}</textarea>
442 </div>
446 </div>
443 <div id="preview-container_${form_id}" class="clearfix" style="display: none;">
447 <div id="preview-container_${form_id}" class="clearfix" style="display: none;">
444 <div id="preview-box_${form_id}" class="preview-box"></div>
448 <div id="preview-box_${form_id}" class="preview-box"></div>
445 </div>
449 </div>
446 </div>
450 </div>
447
451
448 <div class="markup-form-area-footer">
452 <div class="markup-form-area-footer">
449 <div class="toolbar">
453 <div class="toolbar">
450 <div class="toolbar-text">
454 <div class="toolbar-text">
451 ${(_('Parsed using %s syntax') % (
455 ${(_('Parsed using %s syntax') % (
452 ('<a href="%s">%s</a>' % (h.route_url('%s_help' % c.visual.default_renderer), c.visual.default_renderer.upper())),
456 ('<a href="%s">%s</a>' % (h.route_url('%s_help' % c.visual.default_renderer), c.visual.default_renderer.upper())),
453 )
457 )
454 )|n}
458 )|n}
455 </div>
459 </div>
456 </div>
460 </div>
457 </div>
461 </div>
458 </div>
462 </div>
459
463
460 <div class="markup-form-footer">
464 <div class="markup-form-footer">
461 % if help_text:
465 % if help_text:
462 <span class="help-block">${help_text}</span>
466 <span class="help-block">${help_text}</span>
463 % endif
467 % endif
464 </div>
468 </div>
465 </div>
469 </div>
466 <script type="text/javascript">
470 <script type="text/javascript">
467 new MarkupForm('${form_id}');
471 new MarkupForm('${form_id}');
468 </script>
472 </script>
469
473
470 </%def>
474 </%def>
@@ -1,540 +1,543 b''
1 <%inherit file="/base/base.mako"/>
1 <%inherit file="/base/base.mako"/>
2 <%namespace name="dt" file="/data_table/_dt_elements.mako"/>
2 <%namespace name="dt" file="/data_table/_dt_elements.mako"/>
3
3
4 <%def name="title()">
4 <%def name="title()">
5 ${c.repo_name} ${_('New pull request')}
5 ${c.repo_name} ${_('New pull request')}
6 </%def>
6 </%def>
7
7
8 <%def name="breadcrumbs_links()"></%def>
8 <%def name="breadcrumbs_links()"></%def>
9
9
10 <%def name="menu_bar_nav()">
10 <%def name="menu_bar_nav()">
11 ${self.menu_items(active='repositories')}
11 ${self.menu_items(active='repositories')}
12 </%def>
12 </%def>
13
13
14 <%def name="menu_bar_subnav()">
14 <%def name="menu_bar_subnav()">
15 ${self.repo_menu(active='showpullrequest')}
15 ${self.repo_menu(active='showpullrequest')}
16 </%def>
16 </%def>
17
17
18 <%def name="main()">
18 <%def name="main()">
19 <div class="box">
19 <div class="box">
20 ${h.secure_form(h.route_path('pullrequest_create', repo_name=c.repo_name, _query=request.GET.mixed()), id='pull_request_form', request=request)}
20 ${h.secure_form(h.route_path('pullrequest_create', repo_name=c.repo_name, _query=request.GET.mixed()), id='pull_request_form', request=request)}
21
21
22 <div class="box pr-summary">
22 <div class="box pr-summary">
23
23
24 <div class="summary-details block-left">
24 <div class="summary-details block-left">
25
25
26
26
27 <div class="pr-details-title">
27 <div class="pr-details-title">
28 ${_('New pull request')}
28 ${_('New pull request')}
29 </div>
29 </div>
30
30
31 <div class="form" style="padding-top: 10px">
31 <div class="form" style="padding-top: 10px">
32 <!-- fields -->
32 <!-- fields -->
33
33
34 <div class="fields" >
34 <div class="fields" >
35
35
36 <div class="field">
36 <div class="field">
37 <div class="label">
37 <div class="label">
38 <label for="pullrequest_title">${_('Title')}:</label>
38 <label for="pullrequest_title">${_('Title')}:</label>
39 </div>
39 </div>
40 <div class="input">
40 <div class="input">
41 ${h.text('pullrequest_title', c.default_title, class_="medium autogenerated-title")}
41 ${h.text('pullrequest_title', c.default_title, class_="medium autogenerated-title")}
42 </div>
42 </div>
43 <p class="help-block">
44 Start the title with WIP: to prevent accidental merge of Work In Progress pull request before it's ready.
45 </p>
43 </div>
46 </div>
44
47
45 <div class="field">
48 <div class="field">
46 <div class="label label-textarea">
49 <div class="label label-textarea">
47 <label for="pullrequest_desc">${_('Description')}:</label>
50 <label for="pullrequest_desc">${_('Description')}:</label>
48 </div>
51 </div>
49 <div class="textarea text-area editor">
52 <div class="textarea text-area">
50 <input id="pr-renderer-input" type="hidden" name="description_renderer" value="${c.visual.default_renderer}">
53 <input id="pr-renderer-input" type="hidden" name="description_renderer" value="${c.visual.default_renderer}">
51 ${dt.markup_form('pullrequest_desc')}
54 ${dt.markup_form('pullrequest_desc')}
52 </div>
55 </div>
53 </div>
56 </div>
54
57
55 <div class="field">
58 <div class="field">
56 <div class="label label-textarea">
59 <div class="label label-textarea">
57 <label for="commit_flow">${_('Commit flow')}:</label>
60 <label for="commit_flow">${_('Commit flow')}:</label>
58 </div>
61 </div>
59
62
60 ## TODO: johbo: Abusing the "content" class here to get the
63 ## TODO: johbo: Abusing the "content" class here to get the
61 ## desired effect. Should be replaced by a proper solution.
64 ## desired effect. Should be replaced by a proper solution.
62
65
63 ##ORG
66 ##ORG
64 <div class="content">
67 <div class="content">
65 <strong>${_('Source repository')}:</strong>
68 <strong>${_('Source repository')}:</strong>
66 ${c.rhodecode_db_repo.description}
69 ${c.rhodecode_db_repo.description}
67 </div>
70 </div>
68 <div class="content">
71 <div class="content">
69 ${h.hidden('source_repo')}
72 ${h.hidden('source_repo')}
70 ${h.hidden('source_ref')}
73 ${h.hidden('source_ref')}
71 </div>
74 </div>
72
75
73 ##OTHER, most Probably the PARENT OF THIS FORK
76 ##OTHER, most Probably the PARENT OF THIS FORK
74 <div class="content">
77 <div class="content">
75 ## filled with JS
78 ## filled with JS
76 <div id="target_repo_desc"></div>
79 <div id="target_repo_desc"></div>
77 </div>
80 </div>
78
81
79 <div class="content">
82 <div class="content">
80 ${h.hidden('target_repo')}
83 ${h.hidden('target_repo')}
81 ${h.hidden('target_ref')}
84 ${h.hidden('target_ref')}
82 <span id="target_ref_loading" style="display: none">
85 <span id="target_ref_loading" style="display: none">
83 ${_('Loading refs...')}
86 ${_('Loading refs...')}
84 </span>
87 </span>
85 </div>
88 </div>
86 </div>
89 </div>
87
90
88 <div class="field">
91 <div class="field">
89 <div class="label label-textarea">
92 <div class="label label-textarea">
90 <label for="pullrequest_submit"></label>
93 <label for="pullrequest_submit"></label>
91 </div>
94 </div>
92 <div class="input">
95 <div class="input">
93 <div class="pr-submit-button">
96 <div class="pr-submit-button">
94 <input id="pr_submit" class="btn" name="save" type="submit" value="${_('Submit Pull Request')}">
97 <input id="pr_submit" class="btn" name="save" type="submit" value="${_('Submit Pull Request')}">
95 </div>
98 </div>
96 <div id="pr_open_message"></div>
99 <div id="pr_open_message"></div>
97 </div>
100 </div>
98 </div>
101 </div>
99
102
100 <div class="pr-spacing-container"></div>
103 <div class="pr-spacing-container"></div>
101 </div>
104 </div>
102 </div>
105 </div>
103 </div>
106 </div>
104 <div>
107 <div>
105 ## AUTHOR
108 ## AUTHOR
106 <div class="reviewers-title block-right">
109 <div class="reviewers-title block-right">
107 <div class="pr-details-title">
110 <div class="pr-details-title">
108 ${_('Author of this pull request')}
111 ${_('Author of this pull request')}
109 </div>
112 </div>
110 </div>
113 </div>
111 <div class="block-right pr-details-content reviewers">
114 <div class="block-right pr-details-content reviewers">
112 <ul class="group_members">
115 <ul class="group_members">
113 <li>
116 <li>
114 ${self.gravatar_with_user(c.rhodecode_user.email, 16, tooltip=True)}
117 ${self.gravatar_with_user(c.rhodecode_user.email, 16, tooltip=True)}
115 </li>
118 </li>
116 </ul>
119 </ul>
117 </div>
120 </div>
118
121
119 ## REVIEW RULES
122 ## REVIEW RULES
120 <div id="review_rules" style="display: none" class="reviewers-title block-right">
123 <div id="review_rules" style="display: none" class="reviewers-title block-right">
121 <div class="pr-details-title">
124 <div class="pr-details-title">
122 ${_('Reviewer rules')}
125 ${_('Reviewer rules')}
123 </div>
126 </div>
124 <div class="pr-reviewer-rules">
127 <div class="pr-reviewer-rules">
125 ## review rules will be appended here, by default reviewers logic
128 ## review rules will be appended here, by default reviewers logic
126 </div>
129 </div>
127 </div>
130 </div>
128
131
129 ## REVIEWERS
132 ## REVIEWERS
130 <div class="reviewers-title block-right">
133 <div class="reviewers-title block-right">
131 <div class="pr-details-title">
134 <div class="pr-details-title">
132 ${_('Pull request reviewers')}
135 ${_('Pull request reviewers')}
133 <span class="calculate-reviewers"> - ${_('loading...')}</span>
136 <span class="calculate-reviewers"> - ${_('loading...')}</span>
134 </div>
137 </div>
135 </div>
138 </div>
136 <div id="reviewers" class="block-right pr-details-content reviewers">
139 <div id="reviewers" class="block-right pr-details-content reviewers">
137 ## members goes here, filled via JS based on initial selection !
140 ## members goes here, filled via JS based on initial selection !
138 <input type="hidden" name="__start__" value="review_members:sequence">
141 <input type="hidden" name="__start__" value="review_members:sequence">
139 <ul id="review_members" class="group_members"></ul>
142 <ul id="review_members" class="group_members"></ul>
140 <input type="hidden" name="__end__" value="review_members:sequence">
143 <input type="hidden" name="__end__" value="review_members:sequence">
141 <div id="add_reviewer_input" class='ac'>
144 <div id="add_reviewer_input" class='ac'>
142 <div class="reviewer_ac">
145 <div class="reviewer_ac">
143 ${h.text('user', class_='ac-input', placeholder=_('Add reviewer or reviewer group'))}
146 ${h.text('user', class_='ac-input', placeholder=_('Add reviewer or reviewer group'))}
144 <div id="reviewers_container"></div>
147 <div id="reviewers_container"></div>
145 </div>
148 </div>
146 </div>
149 </div>
147 </div>
150 </div>
148 </div>
151 </div>
149 </div>
152 </div>
150 <div class="box">
153 <div class="box">
151 <div>
154 <div>
152 ## overview pulled by ajax
155 ## overview pulled by ajax
153 <div id="pull_request_overview"></div>
156 <div id="pull_request_overview"></div>
154 </div>
157 </div>
155 </div>
158 </div>
156 ${h.end_form()}
159 ${h.end_form()}
157 </div>
160 </div>
158
161
159 <script type="text/javascript">
162 <script type="text/javascript">
160 $(function(){
163 $(function(){
161 var defaultSourceRepo = '${c.default_repo_data['source_repo_name']}';
164 var defaultSourceRepo = '${c.default_repo_data['source_repo_name']}';
162 var defaultSourceRepoData = ${c.default_repo_data['source_refs_json']|n};
165 var defaultSourceRepoData = ${c.default_repo_data['source_refs_json']|n};
163 var defaultTargetRepo = '${c.default_repo_data['target_repo_name']}';
166 var defaultTargetRepo = '${c.default_repo_data['target_repo_name']}';
164 var defaultTargetRepoData = ${c.default_repo_data['target_refs_json']|n};
167 var defaultTargetRepoData = ${c.default_repo_data['target_refs_json']|n};
165
168
166 var $pullRequestForm = $('#pull_request_form');
169 var $pullRequestForm = $('#pull_request_form');
167 var $pullRequestSubmit = $('#pr_submit', $pullRequestForm);
170 var $pullRequestSubmit = $('#pr_submit', $pullRequestForm);
168 var $sourceRepo = $('#source_repo', $pullRequestForm);
171 var $sourceRepo = $('#source_repo', $pullRequestForm);
169 var $targetRepo = $('#target_repo', $pullRequestForm);
172 var $targetRepo = $('#target_repo', $pullRequestForm);
170 var $sourceRef = $('#source_ref', $pullRequestForm);
173 var $sourceRef = $('#source_ref', $pullRequestForm);
171 var $targetRef = $('#target_ref', $pullRequestForm);
174 var $targetRef = $('#target_ref', $pullRequestForm);
172
175
173 var sourceRepo = function() { return $sourceRepo.eq(0).val() };
176 var sourceRepo = function() { return $sourceRepo.eq(0).val() };
174 var sourceRef = function() { return $sourceRef.eq(0).val().split(':') };
177 var sourceRef = function() { return $sourceRef.eq(0).val().split(':') };
175
178
176 var targetRepo = function() { return $targetRepo.eq(0).val() };
179 var targetRepo = function() { return $targetRepo.eq(0).val() };
177 var targetRef = function() { return $targetRef.eq(0).val().split(':') };
180 var targetRef = function() { return $targetRef.eq(0).val().split(':') };
178
181
179 var calculateContainerWidth = function() {
182 var calculateContainerWidth = function() {
180 var maxWidth = 0;
183 var maxWidth = 0;
181 var repoSelect2Containers = ['#source_repo', '#target_repo'];
184 var repoSelect2Containers = ['#source_repo', '#target_repo'];
182 $.each(repoSelect2Containers, function(idx, value) {
185 $.each(repoSelect2Containers, function(idx, value) {
183 $(value).select2('container').width('auto');
186 $(value).select2('container').width('auto');
184 var curWidth = $(value).select2('container').width();
187 var curWidth = $(value).select2('container').width();
185 if (maxWidth <= curWidth) {
188 if (maxWidth <= curWidth) {
186 maxWidth = curWidth;
189 maxWidth = curWidth;
187 }
190 }
188 $.each(repoSelect2Containers, function(idx, value) {
191 $.each(repoSelect2Containers, function(idx, value) {
189 $(value).select2('container').width(maxWidth + 10);
192 $(value).select2('container').width(maxWidth + 10);
190 });
193 });
191 });
194 });
192 };
195 };
193
196
194 var initRefSelection = function(selectedRef) {
197 var initRefSelection = function(selectedRef) {
195 return function(element, callback) {
198 return function(element, callback) {
196 // translate our select2 id into a text, it's a mapping to show
199 // translate our select2 id into a text, it's a mapping to show
197 // simple label when selecting by internal ID.
200 // simple label when selecting by internal ID.
198 var id, refData;
201 var id, refData;
199 if (selectedRef === undefined || selectedRef === null) {
202 if (selectedRef === undefined || selectedRef === null) {
200 id = element.val();
203 id = element.val();
201 refData = element.val().split(':');
204 refData = element.val().split(':');
202
205
203 if (refData.length !== 3){
206 if (refData.length !== 3){
204 refData = ["", "", ""]
207 refData = ["", "", ""]
205 }
208 }
206 } else {
209 } else {
207 id = selectedRef;
210 id = selectedRef;
208 refData = selectedRef.split(':');
211 refData = selectedRef.split(':');
209 }
212 }
210
213
211 var text = refData[1];
214 var text = refData[1];
212 if (refData[0] === 'rev') {
215 if (refData[0] === 'rev') {
213 text = text.substring(0, 12);
216 text = text.substring(0, 12);
214 }
217 }
215
218
216 var data = {id: id, text: text};
219 var data = {id: id, text: text};
217 callback(data);
220 callback(data);
218 };
221 };
219 };
222 };
220
223
221 var formatRefSelection = function(data, container, escapeMarkup) {
224 var formatRefSelection = function(data, container, escapeMarkup) {
222 var prefix = '';
225 var prefix = '';
223 var refData = data.id.split(':');
226 var refData = data.id.split(':');
224 if (refData[0] === 'branch') {
227 if (refData[0] === 'branch') {
225 prefix = '<i class="icon-branch"></i>';
228 prefix = '<i class="icon-branch"></i>';
226 }
229 }
227 else if (refData[0] === 'book') {
230 else if (refData[0] === 'book') {
228 prefix = '<i class="icon-bookmark"></i>';
231 prefix = '<i class="icon-bookmark"></i>';
229 }
232 }
230 else if (refData[0] === 'tag') {
233 else if (refData[0] === 'tag') {
231 prefix = '<i class="icon-tag"></i>';
234 prefix = '<i class="icon-tag"></i>';
232 }
235 }
233
236
234 var originalOption = data.element;
237 var originalOption = data.element;
235 return prefix + escapeMarkup(data.text);
238 return prefix + escapeMarkup(data.text);
236 };formatSelection:
239 };formatSelection:
237
240
238 // custom code mirror
241 // custom code mirror
239 var codeMirrorInstance = $('#pullrequest_desc').get(0).MarkupForm.cm;
242 var codeMirrorInstance = $('#pullrequest_desc').get(0).MarkupForm.cm;
240
243
241 reviewersController = new ReviewersController();
244 reviewersController = new ReviewersController();
242
245
243 var queryTargetRepo = function(self, query) {
246 var queryTargetRepo = function(self, query) {
244 // cache ALL results if query is empty
247 // cache ALL results if query is empty
245 var cacheKey = query.term || '__';
248 var cacheKey = query.term || '__';
246 var cachedData = self.cachedDataSource[cacheKey];
249 var cachedData = self.cachedDataSource[cacheKey];
247
250
248 if (cachedData) {
251 if (cachedData) {
249 query.callback({results: cachedData.results});
252 query.callback({results: cachedData.results});
250 } else {
253 } else {
251 $.ajax({
254 $.ajax({
252 url: pyroutes.url('pullrequest_repo_targets', {'repo_name': templateContext.repo_name}),
255 url: pyroutes.url('pullrequest_repo_targets', {'repo_name': templateContext.repo_name}),
253 data: {query: query.term},
256 data: {query: query.term},
254 dataType: 'json',
257 dataType: 'json',
255 type: 'GET',
258 type: 'GET',
256 success: function(data) {
259 success: function(data) {
257 self.cachedDataSource[cacheKey] = data;
260 self.cachedDataSource[cacheKey] = data;
258 query.callback({results: data.results});
261 query.callback({results: data.results});
259 },
262 },
260 error: function(data, textStatus, errorThrown) {
263 error: function(data, textStatus, errorThrown) {
261 alert(
264 alert(
262 "Error while fetching entries.\nError code {0} ({1}).".format(data.status, data.statusText));
265 "Error while fetching entries.\nError code {0} ({1}).".format(data.status, data.statusText));
263 }
266 }
264 });
267 });
265 }
268 }
266 };
269 };
267
270
268 var queryTargetRefs = function(initialData, query) {
271 var queryTargetRefs = function(initialData, query) {
269 var data = {results: []};
272 var data = {results: []};
270 // filter initialData
273 // filter initialData
271 $.each(initialData, function() {
274 $.each(initialData, function() {
272 var section = this.text;
275 var section = this.text;
273 var children = [];
276 var children = [];
274 $.each(this.children, function() {
277 $.each(this.children, function() {
275 if (query.term.length === 0 ||
278 if (query.term.length === 0 ||
276 this.text.toUpperCase().indexOf(query.term.toUpperCase()) >= 0 ) {
279 this.text.toUpperCase().indexOf(query.term.toUpperCase()) >= 0 ) {
277 children.push({'id': this.id, 'text': this.text})
280 children.push({'id': this.id, 'text': this.text})
278 }
281 }
279 });
282 });
280 data.results.push({'text': section, 'children': children})
283 data.results.push({'text': section, 'children': children})
281 });
284 });
282 query.callback({results: data.results});
285 query.callback({results: data.results});
283 };
286 };
284
287
285 var loadRepoRefDiffPreview = function() {
288 var loadRepoRefDiffPreview = function() {
286
289
287 var url_data = {
290 var url_data = {
288 'repo_name': targetRepo(),
291 'repo_name': targetRepo(),
289 'target_repo': sourceRepo(),
292 'target_repo': sourceRepo(),
290 'source_ref': targetRef()[2],
293 'source_ref': targetRef()[2],
291 'source_ref_type': 'rev',
294 'source_ref_type': 'rev',
292 'target_ref': sourceRef()[2],
295 'target_ref': sourceRef()[2],
293 'target_ref_type': 'rev',
296 'target_ref_type': 'rev',
294 'merge': true,
297 'merge': true,
295 '_': Date.now() // bypass browser caching
298 '_': Date.now() // bypass browser caching
296 }; // gather the source/target ref and repo here
299 }; // gather the source/target ref and repo here
297
300
298 if (sourceRef().length !== 3 || targetRef().length !== 3) {
301 if (sourceRef().length !== 3 || targetRef().length !== 3) {
299 prButtonLock(true, "${_('Please select source and target')}");
302 prButtonLock(true, "${_('Please select source and target')}");
300 return;
303 return;
301 }
304 }
302 var url = pyroutes.url('repo_compare', url_data);
305 var url = pyroutes.url('repo_compare', url_data);
303
306
304 // lock PR button, so we cannot send PR before it's calculated
307 // lock PR button, so we cannot send PR before it's calculated
305 prButtonLock(true, "${_('Loading compare ...')}", 'compare');
308 prButtonLock(true, "${_('Loading compare ...')}", 'compare');
306
309
307 if (loadRepoRefDiffPreview._currentRequest) {
310 if (loadRepoRefDiffPreview._currentRequest) {
308 loadRepoRefDiffPreview._currentRequest.abort();
311 loadRepoRefDiffPreview._currentRequest.abort();
309 }
312 }
310
313
311 loadRepoRefDiffPreview._currentRequest = $.get(url)
314 loadRepoRefDiffPreview._currentRequest = $.get(url)
312 .error(function(data, textStatus, errorThrown) {
315 .error(function(data, textStatus, errorThrown) {
313 if (textStatus !== 'abort') {
316 if (textStatus !== 'abort') {
314 alert(
317 alert(
315 "Error while processing request.\nError code {0} ({1}).".format(
318 "Error while processing request.\nError code {0} ({1}).".format(
316 data.status, data.statusText));
319 data.status, data.statusText));
317 }
320 }
318
321
319 })
322 })
320 .done(function(data) {
323 .done(function(data) {
321 loadRepoRefDiffPreview._currentRequest = null;
324 loadRepoRefDiffPreview._currentRequest = null;
322 $('#pull_request_overview').html(data);
325 $('#pull_request_overview').html(data);
323
326
324 var commitElements = $(data).find('tr[commit_id]');
327 var commitElements = $(data).find('tr[commit_id]');
325
328
326 var prTitleAndDesc = getTitleAndDescription(
329 var prTitleAndDesc = getTitleAndDescription(
327 sourceRef()[1], commitElements, 5);
330 sourceRef()[1], commitElements, 5);
328
331
329 var title = prTitleAndDesc[0];
332 var title = prTitleAndDesc[0];
330 var proposedDescription = prTitleAndDesc[1];
333 var proposedDescription = prTitleAndDesc[1];
331
334
332 var useGeneratedTitle = (
335 var useGeneratedTitle = (
333 $('#pullrequest_title').hasClass('autogenerated-title') ||
336 $('#pullrequest_title').hasClass('autogenerated-title') ||
334 $('#pullrequest_title').val() === "");
337 $('#pullrequest_title').val() === "");
335
338
336 if (title && useGeneratedTitle) {
339 if (title && useGeneratedTitle) {
337 // use generated title if we haven't specified our own
340 // use generated title if we haven't specified our own
338 $('#pullrequest_title').val(title);
341 $('#pullrequest_title').val(title);
339 $('#pullrequest_title').addClass('autogenerated-title');
342 $('#pullrequest_title').addClass('autogenerated-title');
340
343
341 }
344 }
342
345
343 var useGeneratedDescription = (
346 var useGeneratedDescription = (
344 !codeMirrorInstance._userDefinedValue ||
347 !codeMirrorInstance._userDefinedValue ||
345 codeMirrorInstance.getValue() === "");
348 codeMirrorInstance.getValue() === "");
346
349
347 if (proposedDescription && useGeneratedDescription) {
350 if (proposedDescription && useGeneratedDescription) {
348 // set proposed content, if we haven't defined our own,
351 // set proposed content, if we haven't defined our own,
349 // or we don't have description written
352 // or we don't have description written
350 codeMirrorInstance._userDefinedValue = false; // reset state
353 codeMirrorInstance._userDefinedValue = false; // reset state
351 codeMirrorInstance.setValue(proposedDescription);
354 codeMirrorInstance.setValue(proposedDescription);
352 }
355 }
353
356
354 // refresh our codeMirror so events kicks in and it's change aware
357 // refresh our codeMirror so events kicks in and it's change aware
355 codeMirrorInstance.refresh();
358 codeMirrorInstance.refresh();
356
359
357 var msg = '';
360 var msg = '';
358 if (commitElements.length === 1) {
361 if (commitElements.length === 1) {
359 msg = "${_ungettext('This pull request will consist of __COMMITS__ commit.', 'This pull request will consist of __COMMITS__ commits.', 1)}";
362 msg = "${_ungettext('This pull request will consist of __COMMITS__ commit.', 'This pull request will consist of __COMMITS__ commits.', 1)}";
360 } else {
363 } else {
361 msg = "${_ungettext('This pull request will consist of __COMMITS__ commit.', 'This pull request will consist of __COMMITS__ commits.', 2)}";
364 msg = "${_ungettext('This pull request will consist of __COMMITS__ commit.', 'This pull request will consist of __COMMITS__ commits.', 2)}";
362 }
365 }
363
366
364 msg += ' <a id="pull_request_overview_url" href="{0}" target="_blank">${_('Show detailed compare.')}</a>'.format(url);
367 msg += ' <a id="pull_request_overview_url" href="{0}" target="_blank">${_('Show detailed compare.')}</a>'.format(url);
365
368
366 if (commitElements.length) {
369 if (commitElements.length) {
367 var commitsLink = '<a href="#pull_request_overview"><strong>{0}</strong></a>'.format(commitElements.length);
370 var commitsLink = '<a href="#pull_request_overview"><strong>{0}</strong></a>'.format(commitElements.length);
368 prButtonLock(false, msg.replace('__COMMITS__', commitsLink), 'compare');
371 prButtonLock(false, msg.replace('__COMMITS__', commitsLink), 'compare');
369 }
372 }
370 else {
373 else {
371 prButtonLock(true, "${_('There are no commits to merge.')}", 'compare');
374 prButtonLock(true, "${_('There are no commits to merge.')}", 'compare');
372 }
375 }
373
376
374
377
375 });
378 });
376 };
379 };
377
380
378 var Select2Box = function(element, overrides) {
381 var Select2Box = function(element, overrides) {
379 var globalDefaults = {
382 var globalDefaults = {
380 dropdownAutoWidth: true,
383 dropdownAutoWidth: true,
381 containerCssClass: "drop-menu",
384 containerCssClass: "drop-menu",
382 dropdownCssClass: "drop-menu-dropdown"
385 dropdownCssClass: "drop-menu-dropdown"
383 };
386 };
384
387
385 var initSelect2 = function(defaultOptions) {
388 var initSelect2 = function(defaultOptions) {
386 var options = jQuery.extend(globalDefaults, defaultOptions, overrides);
389 var options = jQuery.extend(globalDefaults, defaultOptions, overrides);
387 element.select2(options);
390 element.select2(options);
388 };
391 };
389
392
390 return {
393 return {
391 initRef: function() {
394 initRef: function() {
392 var defaultOptions = {
395 var defaultOptions = {
393 minimumResultsForSearch: 5,
396 minimumResultsForSearch: 5,
394 formatSelection: formatRefSelection
397 formatSelection: formatRefSelection
395 };
398 };
396
399
397 initSelect2(defaultOptions);
400 initSelect2(defaultOptions);
398 },
401 },
399
402
400 initRepo: function(defaultValue, readOnly) {
403 initRepo: function(defaultValue, readOnly) {
401 var defaultOptions = {
404 var defaultOptions = {
402 initSelection : function (element, callback) {
405 initSelection : function (element, callback) {
403 var data = {id: defaultValue, text: defaultValue};
406 var data = {id: defaultValue, text: defaultValue};
404 callback(data);
407 callback(data);
405 }
408 }
406 };
409 };
407
410
408 initSelect2(defaultOptions);
411 initSelect2(defaultOptions);
409
412
410 element.select2('val', defaultSourceRepo);
413 element.select2('val', defaultSourceRepo);
411 if (readOnly === true) {
414 if (readOnly === true) {
412 element.select2('readonly', true);
415 element.select2('readonly', true);
413 }
416 }
414 }
417 }
415 };
418 };
416 };
419 };
417
420
418 var initTargetRefs = function(refsData, selectedRef) {
421 var initTargetRefs = function(refsData, selectedRef) {
419
422
420 Select2Box($targetRef, {
423 Select2Box($targetRef, {
421 placeholder: "${_('Select commit reference')}",
424 placeholder: "${_('Select commit reference')}",
422 query: function(query) {
425 query: function(query) {
423 queryTargetRefs(refsData, query);
426 queryTargetRefs(refsData, query);
424 },
427 },
425 initSelection : initRefSelection(selectedRef)
428 initSelection : initRefSelection(selectedRef)
426 }).initRef();
429 }).initRef();
427
430
428 if (!(selectedRef === undefined)) {
431 if (!(selectedRef === undefined)) {
429 $targetRef.select2('val', selectedRef);
432 $targetRef.select2('val', selectedRef);
430 }
433 }
431 };
434 };
432
435
433 var targetRepoChanged = function(repoData) {
436 var targetRepoChanged = function(repoData) {
434 // generate new DESC of target repo displayed next to select
437 // generate new DESC of target repo displayed next to select
435 var prLink = pyroutes.url('pullrequest_new', {'repo_name': repoData['name']});
438 var prLink = pyroutes.url('pullrequest_new', {'repo_name': repoData['name']});
436 $('#target_repo_desc').html(
439 $('#target_repo_desc').html(
437 "<strong>${_('Target repository')}</strong>: {0}. <a href=\"{1}\">Switch base, and use as source.</a>".format(repoData['description'], prLink)
440 "<strong>${_('Target repository')}</strong>: {0}. <a href=\"{1}\">Switch base, and use as source.</a>".format(repoData['description'], prLink)
438 );
441 );
439
442
440 // generate dynamic select2 for refs.
443 // generate dynamic select2 for refs.
441 initTargetRefs(repoData['refs']['select2_refs'],
444 initTargetRefs(repoData['refs']['select2_refs'],
442 repoData['refs']['selected_ref']);
445 repoData['refs']['selected_ref']);
443
446
444 };
447 };
445
448
446 var sourceRefSelect2 = Select2Box($sourceRef, {
449 var sourceRefSelect2 = Select2Box($sourceRef, {
447 placeholder: "${_('Select commit reference')}",
450 placeholder: "${_('Select commit reference')}",
448 query: function(query) {
451 query: function(query) {
449 var initialData = defaultSourceRepoData['refs']['select2_refs'];
452 var initialData = defaultSourceRepoData['refs']['select2_refs'];
450 queryTargetRefs(initialData, query)
453 queryTargetRefs(initialData, query)
451 },
454 },
452 initSelection: initRefSelection()
455 initSelection: initRefSelection()
453 }
456 }
454 );
457 );
455
458
456 var sourceRepoSelect2 = Select2Box($sourceRepo, {
459 var sourceRepoSelect2 = Select2Box($sourceRepo, {
457 query: function(query) {}
460 query: function(query) {}
458 });
461 });
459
462
460 var targetRepoSelect2 = Select2Box($targetRepo, {
463 var targetRepoSelect2 = Select2Box($targetRepo, {
461 cachedDataSource: {},
464 cachedDataSource: {},
462 query: $.debounce(250, function(query) {
465 query: $.debounce(250, function(query) {
463 queryTargetRepo(this, query);
466 queryTargetRepo(this, query);
464 }),
467 }),
465 formatResult: formatRepoResult
468 formatResult: formatRepoResult
466 });
469 });
467
470
468 sourceRefSelect2.initRef();
471 sourceRefSelect2.initRef();
469
472
470 sourceRepoSelect2.initRepo(defaultSourceRepo, true);
473 sourceRepoSelect2.initRepo(defaultSourceRepo, true);
471
474
472 targetRepoSelect2.initRepo(defaultTargetRepo, false);
475 targetRepoSelect2.initRepo(defaultTargetRepo, false);
473
476
474 $sourceRef.on('change', function(e){
477 $sourceRef.on('change', function(e){
475 loadRepoRefDiffPreview();
478 loadRepoRefDiffPreview();
476 reviewersController.loadDefaultReviewers(
479 reviewersController.loadDefaultReviewers(
477 sourceRepo(), sourceRef(), targetRepo(), targetRef());
480 sourceRepo(), sourceRef(), targetRepo(), targetRef());
478 });
481 });
479
482
480 $targetRef.on('change', function(e){
483 $targetRef.on('change', function(e){
481 loadRepoRefDiffPreview();
484 loadRepoRefDiffPreview();
482 reviewersController.loadDefaultReviewers(
485 reviewersController.loadDefaultReviewers(
483 sourceRepo(), sourceRef(), targetRepo(), targetRef());
486 sourceRepo(), sourceRef(), targetRepo(), targetRef());
484 });
487 });
485
488
486 $targetRepo.on('change', function(e){
489 $targetRepo.on('change', function(e){
487 var repoName = $(this).val();
490 var repoName = $(this).val();
488 calculateContainerWidth();
491 calculateContainerWidth();
489 $targetRef.select2('destroy');
492 $targetRef.select2('destroy');
490 $('#target_ref_loading').show();
493 $('#target_ref_loading').show();
491
494
492 $.ajax({
495 $.ajax({
493 url: pyroutes.url('pullrequest_repo_refs',
496 url: pyroutes.url('pullrequest_repo_refs',
494 {'repo_name': templateContext.repo_name, 'target_repo_name':repoName}),
497 {'repo_name': templateContext.repo_name, 'target_repo_name':repoName}),
495 data: {},
498 data: {},
496 dataType: 'json',
499 dataType: 'json',
497 type: 'GET',
500 type: 'GET',
498 success: function(data) {
501 success: function(data) {
499 $('#target_ref_loading').hide();
502 $('#target_ref_loading').hide();
500 targetRepoChanged(data);
503 targetRepoChanged(data);
501 loadRepoRefDiffPreview();
504 loadRepoRefDiffPreview();
502 },
505 },
503 error: function(data, textStatus, errorThrown) {
506 error: function(data, textStatus, errorThrown) {
504 alert("Error while fetching entries.\nError code {0} ({1}).".format(data.status, data.statusText));
507 alert("Error while fetching entries.\nError code {0} ({1}).".format(data.status, data.statusText));
505 }
508 }
506 })
509 })
507
510
508 });
511 });
509
512
510 $pullRequestForm.on('submit', function(e){
513 $pullRequestForm.on('submit', function(e){
511 // Flush changes into textarea
514 // Flush changes into textarea
512 codeMirrorInstance.save();
515 codeMirrorInstance.save();
513 prButtonLock(true, null, 'all');
516 prButtonLock(true, null, 'all');
514 });
517 });
515
518
516 prButtonLock(true, "${_('Please select source and target')}", 'all');
519 prButtonLock(true, "${_('Please select source and target')}", 'all');
517
520
518 // auto-load on init, the target refs select2
521 // auto-load on init, the target refs select2
519 calculateContainerWidth();
522 calculateContainerWidth();
520 targetRepoChanged(defaultTargetRepoData);
523 targetRepoChanged(defaultTargetRepoData);
521
524
522 $('#pullrequest_title').on('keyup', function(e){
525 $('#pullrequest_title').on('keyup', function(e){
523 $(this).removeClass('autogenerated-title');
526 $(this).removeClass('autogenerated-title');
524 });
527 });
525
528
526 % if c.default_source_ref:
529 % if c.default_source_ref:
527 // in case we have a pre-selected value, use it now
530 // in case we have a pre-selected value, use it now
528 $sourceRef.select2('val', '${c.default_source_ref}');
531 $sourceRef.select2('val', '${c.default_source_ref}');
529 // diff preview load
532 // diff preview load
530 loadRepoRefDiffPreview();
533 loadRepoRefDiffPreview();
531 // default reviewers
534 // default reviewers
532 reviewersController.loadDefaultReviewers(
535 reviewersController.loadDefaultReviewers(
533 sourceRepo(), sourceRef(), targetRepo(), targetRef());
536 sourceRepo(), sourceRef(), targetRepo(), targetRef());
534 % endif
537 % endif
535
538
536 ReviewerAutoComplete('#user');
539 ReviewerAutoComplete('#user');
537 });
540 });
538 </script>
541 </script>
539
542
540 </%def>
543 </%def>
General Comments 0
You need to be logged in to leave comments. Login now