##// END OF EJS Templates
forks: don't expose fork link if we don't have permission to read it, and also don't pre-select in pull request.
marcink -
r3367:2e466d45 default
parent child Browse files
Show More
@@ -1,680 +1,686 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 time
21 import time
22 import logging
22 import logging
23 import operator
23 import operator
24
24
25 from pyramid.httpexceptions import HTTPFound, HTTPForbidden, HTTPBadRequest
25 from pyramid.httpexceptions import HTTPFound, HTTPForbidden, HTTPBadRequest
26
26
27 from rhodecode.lib import helpers as h, diffs
27 from rhodecode.lib import helpers as h, diffs
28 from rhodecode.lib.utils2 import (
28 from rhodecode.lib.utils2 import (
29 StrictAttributeDict, safe_int, datetime_to_time, safe_unicode)
29 StrictAttributeDict, safe_int, datetime_to_time, safe_unicode)
30 from rhodecode.lib.vcs.exceptions import RepositoryRequirementError
30 from rhodecode.lib.vcs.exceptions import RepositoryRequirementError
31 from rhodecode.model import repo
31 from rhodecode.model import repo
32 from rhodecode.model import repo_group
32 from rhodecode.model import repo_group
33 from rhodecode.model import user_group
33 from rhodecode.model import user_group
34 from rhodecode.model import user
34 from rhodecode.model import user
35 from rhodecode.model.db import User
35 from rhodecode.model.db import User
36 from rhodecode.model.scm import ScmModel
36 from rhodecode.model.scm import ScmModel
37 from rhodecode.model.settings import VcsSettingsModel
37 from rhodecode.model.settings import VcsSettingsModel
38
38
39 log = logging.getLogger(__name__)
39 log = logging.getLogger(__name__)
40
40
41
41
42 ADMIN_PREFIX = '/_admin'
42 ADMIN_PREFIX = '/_admin'
43 STATIC_FILE_PREFIX = '/_static'
43 STATIC_FILE_PREFIX = '/_static'
44
44
45 URL_NAME_REQUIREMENTS = {
45 URL_NAME_REQUIREMENTS = {
46 # group name can have a slash in them, but they must not end with a slash
46 # group name can have a slash in them, but they must not end with a slash
47 'group_name': r'.*?[^/]',
47 'group_name': r'.*?[^/]',
48 'repo_group_name': r'.*?[^/]',
48 'repo_group_name': r'.*?[^/]',
49 # repo names can have a slash in them, but they must not end with a slash
49 # repo names can have a slash in them, but they must not end with a slash
50 'repo_name': r'.*?[^/]',
50 'repo_name': r'.*?[^/]',
51 # file path eats up everything at the end
51 # file path eats up everything at the end
52 'f_path': r'.*',
52 'f_path': r'.*',
53 # reference types
53 # reference types
54 'source_ref_type': '(branch|book|tag|rev|\%\(source_ref_type\)s)',
54 'source_ref_type': '(branch|book|tag|rev|\%\(source_ref_type\)s)',
55 'target_ref_type': '(branch|book|tag|rev|\%\(target_ref_type\)s)',
55 'target_ref_type': '(branch|book|tag|rev|\%\(target_ref_type\)s)',
56 }
56 }
57
57
58
58
59 def add_route_with_slash(config,name, pattern, **kw):
59 def add_route_with_slash(config,name, pattern, **kw):
60 config.add_route(name, pattern, **kw)
60 config.add_route(name, pattern, **kw)
61 if not pattern.endswith('/'):
61 if not pattern.endswith('/'):
62 config.add_route(name + '_slash', pattern + '/', **kw)
62 config.add_route(name + '_slash', pattern + '/', **kw)
63
63
64
64
65 def add_route_requirements(route_path, requirements=None):
65 def add_route_requirements(route_path, requirements=None):
66 """
66 """
67 Adds regex requirements to pyramid routes using a mapping dict
67 Adds regex requirements to pyramid routes using a mapping dict
68 e.g::
68 e.g::
69 add_route_requirements('{repo_name}/settings')
69 add_route_requirements('{repo_name}/settings')
70 """
70 """
71 requirements = requirements or URL_NAME_REQUIREMENTS
71 requirements = requirements or URL_NAME_REQUIREMENTS
72 for key, regex in requirements.items():
72 for key, regex in requirements.items():
73 route_path = route_path.replace('{%s}' % key, '{%s:%s}' % (key, regex))
73 route_path = route_path.replace('{%s}' % key, '{%s:%s}' % (key, regex))
74 return route_path
74 return route_path
75
75
76
76
77 def get_format_ref_id(repo):
77 def get_format_ref_id(repo):
78 """Returns a `repo` specific reference formatter function"""
78 """Returns a `repo` specific reference formatter function"""
79 if h.is_svn(repo):
79 if h.is_svn(repo):
80 return _format_ref_id_svn
80 return _format_ref_id_svn
81 else:
81 else:
82 return _format_ref_id
82 return _format_ref_id
83
83
84
84
85 def _format_ref_id(name, raw_id):
85 def _format_ref_id(name, raw_id):
86 """Default formatting of a given reference `name`"""
86 """Default formatting of a given reference `name`"""
87 return name
87 return name
88
88
89
89
90 def _format_ref_id_svn(name, raw_id):
90 def _format_ref_id_svn(name, raw_id):
91 """Special way of formatting a reference for Subversion including path"""
91 """Special way of formatting a reference for Subversion including path"""
92 return '%s@%s' % (name, raw_id)
92 return '%s@%s' % (name, raw_id)
93
93
94
94
95 class TemplateArgs(StrictAttributeDict):
95 class TemplateArgs(StrictAttributeDict):
96 pass
96 pass
97
97
98
98
99 class BaseAppView(object):
99 class BaseAppView(object):
100
100
101 def __init__(self, context, request):
101 def __init__(self, context, request):
102 self.request = request
102 self.request = request
103 self.context = context
103 self.context = context
104 self.session = request.session
104 self.session = request.session
105 if not hasattr(request, 'user'):
105 if not hasattr(request, 'user'):
106 # NOTE(marcink): edge case, we ended up in matched route
106 # NOTE(marcink): edge case, we ended up in matched route
107 # but probably of web-app context, e.g API CALL/VCS CALL
107 # but probably of web-app context, e.g API CALL/VCS CALL
108 if hasattr(request, 'vcs_call') or hasattr(request, 'rpc_method'):
108 if hasattr(request, 'vcs_call') or hasattr(request, 'rpc_method'):
109 log.warning('Unable to process request `%s` in this scope', request)
109 log.warning('Unable to process request `%s` in this scope', request)
110 raise HTTPBadRequest()
110 raise HTTPBadRequest()
111
111
112 self._rhodecode_user = request.user # auth user
112 self._rhodecode_user = request.user # auth user
113 self._rhodecode_db_user = self._rhodecode_user.get_instance()
113 self._rhodecode_db_user = self._rhodecode_user.get_instance()
114 self._maybe_needs_password_change(
114 self._maybe_needs_password_change(
115 request.matched_route.name, self._rhodecode_db_user)
115 request.matched_route.name, self._rhodecode_db_user)
116
116
117 def _maybe_needs_password_change(self, view_name, user_obj):
117 def _maybe_needs_password_change(self, view_name, user_obj):
118 log.debug('Checking if user %s needs password change on view %s',
118 log.debug('Checking if user %s needs password change on view %s',
119 user_obj, view_name)
119 user_obj, view_name)
120 skip_user_views = [
120 skip_user_views = [
121 'logout', 'login',
121 'logout', 'login',
122 'my_account_password', 'my_account_password_update'
122 'my_account_password', 'my_account_password_update'
123 ]
123 ]
124
124
125 if not user_obj:
125 if not user_obj:
126 return
126 return
127
127
128 if user_obj.username == User.DEFAULT_USER:
128 if user_obj.username == User.DEFAULT_USER:
129 return
129 return
130
130
131 now = time.time()
131 now = time.time()
132 should_change = user_obj.user_data.get('force_password_change')
132 should_change = user_obj.user_data.get('force_password_change')
133 change_after = safe_int(should_change) or 0
133 change_after = safe_int(should_change) or 0
134 if should_change and now > change_after:
134 if should_change and now > change_after:
135 log.debug('User %s requires password change', user_obj)
135 log.debug('User %s requires password change', user_obj)
136 h.flash('You are required to change your password', 'warning',
136 h.flash('You are required to change your password', 'warning',
137 ignore_duplicate=True)
137 ignore_duplicate=True)
138
138
139 if view_name not in skip_user_views:
139 if view_name not in skip_user_views:
140 raise HTTPFound(
140 raise HTTPFound(
141 self.request.route_path('my_account_password'))
141 self.request.route_path('my_account_password'))
142
142
143 def _log_creation_exception(self, e, repo_name):
143 def _log_creation_exception(self, e, repo_name):
144 _ = self.request.translate
144 _ = self.request.translate
145 reason = None
145 reason = None
146 if len(e.args) == 2:
146 if len(e.args) == 2:
147 reason = e.args[1]
147 reason = e.args[1]
148
148
149 if reason == 'INVALID_CERTIFICATE':
149 if reason == 'INVALID_CERTIFICATE':
150 log.exception(
150 log.exception(
151 'Exception creating a repository: invalid certificate')
151 'Exception creating a repository: invalid certificate')
152 msg = (_('Error creating repository %s: invalid certificate')
152 msg = (_('Error creating repository %s: invalid certificate')
153 % repo_name)
153 % repo_name)
154 else:
154 else:
155 log.exception("Exception creating a repository")
155 log.exception("Exception creating a repository")
156 msg = (_('Error creating repository %s')
156 msg = (_('Error creating repository %s')
157 % repo_name)
157 % repo_name)
158 return msg
158 return msg
159
159
160 def _get_local_tmpl_context(self, include_app_defaults=True):
160 def _get_local_tmpl_context(self, include_app_defaults=True):
161 c = TemplateArgs()
161 c = TemplateArgs()
162 c.auth_user = self.request.user
162 c.auth_user = self.request.user
163 # TODO(marcink): migrate the usage of c.rhodecode_user to c.auth_user
163 # TODO(marcink): migrate the usage of c.rhodecode_user to c.auth_user
164 c.rhodecode_user = self.request.user
164 c.rhodecode_user = self.request.user
165
165
166 if include_app_defaults:
166 if include_app_defaults:
167 from rhodecode.lib.base import attach_context_attributes
167 from rhodecode.lib.base import attach_context_attributes
168 attach_context_attributes(c, self.request, self.request.user.user_id)
168 attach_context_attributes(c, self.request, self.request.user.user_id)
169
169
170 return c
170 return c
171
171
172 def _get_template_context(self, tmpl_args, **kwargs):
172 def _get_template_context(self, tmpl_args, **kwargs):
173
173
174 local_tmpl_args = {
174 local_tmpl_args = {
175 'defaults': {},
175 'defaults': {},
176 'errors': {},
176 'errors': {},
177 'c': tmpl_args
177 'c': tmpl_args
178 }
178 }
179 local_tmpl_args.update(kwargs)
179 local_tmpl_args.update(kwargs)
180 return local_tmpl_args
180 return local_tmpl_args
181
181
182 def load_default_context(self):
182 def load_default_context(self):
183 """
183 """
184 example:
184 example:
185
185
186 def load_default_context(self):
186 def load_default_context(self):
187 c = self._get_local_tmpl_context()
187 c = self._get_local_tmpl_context()
188 c.custom_var = 'foobar'
188 c.custom_var = 'foobar'
189
189
190 return c
190 return c
191 """
191 """
192 raise NotImplementedError('Needs implementation in view class')
192 raise NotImplementedError('Needs implementation in view class')
193
193
194
194
195 class RepoAppView(BaseAppView):
195 class RepoAppView(BaseAppView):
196
196
197 def __init__(self, context, request):
197 def __init__(self, context, request):
198 super(RepoAppView, self).__init__(context, request)
198 super(RepoAppView, self).__init__(context, request)
199 self.db_repo = request.db_repo
199 self.db_repo = request.db_repo
200 self.db_repo_name = self.db_repo.repo_name
200 self.db_repo_name = self.db_repo.repo_name
201 self.db_repo_pull_requests = ScmModel().get_pull_requests(self.db_repo)
201 self.db_repo_pull_requests = ScmModel().get_pull_requests(self.db_repo)
202
202
203 def _handle_missing_requirements(self, error):
203 def _handle_missing_requirements(self, error):
204 log.error(
204 log.error(
205 'Requirements are missing for repository %s: %s',
205 'Requirements are missing for repository %s: %s',
206 self.db_repo_name, safe_unicode(error))
206 self.db_repo_name, safe_unicode(error))
207
207
208 def _get_local_tmpl_context(self, include_app_defaults=True):
208 def _get_local_tmpl_context(self, include_app_defaults=True):
209 _ = self.request.translate
209 _ = self.request.translate
210 c = super(RepoAppView, self)._get_local_tmpl_context(
210 c = super(RepoAppView, self)._get_local_tmpl_context(
211 include_app_defaults=include_app_defaults)
211 include_app_defaults=include_app_defaults)
212
212
213 # register common vars for this type of view
213 # register common vars for this type of view
214 c.rhodecode_db_repo = self.db_repo
214 c.rhodecode_db_repo = self.db_repo
215 c.repo_name = self.db_repo_name
215 c.repo_name = self.db_repo_name
216 c.repository_pull_requests = self.db_repo_pull_requests
216 c.repository_pull_requests = self.db_repo_pull_requests
217 self.path_filter = PathFilter(None)
217 self.path_filter = PathFilter(None)
218
218
219 c.repository_requirements_missing = {}
219 c.repository_requirements_missing = {}
220 try:
220 try:
221 self.rhodecode_vcs_repo = self.db_repo.scm_instance()
221 self.rhodecode_vcs_repo = self.db_repo.scm_instance()
222 if self.rhodecode_vcs_repo:
222 if self.rhodecode_vcs_repo:
223 path_perms = self.rhodecode_vcs_repo.get_path_permissions(
223 path_perms = self.rhodecode_vcs_repo.get_path_permissions(
224 c.auth_user.username)
224 c.auth_user.username)
225 self.path_filter = PathFilter(path_perms)
225 self.path_filter = PathFilter(path_perms)
226 except RepositoryRequirementError as e:
226 except RepositoryRequirementError as e:
227 c.repository_requirements_missing = {'error': str(e)}
227 c.repository_requirements_missing = {'error': str(e)}
228 self._handle_missing_requirements(e)
228 self._handle_missing_requirements(e)
229 self.rhodecode_vcs_repo = None
229 self.rhodecode_vcs_repo = None
230
230
231 c.path_filter = self.path_filter # used by atom_feed_entry.mako
231 c.path_filter = self.path_filter # used by atom_feed_entry.mako
232
232
233 if self.rhodecode_vcs_repo is None:
233 if self.rhodecode_vcs_repo is None:
234 # unable to fetch this repo as vcs instance, report back to user
234 # unable to fetch this repo as vcs instance, report back to user
235 h.flash(_(
235 h.flash(_(
236 "The repository `%(repo_name)s` cannot be loaded in filesystem. "
236 "The repository `%(repo_name)s` cannot be loaded in filesystem. "
237 "Please check if it exist, or is not damaged.") %
237 "Please check if it exist, or is not damaged.") %
238 {'repo_name': c.repo_name},
238 {'repo_name': c.repo_name},
239 category='error', ignore_duplicate=True)
239 category='error', ignore_duplicate=True)
240 if c.repository_requirements_missing:
240 if c.repository_requirements_missing:
241 route = self.request.matched_route.name
241 route = self.request.matched_route.name
242 if route.startswith(('edit_repo', 'repo_summary')):
242 if route.startswith(('edit_repo', 'repo_summary')):
243 # allow summary and edit repo on missing requirements
243 # allow summary and edit repo on missing requirements
244 return c
244 return c
245
245
246 raise HTTPFound(
246 raise HTTPFound(
247 h.route_path('repo_summary', repo_name=self.db_repo_name))
247 h.route_path('repo_summary', repo_name=self.db_repo_name))
248
248
249 else: # redirect if we don't show missing requirements
249 else: # redirect if we don't show missing requirements
250 raise HTTPFound(h.route_path('home'))
250 raise HTTPFound(h.route_path('home'))
251
251
252 c.has_origin_repo_read_perm = False
253 if self.db_repo.fork:
254 c.has_origin_repo_read_perm = h.HasRepoPermissionAny(
255 'repository.write', 'repository.read', 'repository.admin')(
256 self.db_repo.fork.repo_name, 'summary fork link')
257
252 return c
258 return c
253
259
254 def _get_f_path_unchecked(self, matchdict, default=None):
260 def _get_f_path_unchecked(self, matchdict, default=None):
255 """
261 """
256 Should only be used by redirects, everything else should call _get_f_path
262 Should only be used by redirects, everything else should call _get_f_path
257 """
263 """
258 f_path = matchdict.get('f_path')
264 f_path = matchdict.get('f_path')
259 if f_path:
265 if f_path:
260 # fix for multiple initial slashes that causes errors for GIT
266 # fix for multiple initial slashes that causes errors for GIT
261 return f_path.lstrip('/')
267 return f_path.lstrip('/')
262
268
263 return default
269 return default
264
270
265 def _get_f_path(self, matchdict, default=None):
271 def _get_f_path(self, matchdict, default=None):
266 f_path_match = self._get_f_path_unchecked(matchdict, default)
272 f_path_match = self._get_f_path_unchecked(matchdict, default)
267 return self.path_filter.assert_path_permissions(f_path_match)
273 return self.path_filter.assert_path_permissions(f_path_match)
268
274
269 def _get_general_setting(self, target_repo, settings_key, default=False):
275 def _get_general_setting(self, target_repo, settings_key, default=False):
270 settings_model = VcsSettingsModel(repo=target_repo)
276 settings_model = VcsSettingsModel(repo=target_repo)
271 settings = settings_model.get_general_settings()
277 settings = settings_model.get_general_settings()
272 return settings.get(settings_key, default)
278 return settings.get(settings_key, default)
273
279
274
280
275 class PathFilter(object):
281 class PathFilter(object):
276
282
277 # Expects and instance of BasePathPermissionChecker or None
283 # Expects and instance of BasePathPermissionChecker or None
278 def __init__(self, permission_checker):
284 def __init__(self, permission_checker):
279 self.permission_checker = permission_checker
285 self.permission_checker = permission_checker
280
286
281 def assert_path_permissions(self, path):
287 def assert_path_permissions(self, path):
282 if path and self.permission_checker and not self.permission_checker.has_access(path):
288 if path and self.permission_checker and not self.permission_checker.has_access(path):
283 raise HTTPForbidden()
289 raise HTTPForbidden()
284 return path
290 return path
285
291
286 def filter_patchset(self, patchset):
292 def filter_patchset(self, patchset):
287 if not self.permission_checker or not patchset:
293 if not self.permission_checker or not patchset:
288 return patchset, False
294 return patchset, False
289 had_filtered = False
295 had_filtered = False
290 filtered_patchset = []
296 filtered_patchset = []
291 for patch in patchset:
297 for patch in patchset:
292 filename = patch.get('filename', None)
298 filename = patch.get('filename', None)
293 if not filename or self.permission_checker.has_access(filename):
299 if not filename or self.permission_checker.has_access(filename):
294 filtered_patchset.append(patch)
300 filtered_patchset.append(patch)
295 else:
301 else:
296 had_filtered = True
302 had_filtered = True
297 if had_filtered:
303 if had_filtered:
298 if isinstance(patchset, diffs.LimitedDiffContainer):
304 if isinstance(patchset, diffs.LimitedDiffContainer):
299 filtered_patchset = diffs.LimitedDiffContainer(patchset.diff_limit, patchset.cur_diff_size, filtered_patchset)
305 filtered_patchset = diffs.LimitedDiffContainer(patchset.diff_limit, patchset.cur_diff_size, filtered_patchset)
300 return filtered_patchset, True
306 return filtered_patchset, True
301 else:
307 else:
302 return patchset, False
308 return patchset, False
303
309
304 def render_patchset_filtered(self, diffset, patchset, source_ref=None, target_ref=None):
310 def render_patchset_filtered(self, diffset, patchset, source_ref=None, target_ref=None):
305 filtered_patchset, has_hidden_changes = self.filter_patchset(patchset)
311 filtered_patchset, has_hidden_changes = self.filter_patchset(patchset)
306 result = diffset.render_patchset(
312 result = diffset.render_patchset(
307 filtered_patchset, source_ref=source_ref, target_ref=target_ref)
313 filtered_patchset, source_ref=source_ref, target_ref=target_ref)
308 result.has_hidden_changes = has_hidden_changes
314 result.has_hidden_changes = has_hidden_changes
309 return result
315 return result
310
316
311 def get_raw_patch(self, diff_processor):
317 def get_raw_patch(self, diff_processor):
312 if self.permission_checker is None:
318 if self.permission_checker is None:
313 return diff_processor.as_raw()
319 return diff_processor.as_raw()
314 elif self.permission_checker.has_full_access:
320 elif self.permission_checker.has_full_access:
315 return diff_processor.as_raw()
321 return diff_processor.as_raw()
316 else:
322 else:
317 return '# Repository has user-specific filters, raw patch generation is disabled.'
323 return '# Repository has user-specific filters, raw patch generation is disabled.'
318
324
319 @property
325 @property
320 def is_enabled(self):
326 def is_enabled(self):
321 return self.permission_checker is not None
327 return self.permission_checker is not None
322
328
323
329
324 class RepoGroupAppView(BaseAppView):
330 class RepoGroupAppView(BaseAppView):
325 def __init__(self, context, request):
331 def __init__(self, context, request):
326 super(RepoGroupAppView, self).__init__(context, request)
332 super(RepoGroupAppView, self).__init__(context, request)
327 self.db_repo_group = request.db_repo_group
333 self.db_repo_group = request.db_repo_group
328 self.db_repo_group_name = self.db_repo_group.group_name
334 self.db_repo_group_name = self.db_repo_group.group_name
329
335
330 def _revoke_perms_on_yourself(self, form_result):
336 def _revoke_perms_on_yourself(self, form_result):
331 _updates = filter(lambda u: self._rhodecode_user.user_id == int(u[0]),
337 _updates = filter(lambda u: self._rhodecode_user.user_id == int(u[0]),
332 form_result['perm_updates'])
338 form_result['perm_updates'])
333 _additions = filter(lambda u: self._rhodecode_user.user_id == int(u[0]),
339 _additions = filter(lambda u: self._rhodecode_user.user_id == int(u[0]),
334 form_result['perm_additions'])
340 form_result['perm_additions'])
335 _deletions = filter(lambda u: self._rhodecode_user.user_id == int(u[0]),
341 _deletions = filter(lambda u: self._rhodecode_user.user_id == int(u[0]),
336 form_result['perm_deletions'])
342 form_result['perm_deletions'])
337 admin_perm = 'group.admin'
343 admin_perm = 'group.admin'
338 if _updates and _updates[0][1] != admin_perm or \
344 if _updates and _updates[0][1] != admin_perm or \
339 _additions and _additions[0][1] != admin_perm or \
345 _additions and _additions[0][1] != admin_perm or \
340 _deletions and _deletions[0][1] != admin_perm:
346 _deletions and _deletions[0][1] != admin_perm:
341 return True
347 return True
342 return False
348 return False
343
349
344
350
345 class UserGroupAppView(BaseAppView):
351 class UserGroupAppView(BaseAppView):
346 def __init__(self, context, request):
352 def __init__(self, context, request):
347 super(UserGroupAppView, self).__init__(context, request)
353 super(UserGroupAppView, self).__init__(context, request)
348 self.db_user_group = request.db_user_group
354 self.db_user_group = request.db_user_group
349 self.db_user_group_name = self.db_user_group.users_group_name
355 self.db_user_group_name = self.db_user_group.users_group_name
350
356
351
357
352 class UserAppView(BaseAppView):
358 class UserAppView(BaseAppView):
353 def __init__(self, context, request):
359 def __init__(self, context, request):
354 super(UserAppView, self).__init__(context, request)
360 super(UserAppView, self).__init__(context, request)
355 self.db_user = request.db_user
361 self.db_user = request.db_user
356 self.db_user_id = self.db_user.user_id
362 self.db_user_id = self.db_user.user_id
357
363
358 _ = self.request.translate
364 _ = self.request.translate
359 if not request.db_user_supports_default:
365 if not request.db_user_supports_default:
360 if self.db_user.username == User.DEFAULT_USER:
366 if self.db_user.username == User.DEFAULT_USER:
361 h.flash(_("Editing user `{}` is disabled.".format(
367 h.flash(_("Editing user `{}` is disabled.".format(
362 User.DEFAULT_USER)), category='warning')
368 User.DEFAULT_USER)), category='warning')
363 raise HTTPFound(h.route_path('users'))
369 raise HTTPFound(h.route_path('users'))
364
370
365
371
366 class DataGridAppView(object):
372 class DataGridAppView(object):
367 """
373 """
368 Common class to have re-usable grid rendering components
374 Common class to have re-usable grid rendering components
369 """
375 """
370
376
371 def _extract_ordering(self, request, column_map=None):
377 def _extract_ordering(self, request, column_map=None):
372 column_map = column_map or {}
378 column_map = column_map or {}
373 column_index = safe_int(request.GET.get('order[0][column]'))
379 column_index = safe_int(request.GET.get('order[0][column]'))
374 order_dir = request.GET.get(
380 order_dir = request.GET.get(
375 'order[0][dir]', 'desc')
381 'order[0][dir]', 'desc')
376 order_by = request.GET.get(
382 order_by = request.GET.get(
377 'columns[%s][data][sort]' % column_index, 'name_raw')
383 'columns[%s][data][sort]' % column_index, 'name_raw')
378
384
379 # translate datatable to DB columns
385 # translate datatable to DB columns
380 order_by = column_map.get(order_by) or order_by
386 order_by = column_map.get(order_by) or order_by
381
387
382 search_q = request.GET.get('search[value]')
388 search_q = request.GET.get('search[value]')
383 return search_q, order_by, order_dir
389 return search_q, order_by, order_dir
384
390
385 def _extract_chunk(self, request):
391 def _extract_chunk(self, request):
386 start = safe_int(request.GET.get('start'), 0)
392 start = safe_int(request.GET.get('start'), 0)
387 length = safe_int(request.GET.get('length'), 25)
393 length = safe_int(request.GET.get('length'), 25)
388 draw = safe_int(request.GET.get('draw'))
394 draw = safe_int(request.GET.get('draw'))
389 return draw, start, length
395 return draw, start, length
390
396
391 def _get_order_col(self, order_by, model):
397 def _get_order_col(self, order_by, model):
392 if isinstance(order_by, basestring):
398 if isinstance(order_by, basestring):
393 try:
399 try:
394 return operator.attrgetter(order_by)(model)
400 return operator.attrgetter(order_by)(model)
395 except AttributeError:
401 except AttributeError:
396 return None
402 return None
397 else:
403 else:
398 return order_by
404 return order_by
399
405
400
406
401 class BaseReferencesView(RepoAppView):
407 class BaseReferencesView(RepoAppView):
402 """
408 """
403 Base for reference view for branches, tags and bookmarks.
409 Base for reference view for branches, tags and bookmarks.
404 """
410 """
405 def load_default_context(self):
411 def load_default_context(self):
406 c = self._get_local_tmpl_context()
412 c = self._get_local_tmpl_context()
407
413
408
414
409 return c
415 return c
410
416
411 def load_refs_context(self, ref_items, partials_template):
417 def load_refs_context(self, ref_items, partials_template):
412 _render = self.request.get_partial_renderer(partials_template)
418 _render = self.request.get_partial_renderer(partials_template)
413 pre_load = ["author", "date", "message"]
419 pre_load = ["author", "date", "message"]
414
420
415 is_svn = h.is_svn(self.rhodecode_vcs_repo)
421 is_svn = h.is_svn(self.rhodecode_vcs_repo)
416 is_hg = h.is_hg(self.rhodecode_vcs_repo)
422 is_hg = h.is_hg(self.rhodecode_vcs_repo)
417
423
418 format_ref_id = get_format_ref_id(self.rhodecode_vcs_repo)
424 format_ref_id = get_format_ref_id(self.rhodecode_vcs_repo)
419
425
420 closed_refs = {}
426 closed_refs = {}
421 if is_hg:
427 if is_hg:
422 closed_refs = self.rhodecode_vcs_repo.branches_closed
428 closed_refs = self.rhodecode_vcs_repo.branches_closed
423
429
424 data = []
430 data = []
425 for ref_name, commit_id in ref_items:
431 for ref_name, commit_id in ref_items:
426 commit = self.rhodecode_vcs_repo.get_commit(
432 commit = self.rhodecode_vcs_repo.get_commit(
427 commit_id=commit_id, pre_load=pre_load)
433 commit_id=commit_id, pre_load=pre_load)
428 closed = ref_name in closed_refs
434 closed = ref_name in closed_refs
429
435
430 # TODO: johbo: Unify generation of reference links
436 # TODO: johbo: Unify generation of reference links
431 use_commit_id = '/' in ref_name or is_svn
437 use_commit_id = '/' in ref_name or is_svn
432
438
433 if use_commit_id:
439 if use_commit_id:
434 files_url = h.route_path(
440 files_url = h.route_path(
435 'repo_files',
441 'repo_files',
436 repo_name=self.db_repo_name,
442 repo_name=self.db_repo_name,
437 f_path=ref_name if is_svn else '',
443 f_path=ref_name if is_svn else '',
438 commit_id=commit_id)
444 commit_id=commit_id)
439
445
440 else:
446 else:
441 files_url = h.route_path(
447 files_url = h.route_path(
442 'repo_files',
448 'repo_files',
443 repo_name=self.db_repo_name,
449 repo_name=self.db_repo_name,
444 f_path=ref_name if is_svn else '',
450 f_path=ref_name if is_svn else '',
445 commit_id=ref_name,
451 commit_id=ref_name,
446 _query=dict(at=ref_name))
452 _query=dict(at=ref_name))
447
453
448 data.append({
454 data.append({
449 "name": _render('name', ref_name, files_url, closed),
455 "name": _render('name', ref_name, files_url, closed),
450 "name_raw": ref_name,
456 "name_raw": ref_name,
451 "date": _render('date', commit.date),
457 "date": _render('date', commit.date),
452 "date_raw": datetime_to_time(commit.date),
458 "date_raw": datetime_to_time(commit.date),
453 "author": _render('author', commit.author),
459 "author": _render('author', commit.author),
454 "commit": _render(
460 "commit": _render(
455 'commit', commit.message, commit.raw_id, commit.idx),
461 'commit', commit.message, commit.raw_id, commit.idx),
456 "commit_raw": commit.idx,
462 "commit_raw": commit.idx,
457 "compare": _render(
463 "compare": _render(
458 'compare', format_ref_id(ref_name, commit.raw_id)),
464 'compare', format_ref_id(ref_name, commit.raw_id)),
459 })
465 })
460
466
461 return data
467 return data
462
468
463
469
464 class RepoRoutePredicate(object):
470 class RepoRoutePredicate(object):
465 def __init__(self, val, config):
471 def __init__(self, val, config):
466 self.val = val
472 self.val = val
467
473
468 def text(self):
474 def text(self):
469 return 'repo_route = %s' % self.val
475 return 'repo_route = %s' % self.val
470
476
471 phash = text
477 phash = text
472
478
473 def __call__(self, info, request):
479 def __call__(self, info, request):
474 if hasattr(request, 'vcs_call'):
480 if hasattr(request, 'vcs_call'):
475 # skip vcs calls
481 # skip vcs calls
476 return
482 return
477
483
478 repo_name = info['match']['repo_name']
484 repo_name = info['match']['repo_name']
479 repo_model = repo.RepoModel()
485 repo_model = repo.RepoModel()
480
486
481 by_name_match = repo_model.get_by_repo_name(repo_name, cache=False)
487 by_name_match = repo_model.get_by_repo_name(repo_name, cache=False)
482
488
483 def redirect_if_creating(route_info, db_repo):
489 def redirect_if_creating(route_info, db_repo):
484 skip_views = ['edit_repo_advanced_delete']
490 skip_views = ['edit_repo_advanced_delete']
485 route = route_info['route']
491 route = route_info['route']
486 # we should skip delete view so we can actually "remove" repositories
492 # we should skip delete view so we can actually "remove" repositories
487 # if they get stuck in creating state.
493 # if they get stuck in creating state.
488 if route.name in skip_views:
494 if route.name in skip_views:
489 return
495 return
490
496
491 if db_repo.repo_state in [repo.Repository.STATE_PENDING]:
497 if db_repo.repo_state in [repo.Repository.STATE_PENDING]:
492 repo_creating_url = request.route_path(
498 repo_creating_url = request.route_path(
493 'repo_creating', repo_name=db_repo.repo_name)
499 'repo_creating', repo_name=db_repo.repo_name)
494 raise HTTPFound(repo_creating_url)
500 raise HTTPFound(repo_creating_url)
495
501
496 if by_name_match:
502 if by_name_match:
497 # register this as request object we can re-use later
503 # register this as request object we can re-use later
498 request.db_repo = by_name_match
504 request.db_repo = by_name_match
499 redirect_if_creating(info, by_name_match)
505 redirect_if_creating(info, by_name_match)
500 return True
506 return True
501
507
502 by_id_match = repo_model.get_repo_by_id(repo_name)
508 by_id_match = repo_model.get_repo_by_id(repo_name)
503 if by_id_match:
509 if by_id_match:
504 request.db_repo = by_id_match
510 request.db_repo = by_id_match
505 redirect_if_creating(info, by_id_match)
511 redirect_if_creating(info, by_id_match)
506 return True
512 return True
507
513
508 return False
514 return False
509
515
510
516
511 class RepoForbidArchivedRoutePredicate(object):
517 class RepoForbidArchivedRoutePredicate(object):
512 def __init__(self, val, config):
518 def __init__(self, val, config):
513 self.val = val
519 self.val = val
514
520
515 def text(self):
521 def text(self):
516 return 'repo_forbid_archived = %s' % self.val
522 return 'repo_forbid_archived = %s' % self.val
517
523
518 phash = text
524 phash = text
519
525
520 def __call__(self, info, request):
526 def __call__(self, info, request):
521 _ = request.translate
527 _ = request.translate
522 rhodecode_db_repo = request.db_repo
528 rhodecode_db_repo = request.db_repo
523
529
524 log.debug(
530 log.debug(
525 '%s checking if archived flag for repo for %s',
531 '%s checking if archived flag for repo for %s',
526 self.__class__.__name__, rhodecode_db_repo.repo_name)
532 self.__class__.__name__, rhodecode_db_repo.repo_name)
527
533
528 if rhodecode_db_repo.archived:
534 if rhodecode_db_repo.archived:
529 log.warning('Current view is not supported for archived repo:%s',
535 log.warning('Current view is not supported for archived repo:%s',
530 rhodecode_db_repo.repo_name)
536 rhodecode_db_repo.repo_name)
531
537
532 h.flash(
538 h.flash(
533 h.literal(_('Action not supported for archived repository.')),
539 h.literal(_('Action not supported for archived repository.')),
534 category='warning')
540 category='warning')
535 summary_url = request.route_path(
541 summary_url = request.route_path(
536 'repo_summary', repo_name=rhodecode_db_repo.repo_name)
542 'repo_summary', repo_name=rhodecode_db_repo.repo_name)
537 raise HTTPFound(summary_url)
543 raise HTTPFound(summary_url)
538 return True
544 return True
539
545
540
546
541 class RepoTypeRoutePredicate(object):
547 class RepoTypeRoutePredicate(object):
542 def __init__(self, val, config):
548 def __init__(self, val, config):
543 self.val = val or ['hg', 'git', 'svn']
549 self.val = val or ['hg', 'git', 'svn']
544
550
545 def text(self):
551 def text(self):
546 return 'repo_accepted_type = %s' % self.val
552 return 'repo_accepted_type = %s' % self.val
547
553
548 phash = text
554 phash = text
549
555
550 def __call__(self, info, request):
556 def __call__(self, info, request):
551 if hasattr(request, 'vcs_call'):
557 if hasattr(request, 'vcs_call'):
552 # skip vcs calls
558 # skip vcs calls
553 return
559 return
554
560
555 rhodecode_db_repo = request.db_repo
561 rhodecode_db_repo = request.db_repo
556
562
557 log.debug(
563 log.debug(
558 '%s checking repo type for %s in %s',
564 '%s checking repo type for %s in %s',
559 self.__class__.__name__, rhodecode_db_repo.repo_type, self.val)
565 self.__class__.__name__, rhodecode_db_repo.repo_type, self.val)
560
566
561 if rhodecode_db_repo.repo_type in self.val:
567 if rhodecode_db_repo.repo_type in self.val:
562 return True
568 return True
563 else:
569 else:
564 log.warning('Current view is not supported for repo type:%s',
570 log.warning('Current view is not supported for repo type:%s',
565 rhodecode_db_repo.repo_type)
571 rhodecode_db_repo.repo_type)
566 return False
572 return False
567
573
568
574
569 class RepoGroupRoutePredicate(object):
575 class RepoGroupRoutePredicate(object):
570 def __init__(self, val, config):
576 def __init__(self, val, config):
571 self.val = val
577 self.val = val
572
578
573 def text(self):
579 def text(self):
574 return 'repo_group_route = %s' % self.val
580 return 'repo_group_route = %s' % self.val
575
581
576 phash = text
582 phash = text
577
583
578 def __call__(self, info, request):
584 def __call__(self, info, request):
579 if hasattr(request, 'vcs_call'):
585 if hasattr(request, 'vcs_call'):
580 # skip vcs calls
586 # skip vcs calls
581 return
587 return
582
588
583 repo_group_name = info['match']['repo_group_name']
589 repo_group_name = info['match']['repo_group_name']
584 repo_group_model = repo_group.RepoGroupModel()
590 repo_group_model = repo_group.RepoGroupModel()
585 by_name_match = repo_group_model.get_by_group_name(repo_group_name, cache=False)
591 by_name_match = repo_group_model.get_by_group_name(repo_group_name, cache=False)
586
592
587 if by_name_match:
593 if by_name_match:
588 # register this as request object we can re-use later
594 # register this as request object we can re-use later
589 request.db_repo_group = by_name_match
595 request.db_repo_group = by_name_match
590 return True
596 return True
591
597
592 return False
598 return False
593
599
594
600
595 class UserGroupRoutePredicate(object):
601 class UserGroupRoutePredicate(object):
596 def __init__(self, val, config):
602 def __init__(self, val, config):
597 self.val = val
603 self.val = val
598
604
599 def text(self):
605 def text(self):
600 return 'user_group_route = %s' % self.val
606 return 'user_group_route = %s' % self.val
601
607
602 phash = text
608 phash = text
603
609
604 def __call__(self, info, request):
610 def __call__(self, info, request):
605 if hasattr(request, 'vcs_call'):
611 if hasattr(request, 'vcs_call'):
606 # skip vcs calls
612 # skip vcs calls
607 return
613 return
608
614
609 user_group_id = info['match']['user_group_id']
615 user_group_id = info['match']['user_group_id']
610 user_group_model = user_group.UserGroup()
616 user_group_model = user_group.UserGroup()
611 by_id_match = user_group_model.get(user_group_id, cache=False)
617 by_id_match = user_group_model.get(user_group_id, cache=False)
612
618
613 if by_id_match:
619 if by_id_match:
614 # register this as request object we can re-use later
620 # register this as request object we can re-use later
615 request.db_user_group = by_id_match
621 request.db_user_group = by_id_match
616 return True
622 return True
617
623
618 return False
624 return False
619
625
620
626
621 class UserRoutePredicateBase(object):
627 class UserRoutePredicateBase(object):
622 supports_default = None
628 supports_default = None
623
629
624 def __init__(self, val, config):
630 def __init__(self, val, config):
625 self.val = val
631 self.val = val
626
632
627 def text(self):
633 def text(self):
628 raise NotImplementedError()
634 raise NotImplementedError()
629
635
630 def __call__(self, info, request):
636 def __call__(self, info, request):
631 if hasattr(request, 'vcs_call'):
637 if hasattr(request, 'vcs_call'):
632 # skip vcs calls
638 # skip vcs calls
633 return
639 return
634
640
635 user_id = info['match']['user_id']
641 user_id = info['match']['user_id']
636 user_model = user.User()
642 user_model = user.User()
637 by_id_match = user_model.get(user_id, cache=False)
643 by_id_match = user_model.get(user_id, cache=False)
638
644
639 if by_id_match:
645 if by_id_match:
640 # register this as request object we can re-use later
646 # register this as request object we can re-use later
641 request.db_user = by_id_match
647 request.db_user = by_id_match
642 request.db_user_supports_default = self.supports_default
648 request.db_user_supports_default = self.supports_default
643 return True
649 return True
644
650
645 return False
651 return False
646
652
647
653
648 class UserRoutePredicate(UserRoutePredicateBase):
654 class UserRoutePredicate(UserRoutePredicateBase):
649 supports_default = False
655 supports_default = False
650
656
651 def text(self):
657 def text(self):
652 return 'user_route = %s' % self.val
658 return 'user_route = %s' % self.val
653
659
654 phash = text
660 phash = text
655
661
656
662
657 class UserRouteWithDefaultPredicate(UserRoutePredicateBase):
663 class UserRouteWithDefaultPredicate(UserRoutePredicateBase):
658 supports_default = True
664 supports_default = True
659
665
660 def text(self):
666 def text(self):
661 return 'user_with_default_route = %s' % self.val
667 return 'user_with_default_route = %s' % self.val
662
668
663 phash = text
669 phash = text
664
670
665
671
666 def includeme(config):
672 def includeme(config):
667 config.add_route_predicate(
673 config.add_route_predicate(
668 'repo_route', RepoRoutePredicate)
674 'repo_route', RepoRoutePredicate)
669 config.add_route_predicate(
675 config.add_route_predicate(
670 'repo_accepted_types', RepoTypeRoutePredicate)
676 'repo_accepted_types', RepoTypeRoutePredicate)
671 config.add_route_predicate(
677 config.add_route_predicate(
672 'repo_forbid_when_archived', RepoForbidArchivedRoutePredicate)
678 'repo_forbid_when_archived', RepoForbidArchivedRoutePredicate)
673 config.add_route_predicate(
679 config.add_route_predicate(
674 'repo_group_route', RepoGroupRoutePredicate)
680 'repo_group_route', RepoGroupRoutePredicate)
675 config.add_route_predicate(
681 config.add_route_predicate(
676 'user_group_route', UserGroupRoutePredicate)
682 'user_group_route', UserGroupRoutePredicate)
677 config.add_route_predicate(
683 config.add_route_predicate(
678 'user_route_with_default', UserRouteWithDefaultPredicate)
684 'user_route_with_default', UserRouteWithDefaultPredicate)
679 config.add_route_predicate(
685 config.add_route_predicate(
680 'user_route', UserRoutePredicate)
686 'user_route', UserRoutePredicate)
@@ -1,1412 +1,1413 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 import events
32 from rhodecode import events
33 from rhodecode.apps._base import RepoAppView, DataGridAppView
33 from rhodecode.apps._base import RepoAppView, DataGridAppView
34
34
35 from rhodecode.lib import helpers as h, diffs, codeblocks, channelstream
35 from rhodecode.lib import helpers as h, diffs, codeblocks, channelstream
36 from rhodecode.lib.base import vcs_operation_context
36 from rhodecode.lib.base import vcs_operation_context
37 from rhodecode.lib.diffs import load_cached_diff, cache_diff, diff_cache_exist
37 from rhodecode.lib.diffs import load_cached_diff, cache_diff, diff_cache_exist
38 from rhodecode.lib.ext_json import json
38 from rhodecode.lib.ext_json import json
39 from rhodecode.lib.auth import (
39 from rhodecode.lib.auth import (
40 LoginRequired, HasRepoPermissionAny, HasRepoPermissionAnyDecorator,
40 LoginRequired, HasRepoPermissionAny, HasRepoPermissionAnyDecorator,
41 NotAnonymous, CSRFRequired)
41 NotAnonymous, CSRFRequired)
42 from rhodecode.lib.utils2 import str2bool, safe_str, safe_unicode
42 from rhodecode.lib.utils2 import str2bool, safe_str, safe_unicode
43 from rhodecode.lib.vcs.backends.base import EmptyCommit, UpdateFailureReason
43 from rhodecode.lib.vcs.backends.base import EmptyCommit, UpdateFailureReason
44 from rhodecode.lib.vcs.exceptions import (CommitDoesNotExistError,
44 from rhodecode.lib.vcs.exceptions import (CommitDoesNotExistError,
45 RepositoryRequirementError, EmptyRepositoryError)
45 RepositoryRequirementError, EmptyRepositoryError)
46 from rhodecode.model.changeset_status import ChangesetStatusModel
46 from rhodecode.model.changeset_status import ChangesetStatusModel
47 from rhodecode.model.comment import CommentsModel
47 from rhodecode.model.comment import CommentsModel
48 from rhodecode.model.db import (func, or_, PullRequest, PullRequestVersion,
48 from rhodecode.model.db import (func, or_, PullRequest, PullRequestVersion,
49 ChangesetComment, ChangesetStatus, Repository)
49 ChangesetComment, ChangesetStatus, Repository)
50 from rhodecode.model.forms import PullRequestForm
50 from rhodecode.model.forms import PullRequestForm
51 from rhodecode.model.meta import Session
51 from rhodecode.model.meta import Session
52 from rhodecode.model.pull_request import PullRequestModel, MergeCheck
52 from rhodecode.model.pull_request import PullRequestModel, MergeCheck
53 from rhodecode.model.scm import ScmModel
53 from rhodecode.model.scm import ScmModel
54
54
55 log = logging.getLogger(__name__)
55 log = logging.getLogger(__name__)
56
56
57
57
58 class RepoPullRequestsView(RepoAppView, DataGridAppView):
58 class RepoPullRequestsView(RepoAppView, DataGridAppView):
59
59
60 def load_default_context(self):
60 def load_default_context(self):
61 c = self._get_local_tmpl_context(include_app_defaults=True)
61 c = self._get_local_tmpl_context(include_app_defaults=True)
62 c.REVIEW_STATUS_APPROVED = ChangesetStatus.STATUS_APPROVED
62 c.REVIEW_STATUS_APPROVED = ChangesetStatus.STATUS_APPROVED
63 c.REVIEW_STATUS_REJECTED = ChangesetStatus.STATUS_REJECTED
63 c.REVIEW_STATUS_REJECTED = ChangesetStatus.STATUS_REJECTED
64 # backward compat., we use for OLD PRs a plain renderer
64 # backward compat., we use for OLD PRs a plain renderer
65 c.renderer = 'plain'
65 c.renderer = 'plain'
66 return c
66 return c
67
67
68 def _get_pull_requests_list(
68 def _get_pull_requests_list(
69 self, repo_name, source, filter_type, opened_by, statuses):
69 self, repo_name, source, filter_type, opened_by, statuses):
70
70
71 draw, start, limit = self._extract_chunk(self.request)
71 draw, start, limit = self._extract_chunk(self.request)
72 search_q, order_by, order_dir = self._extract_ordering(self.request)
72 search_q, order_by, order_dir = self._extract_ordering(self.request)
73 _render = self.request.get_partial_renderer(
73 _render = self.request.get_partial_renderer(
74 'rhodecode:templates/data_table/_dt_elements.mako')
74 'rhodecode:templates/data_table/_dt_elements.mako')
75
75
76 # pagination
76 # pagination
77
77
78 if filter_type == 'awaiting_review':
78 if filter_type == 'awaiting_review':
79 pull_requests = PullRequestModel().get_awaiting_review(
79 pull_requests = PullRequestModel().get_awaiting_review(
80 repo_name, source=source, opened_by=opened_by,
80 repo_name, source=source, opened_by=opened_by,
81 statuses=statuses, offset=start, length=limit,
81 statuses=statuses, offset=start, length=limit,
82 order_by=order_by, order_dir=order_dir)
82 order_by=order_by, order_dir=order_dir)
83 pull_requests_total_count = PullRequestModel().count_awaiting_review(
83 pull_requests_total_count = PullRequestModel().count_awaiting_review(
84 repo_name, source=source, statuses=statuses,
84 repo_name, source=source, statuses=statuses,
85 opened_by=opened_by)
85 opened_by=opened_by)
86 elif filter_type == 'awaiting_my_review':
86 elif filter_type == 'awaiting_my_review':
87 pull_requests = PullRequestModel().get_awaiting_my_review(
87 pull_requests = PullRequestModel().get_awaiting_my_review(
88 repo_name, source=source, opened_by=opened_by,
88 repo_name, source=source, opened_by=opened_by,
89 user_id=self._rhodecode_user.user_id, statuses=statuses,
89 user_id=self._rhodecode_user.user_id, statuses=statuses,
90 offset=start, length=limit, order_by=order_by,
90 offset=start, length=limit, order_by=order_by,
91 order_dir=order_dir)
91 order_dir=order_dir)
92 pull_requests_total_count = PullRequestModel().count_awaiting_my_review(
92 pull_requests_total_count = PullRequestModel().count_awaiting_my_review(
93 repo_name, source=source, user_id=self._rhodecode_user.user_id,
93 repo_name, source=source, user_id=self._rhodecode_user.user_id,
94 statuses=statuses, opened_by=opened_by)
94 statuses=statuses, opened_by=opened_by)
95 else:
95 else:
96 pull_requests = PullRequestModel().get_all(
96 pull_requests = PullRequestModel().get_all(
97 repo_name, source=source, opened_by=opened_by,
97 repo_name, source=source, opened_by=opened_by,
98 statuses=statuses, offset=start, length=limit,
98 statuses=statuses, offset=start, length=limit,
99 order_by=order_by, order_dir=order_dir)
99 order_by=order_by, order_dir=order_dir)
100 pull_requests_total_count = PullRequestModel().count_all(
100 pull_requests_total_count = PullRequestModel().count_all(
101 repo_name, source=source, statuses=statuses,
101 repo_name, source=source, statuses=statuses,
102 opened_by=opened_by)
102 opened_by=opened_by)
103
103
104 data = []
104 data = []
105 comments_model = CommentsModel()
105 comments_model = CommentsModel()
106 for pr in pull_requests:
106 for pr in pull_requests:
107 comments = comments_model.get_all_comments(
107 comments = comments_model.get_all_comments(
108 self.db_repo.repo_id, pull_request=pr)
108 self.db_repo.repo_id, pull_request=pr)
109
109
110 data.append({
110 data.append({
111 'name': _render('pullrequest_name',
111 'name': _render('pullrequest_name',
112 pr.pull_request_id, pr.target_repo.repo_name),
112 pr.pull_request_id, pr.target_repo.repo_name),
113 'name_raw': pr.pull_request_id,
113 'name_raw': pr.pull_request_id,
114 'status': _render('pullrequest_status',
114 'status': _render('pullrequest_status',
115 pr.calculated_review_status()),
115 pr.calculated_review_status()),
116 'title': _render(
116 'title': _render(
117 'pullrequest_title', pr.title, pr.description),
117 'pullrequest_title', pr.title, pr.description),
118 'description': h.escape(pr.description),
118 'description': h.escape(pr.description),
119 'updated_on': _render('pullrequest_updated_on',
119 'updated_on': _render('pullrequest_updated_on',
120 h.datetime_to_time(pr.updated_on)),
120 h.datetime_to_time(pr.updated_on)),
121 'updated_on_raw': h.datetime_to_time(pr.updated_on),
121 'updated_on_raw': h.datetime_to_time(pr.updated_on),
122 'created_on': _render('pullrequest_updated_on',
122 'created_on': _render('pullrequest_updated_on',
123 h.datetime_to_time(pr.created_on)),
123 h.datetime_to_time(pr.created_on)),
124 'created_on_raw': h.datetime_to_time(pr.created_on),
124 'created_on_raw': h.datetime_to_time(pr.created_on),
125 'author': _render('pullrequest_author',
125 'author': _render('pullrequest_author',
126 pr.author.full_contact, ),
126 pr.author.full_contact, ),
127 'author_raw': pr.author.full_name,
127 'author_raw': pr.author.full_name,
128 'comments': _render('pullrequest_comments', len(comments)),
128 'comments': _render('pullrequest_comments', len(comments)),
129 'comments_raw': len(comments),
129 'comments_raw': len(comments),
130 'closed': pr.is_closed(),
130 'closed': pr.is_closed(),
131 })
131 })
132
132
133 data = ({
133 data = ({
134 'draw': draw,
134 'draw': draw,
135 'data': data,
135 'data': data,
136 'recordsTotal': pull_requests_total_count,
136 'recordsTotal': pull_requests_total_count,
137 'recordsFiltered': pull_requests_total_count,
137 'recordsFiltered': pull_requests_total_count,
138 })
138 })
139 return data
139 return data
140
140
141 def get_recache_flag(self):
141 def get_recache_flag(self):
142 for flag_name in ['force_recache', 'force-recache', 'no-cache']:
142 for flag_name in ['force_recache', 'force-recache', 'no-cache']:
143 flag_val = self.request.GET.get(flag_name)
143 flag_val = self.request.GET.get(flag_name)
144 if str2bool(flag_val):
144 if str2bool(flag_val):
145 return True
145 return True
146 return False
146 return False
147
147
148 @LoginRequired()
148 @LoginRequired()
149 @HasRepoPermissionAnyDecorator(
149 @HasRepoPermissionAnyDecorator(
150 'repository.read', 'repository.write', 'repository.admin')
150 'repository.read', 'repository.write', 'repository.admin')
151 @view_config(
151 @view_config(
152 route_name='pullrequest_show_all', request_method='GET',
152 route_name='pullrequest_show_all', request_method='GET',
153 renderer='rhodecode:templates/pullrequests/pullrequests.mako')
153 renderer='rhodecode:templates/pullrequests/pullrequests.mako')
154 def pull_request_list(self):
154 def pull_request_list(self):
155 c = self.load_default_context()
155 c = self.load_default_context()
156
156
157 req_get = self.request.GET
157 req_get = self.request.GET
158 c.source = str2bool(req_get.get('source'))
158 c.source = str2bool(req_get.get('source'))
159 c.closed = str2bool(req_get.get('closed'))
159 c.closed = str2bool(req_get.get('closed'))
160 c.my = str2bool(req_get.get('my'))
160 c.my = str2bool(req_get.get('my'))
161 c.awaiting_review = str2bool(req_get.get('awaiting_review'))
161 c.awaiting_review = str2bool(req_get.get('awaiting_review'))
162 c.awaiting_my_review = str2bool(req_get.get('awaiting_my_review'))
162 c.awaiting_my_review = str2bool(req_get.get('awaiting_my_review'))
163
163
164 c.active = 'open'
164 c.active = 'open'
165 if c.my:
165 if c.my:
166 c.active = 'my'
166 c.active = 'my'
167 if c.closed:
167 if c.closed:
168 c.active = 'closed'
168 c.active = 'closed'
169 if c.awaiting_review and not c.source:
169 if c.awaiting_review and not c.source:
170 c.active = 'awaiting'
170 c.active = 'awaiting'
171 if c.source and not c.awaiting_review:
171 if c.source and not c.awaiting_review:
172 c.active = 'source'
172 c.active = 'source'
173 if c.awaiting_my_review:
173 if c.awaiting_my_review:
174 c.active = 'awaiting_my'
174 c.active = 'awaiting_my'
175
175
176 return self._get_template_context(c)
176 return self._get_template_context(c)
177
177
178 @LoginRequired()
178 @LoginRequired()
179 @HasRepoPermissionAnyDecorator(
179 @HasRepoPermissionAnyDecorator(
180 'repository.read', 'repository.write', 'repository.admin')
180 'repository.read', 'repository.write', 'repository.admin')
181 @view_config(
181 @view_config(
182 route_name='pullrequest_show_all_data', request_method='GET',
182 route_name='pullrequest_show_all_data', request_method='GET',
183 renderer='json_ext', xhr=True)
183 renderer='json_ext', xhr=True)
184 def pull_request_list_data(self):
184 def pull_request_list_data(self):
185 self.load_default_context()
185 self.load_default_context()
186
186
187 # additional filters
187 # additional filters
188 req_get = self.request.GET
188 req_get = self.request.GET
189 source = str2bool(req_get.get('source'))
189 source = str2bool(req_get.get('source'))
190 closed = str2bool(req_get.get('closed'))
190 closed = str2bool(req_get.get('closed'))
191 my = str2bool(req_get.get('my'))
191 my = str2bool(req_get.get('my'))
192 awaiting_review = str2bool(req_get.get('awaiting_review'))
192 awaiting_review = str2bool(req_get.get('awaiting_review'))
193 awaiting_my_review = str2bool(req_get.get('awaiting_my_review'))
193 awaiting_my_review = str2bool(req_get.get('awaiting_my_review'))
194
194
195 filter_type = 'awaiting_review' if awaiting_review \
195 filter_type = 'awaiting_review' if awaiting_review \
196 else 'awaiting_my_review' if awaiting_my_review \
196 else 'awaiting_my_review' if awaiting_my_review \
197 else None
197 else None
198
198
199 opened_by = None
199 opened_by = None
200 if my:
200 if my:
201 opened_by = [self._rhodecode_user.user_id]
201 opened_by = [self._rhodecode_user.user_id]
202
202
203 statuses = [PullRequest.STATUS_NEW, PullRequest.STATUS_OPEN]
203 statuses = [PullRequest.STATUS_NEW, PullRequest.STATUS_OPEN]
204 if closed:
204 if closed:
205 statuses = [PullRequest.STATUS_CLOSED]
205 statuses = [PullRequest.STATUS_CLOSED]
206
206
207 data = self._get_pull_requests_list(
207 data = self._get_pull_requests_list(
208 repo_name=self.db_repo_name, source=source,
208 repo_name=self.db_repo_name, source=source,
209 filter_type=filter_type, opened_by=opened_by, statuses=statuses)
209 filter_type=filter_type, opened_by=opened_by, statuses=statuses)
210
210
211 return data
211 return data
212
212
213 def _is_diff_cache_enabled(self, target_repo):
213 def _is_diff_cache_enabled(self, target_repo):
214 caching_enabled = self._get_general_setting(
214 caching_enabled = self._get_general_setting(
215 target_repo, 'rhodecode_diff_cache')
215 target_repo, 'rhodecode_diff_cache')
216 log.debug('Diff caching enabled: %s', caching_enabled)
216 log.debug('Diff caching enabled: %s', caching_enabled)
217 return caching_enabled
217 return caching_enabled
218
218
219 def _get_diffset(self, source_repo_name, source_repo,
219 def _get_diffset(self, source_repo_name, source_repo,
220 source_ref_id, target_ref_id,
220 source_ref_id, target_ref_id,
221 target_commit, source_commit, diff_limit, file_limit,
221 target_commit, source_commit, diff_limit, file_limit,
222 fulldiff, hide_whitespace_changes, diff_context):
222 fulldiff, hide_whitespace_changes, diff_context):
223
223
224 vcs_diff = PullRequestModel().get_diff(
224 vcs_diff = PullRequestModel().get_diff(
225 source_repo, source_ref_id, target_ref_id,
225 source_repo, source_ref_id, target_ref_id,
226 hide_whitespace_changes, diff_context)
226 hide_whitespace_changes, diff_context)
227
227
228 diff_processor = diffs.DiffProcessor(
228 diff_processor = diffs.DiffProcessor(
229 vcs_diff, format='newdiff', diff_limit=diff_limit,
229 vcs_diff, format='newdiff', diff_limit=diff_limit,
230 file_limit=file_limit, show_full_diff=fulldiff)
230 file_limit=file_limit, show_full_diff=fulldiff)
231
231
232 _parsed = diff_processor.prepare()
232 _parsed = diff_processor.prepare()
233
233
234 diffset = codeblocks.DiffSet(
234 diffset = codeblocks.DiffSet(
235 repo_name=self.db_repo_name,
235 repo_name=self.db_repo_name,
236 source_repo_name=source_repo_name,
236 source_repo_name=source_repo_name,
237 source_node_getter=codeblocks.diffset_node_getter(target_commit),
237 source_node_getter=codeblocks.diffset_node_getter(target_commit),
238 target_node_getter=codeblocks.diffset_node_getter(source_commit),
238 target_node_getter=codeblocks.diffset_node_getter(source_commit),
239 )
239 )
240 diffset = self.path_filter.render_patchset_filtered(
240 diffset = self.path_filter.render_patchset_filtered(
241 diffset, _parsed, target_commit.raw_id, source_commit.raw_id)
241 diffset, _parsed, target_commit.raw_id, source_commit.raw_id)
242
242
243 return diffset
243 return diffset
244
244
245 def _get_range_diffset(self, source_scm, source_repo,
245 def _get_range_diffset(self, source_scm, source_repo,
246 commit1, commit2, diff_limit, file_limit,
246 commit1, commit2, diff_limit, file_limit,
247 fulldiff, hide_whitespace_changes, diff_context):
247 fulldiff, hide_whitespace_changes, diff_context):
248 vcs_diff = source_scm.get_diff(
248 vcs_diff = source_scm.get_diff(
249 commit1, commit2,
249 commit1, commit2,
250 ignore_whitespace=hide_whitespace_changes,
250 ignore_whitespace=hide_whitespace_changes,
251 context=diff_context)
251 context=diff_context)
252
252
253 diff_processor = diffs.DiffProcessor(
253 diff_processor = diffs.DiffProcessor(
254 vcs_diff, format='newdiff', diff_limit=diff_limit,
254 vcs_diff, format='newdiff', diff_limit=diff_limit,
255 file_limit=file_limit, show_full_diff=fulldiff)
255 file_limit=file_limit, show_full_diff=fulldiff)
256
256
257 _parsed = diff_processor.prepare()
257 _parsed = diff_processor.prepare()
258
258
259 diffset = codeblocks.DiffSet(
259 diffset = codeblocks.DiffSet(
260 repo_name=source_repo.repo_name,
260 repo_name=source_repo.repo_name,
261 source_node_getter=codeblocks.diffset_node_getter(commit1),
261 source_node_getter=codeblocks.diffset_node_getter(commit1),
262 target_node_getter=codeblocks.diffset_node_getter(commit2))
262 target_node_getter=codeblocks.diffset_node_getter(commit2))
263
263
264 diffset = self.path_filter.render_patchset_filtered(
264 diffset = self.path_filter.render_patchset_filtered(
265 diffset, _parsed, commit1.raw_id, commit2.raw_id)
265 diffset, _parsed, commit1.raw_id, commit2.raw_id)
266
266
267 return diffset
267 return diffset
268
268
269 @LoginRequired()
269 @LoginRequired()
270 @HasRepoPermissionAnyDecorator(
270 @HasRepoPermissionAnyDecorator(
271 'repository.read', 'repository.write', 'repository.admin')
271 'repository.read', 'repository.write', 'repository.admin')
272 @view_config(
272 @view_config(
273 route_name='pullrequest_show', request_method='GET',
273 route_name='pullrequest_show', request_method='GET',
274 renderer='rhodecode:templates/pullrequests/pullrequest_show.mako')
274 renderer='rhodecode:templates/pullrequests/pullrequest_show.mako')
275 def pull_request_show(self):
275 def pull_request_show(self):
276 pull_request_id = self.request.matchdict['pull_request_id']
276 pull_request_id = self.request.matchdict['pull_request_id']
277
277
278 c = self.load_default_context()
278 c = self.load_default_context()
279
279
280 version = self.request.GET.get('version')
280 version = self.request.GET.get('version')
281 from_version = self.request.GET.get('from_version') or version
281 from_version = self.request.GET.get('from_version') or version
282 merge_checks = self.request.GET.get('merge_checks')
282 merge_checks = self.request.GET.get('merge_checks')
283 c.fulldiff = str2bool(self.request.GET.get('fulldiff'))
283 c.fulldiff = str2bool(self.request.GET.get('fulldiff'))
284
284
285 # fetch global flags of ignore ws or context lines
285 # fetch global flags of ignore ws or context lines
286 diff_context = diffs.get_diff_context(self.request)
286 diff_context = diffs.get_diff_context(self.request)
287 hide_whitespace_changes = diffs.get_diff_whitespace_flag(self.request)
287 hide_whitespace_changes = diffs.get_diff_whitespace_flag(self.request)
288
288
289 force_refresh = str2bool(self.request.GET.get('force_refresh'))
289 force_refresh = str2bool(self.request.GET.get('force_refresh'))
290
290
291 (pull_request_latest,
291 (pull_request_latest,
292 pull_request_at_ver,
292 pull_request_at_ver,
293 pull_request_display_obj,
293 pull_request_display_obj,
294 at_version) = PullRequestModel().get_pr_version(
294 at_version) = PullRequestModel().get_pr_version(
295 pull_request_id, version=version)
295 pull_request_id, version=version)
296 pr_closed = pull_request_latest.is_closed()
296 pr_closed = pull_request_latest.is_closed()
297
297
298 if pr_closed and (version or from_version):
298 if pr_closed and (version or from_version):
299 # not allow to browse versions
299 # not allow to browse versions
300 raise HTTPFound(h.route_path(
300 raise HTTPFound(h.route_path(
301 'pullrequest_show', repo_name=self.db_repo_name,
301 'pullrequest_show', repo_name=self.db_repo_name,
302 pull_request_id=pull_request_id))
302 pull_request_id=pull_request_id))
303
303
304 versions = pull_request_display_obj.versions()
304 versions = pull_request_display_obj.versions()
305 # used to store per-commit range diffs
305 # used to store per-commit range diffs
306 c.changes = collections.OrderedDict()
306 c.changes = collections.OrderedDict()
307 c.range_diff_on = self.request.GET.get('range-diff') == "1"
307 c.range_diff_on = self.request.GET.get('range-diff') == "1"
308
308
309 c.at_version = at_version
309 c.at_version = at_version
310 c.at_version_num = (at_version
310 c.at_version_num = (at_version
311 if at_version and at_version != 'latest'
311 if at_version and at_version != 'latest'
312 else None)
312 else None)
313 c.at_version_pos = ChangesetComment.get_index_from_version(
313 c.at_version_pos = ChangesetComment.get_index_from_version(
314 c.at_version_num, versions)
314 c.at_version_num, versions)
315
315
316 (prev_pull_request_latest,
316 (prev_pull_request_latest,
317 prev_pull_request_at_ver,
317 prev_pull_request_at_ver,
318 prev_pull_request_display_obj,
318 prev_pull_request_display_obj,
319 prev_at_version) = PullRequestModel().get_pr_version(
319 prev_at_version) = PullRequestModel().get_pr_version(
320 pull_request_id, version=from_version)
320 pull_request_id, version=from_version)
321
321
322 c.from_version = prev_at_version
322 c.from_version = prev_at_version
323 c.from_version_num = (prev_at_version
323 c.from_version_num = (prev_at_version
324 if prev_at_version and prev_at_version != 'latest'
324 if prev_at_version and prev_at_version != 'latest'
325 else None)
325 else None)
326 c.from_version_pos = ChangesetComment.get_index_from_version(
326 c.from_version_pos = ChangesetComment.get_index_from_version(
327 c.from_version_num, versions)
327 c.from_version_num, versions)
328
328
329 # define if we're in COMPARE mode or VIEW at version mode
329 # define if we're in COMPARE mode or VIEW at version mode
330 compare = at_version != prev_at_version
330 compare = at_version != prev_at_version
331
331
332 # pull_requests repo_name we opened it against
332 # pull_requests repo_name we opened it against
333 # ie. target_repo must match
333 # ie. target_repo must match
334 if self.db_repo_name != pull_request_at_ver.target_repo.repo_name:
334 if self.db_repo_name != pull_request_at_ver.target_repo.repo_name:
335 raise HTTPNotFound()
335 raise HTTPNotFound()
336
336
337 c.shadow_clone_url = PullRequestModel().get_shadow_clone_url(
337 c.shadow_clone_url = PullRequestModel().get_shadow_clone_url(
338 pull_request_at_ver)
338 pull_request_at_ver)
339
339
340 c.pull_request = pull_request_display_obj
340 c.pull_request = pull_request_display_obj
341 c.renderer = pull_request_at_ver.description_renderer or c.renderer
341 c.renderer = pull_request_at_ver.description_renderer or c.renderer
342 c.pull_request_latest = pull_request_latest
342 c.pull_request_latest = pull_request_latest
343
343
344 if compare or (at_version and not at_version == 'latest'):
344 if compare or (at_version and not at_version == 'latest'):
345 c.allowed_to_change_status = False
345 c.allowed_to_change_status = False
346 c.allowed_to_update = False
346 c.allowed_to_update = False
347 c.allowed_to_merge = False
347 c.allowed_to_merge = False
348 c.allowed_to_delete = False
348 c.allowed_to_delete = False
349 c.allowed_to_comment = False
349 c.allowed_to_comment = False
350 c.allowed_to_close = False
350 c.allowed_to_close = False
351 else:
351 else:
352 can_change_status = PullRequestModel().check_user_change_status(
352 can_change_status = PullRequestModel().check_user_change_status(
353 pull_request_at_ver, self._rhodecode_user)
353 pull_request_at_ver, self._rhodecode_user)
354 c.allowed_to_change_status = can_change_status and not pr_closed
354 c.allowed_to_change_status = can_change_status and not pr_closed
355
355
356 c.allowed_to_update = PullRequestModel().check_user_update(
356 c.allowed_to_update = PullRequestModel().check_user_update(
357 pull_request_latest, self._rhodecode_user) and not pr_closed
357 pull_request_latest, self._rhodecode_user) and not pr_closed
358 c.allowed_to_merge = PullRequestModel().check_user_merge(
358 c.allowed_to_merge = PullRequestModel().check_user_merge(
359 pull_request_latest, self._rhodecode_user) and not pr_closed
359 pull_request_latest, self._rhodecode_user) and not pr_closed
360 c.allowed_to_delete = PullRequestModel().check_user_delete(
360 c.allowed_to_delete = PullRequestModel().check_user_delete(
361 pull_request_latest, self._rhodecode_user) and not pr_closed
361 pull_request_latest, self._rhodecode_user) and not pr_closed
362 c.allowed_to_comment = not pr_closed
362 c.allowed_to_comment = not pr_closed
363 c.allowed_to_close = c.allowed_to_merge and not pr_closed
363 c.allowed_to_close = c.allowed_to_merge and not pr_closed
364
364
365 c.forbid_adding_reviewers = False
365 c.forbid_adding_reviewers = False
366 c.forbid_author_to_review = False
366 c.forbid_author_to_review = False
367 c.forbid_commit_author_to_review = False
367 c.forbid_commit_author_to_review = False
368
368
369 if pull_request_latest.reviewer_data and \
369 if pull_request_latest.reviewer_data and \
370 'rules' in pull_request_latest.reviewer_data:
370 'rules' in pull_request_latest.reviewer_data:
371 rules = pull_request_latest.reviewer_data['rules'] or {}
371 rules = pull_request_latest.reviewer_data['rules'] or {}
372 try:
372 try:
373 c.forbid_adding_reviewers = rules.get(
373 c.forbid_adding_reviewers = rules.get(
374 'forbid_adding_reviewers')
374 'forbid_adding_reviewers')
375 c.forbid_author_to_review = rules.get(
375 c.forbid_author_to_review = rules.get(
376 'forbid_author_to_review')
376 'forbid_author_to_review')
377 c.forbid_commit_author_to_review = rules.get(
377 c.forbid_commit_author_to_review = rules.get(
378 'forbid_commit_author_to_review')
378 'forbid_commit_author_to_review')
379 except Exception:
379 except Exception:
380 pass
380 pass
381
381
382 # check merge capabilities
382 # check merge capabilities
383 _merge_check = MergeCheck.validate(
383 _merge_check = MergeCheck.validate(
384 pull_request_latest, auth_user=self._rhodecode_user,
384 pull_request_latest, auth_user=self._rhodecode_user,
385 translator=self.request.translate,
385 translator=self.request.translate,
386 force_shadow_repo_refresh=force_refresh)
386 force_shadow_repo_refresh=force_refresh)
387 c.pr_merge_errors = _merge_check.error_details
387 c.pr_merge_errors = _merge_check.error_details
388 c.pr_merge_possible = not _merge_check.failed
388 c.pr_merge_possible = not _merge_check.failed
389 c.pr_merge_message = _merge_check.merge_msg
389 c.pr_merge_message = _merge_check.merge_msg
390
390
391 c.pr_merge_info = MergeCheck.get_merge_conditions(
391 c.pr_merge_info = MergeCheck.get_merge_conditions(
392 pull_request_latest, translator=self.request.translate)
392 pull_request_latest, translator=self.request.translate)
393
393
394 c.pull_request_review_status = _merge_check.review_status
394 c.pull_request_review_status = _merge_check.review_status
395 if merge_checks:
395 if merge_checks:
396 self.request.override_renderer = \
396 self.request.override_renderer = \
397 'rhodecode:templates/pullrequests/pullrequest_merge_checks.mako'
397 'rhodecode:templates/pullrequests/pullrequest_merge_checks.mako'
398 return self._get_template_context(c)
398 return self._get_template_context(c)
399
399
400 comments_model = CommentsModel()
400 comments_model = CommentsModel()
401
401
402 # reviewers and statuses
402 # reviewers and statuses
403 c.pull_request_reviewers = pull_request_at_ver.reviewers_statuses()
403 c.pull_request_reviewers = pull_request_at_ver.reviewers_statuses()
404 allowed_reviewers = [x[0].user_id for x in c.pull_request_reviewers]
404 allowed_reviewers = [x[0].user_id for x in c.pull_request_reviewers]
405
405
406 # GENERAL COMMENTS with versions #
406 # GENERAL COMMENTS with versions #
407 q = comments_model._all_general_comments_of_pull_request(pull_request_latest)
407 q = comments_model._all_general_comments_of_pull_request(pull_request_latest)
408 q = q.order_by(ChangesetComment.comment_id.asc())
408 q = q.order_by(ChangesetComment.comment_id.asc())
409 general_comments = q
409 general_comments = q
410
410
411 # pick comments we want to render at current version
411 # pick comments we want to render at current version
412 c.comment_versions = comments_model.aggregate_comments(
412 c.comment_versions = comments_model.aggregate_comments(
413 general_comments, versions, c.at_version_num)
413 general_comments, versions, c.at_version_num)
414 c.comments = c.comment_versions[c.at_version_num]['until']
414 c.comments = c.comment_versions[c.at_version_num]['until']
415
415
416 # INLINE COMMENTS with versions #
416 # INLINE COMMENTS with versions #
417 q = comments_model._all_inline_comments_of_pull_request(pull_request_latest)
417 q = comments_model._all_inline_comments_of_pull_request(pull_request_latest)
418 q = q.order_by(ChangesetComment.comment_id.asc())
418 q = q.order_by(ChangesetComment.comment_id.asc())
419 inline_comments = q
419 inline_comments = q
420
420
421 c.inline_versions = comments_model.aggregate_comments(
421 c.inline_versions = comments_model.aggregate_comments(
422 inline_comments, versions, c.at_version_num, inline=True)
422 inline_comments, versions, c.at_version_num, inline=True)
423
423
424 # inject latest version
424 # inject latest version
425 latest_ver = PullRequest.get_pr_display_object(
425 latest_ver = PullRequest.get_pr_display_object(
426 pull_request_latest, pull_request_latest)
426 pull_request_latest, pull_request_latest)
427
427
428 c.versions = versions + [latest_ver]
428 c.versions = versions + [latest_ver]
429
429
430 # if we use version, then do not show later comments
430 # if we use version, then do not show later comments
431 # than current version
431 # than current version
432 display_inline_comments = collections.defaultdict(
432 display_inline_comments = collections.defaultdict(
433 lambda: collections.defaultdict(list))
433 lambda: collections.defaultdict(list))
434 for co in inline_comments:
434 for co in inline_comments:
435 if c.at_version_num:
435 if c.at_version_num:
436 # pick comments that are at least UPTO given version, so we
436 # pick comments that are at least UPTO given version, so we
437 # don't render comments for higher version
437 # don't render comments for higher version
438 should_render = co.pull_request_version_id and \
438 should_render = co.pull_request_version_id and \
439 co.pull_request_version_id <= c.at_version_num
439 co.pull_request_version_id <= c.at_version_num
440 else:
440 else:
441 # showing all, for 'latest'
441 # showing all, for 'latest'
442 should_render = True
442 should_render = True
443
443
444 if should_render:
444 if should_render:
445 display_inline_comments[co.f_path][co.line_no].append(co)
445 display_inline_comments[co.f_path][co.line_no].append(co)
446
446
447 # load diff data into template context, if we use compare mode then
447 # load diff data into template context, if we use compare mode then
448 # diff is calculated based on changes between versions of PR
448 # diff is calculated based on changes between versions of PR
449
449
450 source_repo = pull_request_at_ver.source_repo
450 source_repo = pull_request_at_ver.source_repo
451 source_ref_id = pull_request_at_ver.source_ref_parts.commit_id
451 source_ref_id = pull_request_at_ver.source_ref_parts.commit_id
452
452
453 target_repo = pull_request_at_ver.target_repo
453 target_repo = pull_request_at_ver.target_repo
454 target_ref_id = pull_request_at_ver.target_ref_parts.commit_id
454 target_ref_id = pull_request_at_ver.target_ref_parts.commit_id
455
455
456 if compare:
456 if compare:
457 # in compare switch the diff base to latest commit from prev version
457 # in compare switch the diff base to latest commit from prev version
458 target_ref_id = prev_pull_request_display_obj.revisions[0]
458 target_ref_id = prev_pull_request_display_obj.revisions[0]
459
459
460 # despite opening commits for bookmarks/branches/tags, we always
460 # despite opening commits for bookmarks/branches/tags, we always
461 # convert this to rev to prevent changes after bookmark or branch change
461 # convert this to rev to prevent changes after bookmark or branch change
462 c.source_ref_type = 'rev'
462 c.source_ref_type = 'rev'
463 c.source_ref = source_ref_id
463 c.source_ref = source_ref_id
464
464
465 c.target_ref_type = 'rev'
465 c.target_ref_type = 'rev'
466 c.target_ref = target_ref_id
466 c.target_ref = target_ref_id
467
467
468 c.source_repo = source_repo
468 c.source_repo = source_repo
469 c.target_repo = target_repo
469 c.target_repo = target_repo
470
470
471 c.commit_ranges = []
471 c.commit_ranges = []
472 source_commit = EmptyCommit()
472 source_commit = EmptyCommit()
473 target_commit = EmptyCommit()
473 target_commit = EmptyCommit()
474 c.missing_requirements = False
474 c.missing_requirements = False
475
475
476 source_scm = source_repo.scm_instance()
476 source_scm = source_repo.scm_instance()
477 target_scm = target_repo.scm_instance()
477 target_scm = target_repo.scm_instance()
478
478
479 shadow_scm = None
479 shadow_scm = None
480 try:
480 try:
481 shadow_scm = pull_request_latest.get_shadow_repo()
481 shadow_scm = pull_request_latest.get_shadow_repo()
482 except Exception:
482 except Exception:
483 log.debug('Failed to get shadow repo', exc_info=True)
483 log.debug('Failed to get shadow repo', exc_info=True)
484 # try first the existing source_repo, and then shadow
484 # try first the existing source_repo, and then shadow
485 # repo if we can obtain one
485 # repo if we can obtain one
486 commits_source_repo = source_scm or shadow_scm
486 commits_source_repo = source_scm or shadow_scm
487
487
488 c.commits_source_repo = commits_source_repo
488 c.commits_source_repo = commits_source_repo
489 c.ancestor = None # set it to None, to hide it from PR view
489 c.ancestor = None # set it to None, to hide it from PR view
490
490
491 # empty version means latest, so we keep this to prevent
491 # empty version means latest, so we keep this to prevent
492 # double caching
492 # double caching
493 version_normalized = version or 'latest'
493 version_normalized = version or 'latest'
494 from_version_normalized = from_version or 'latest'
494 from_version_normalized = from_version or 'latest'
495
495
496 cache_path = self.rhodecode_vcs_repo.get_create_shadow_cache_pr_path(target_repo)
496 cache_path = self.rhodecode_vcs_repo.get_create_shadow_cache_pr_path(target_repo)
497 cache_file_path = diff_cache_exist(
497 cache_file_path = diff_cache_exist(
498 cache_path, 'pull_request', pull_request_id, version_normalized,
498 cache_path, 'pull_request', pull_request_id, version_normalized,
499 from_version_normalized, source_ref_id, target_ref_id,
499 from_version_normalized, source_ref_id, target_ref_id,
500 hide_whitespace_changes, diff_context, c.fulldiff)
500 hide_whitespace_changes, diff_context, c.fulldiff)
501
501
502 caching_enabled = self._is_diff_cache_enabled(c.target_repo)
502 caching_enabled = self._is_diff_cache_enabled(c.target_repo)
503 force_recache = self.get_recache_flag()
503 force_recache = self.get_recache_flag()
504
504
505 cached_diff = None
505 cached_diff = None
506 if caching_enabled:
506 if caching_enabled:
507 cached_diff = load_cached_diff(cache_file_path)
507 cached_diff = load_cached_diff(cache_file_path)
508
508
509 has_proper_commit_cache = (
509 has_proper_commit_cache = (
510 cached_diff and cached_diff.get('commits')
510 cached_diff and cached_diff.get('commits')
511 and len(cached_diff.get('commits', [])) == 5
511 and len(cached_diff.get('commits', [])) == 5
512 and cached_diff.get('commits')[0]
512 and cached_diff.get('commits')[0]
513 and cached_diff.get('commits')[3])
513 and cached_diff.get('commits')[3])
514
514
515 if not force_recache and not c.range_diff_on and has_proper_commit_cache:
515 if not force_recache and not c.range_diff_on and has_proper_commit_cache:
516 diff_commit_cache = \
516 diff_commit_cache = \
517 (ancestor_commit, commit_cache, missing_requirements,
517 (ancestor_commit, commit_cache, missing_requirements,
518 source_commit, target_commit) = cached_diff['commits']
518 source_commit, target_commit) = cached_diff['commits']
519 else:
519 else:
520 diff_commit_cache = \
520 diff_commit_cache = \
521 (ancestor_commit, commit_cache, missing_requirements,
521 (ancestor_commit, commit_cache, missing_requirements,
522 source_commit, target_commit) = self.get_commits(
522 source_commit, target_commit) = self.get_commits(
523 commits_source_repo,
523 commits_source_repo,
524 pull_request_at_ver,
524 pull_request_at_ver,
525 source_commit,
525 source_commit,
526 source_ref_id,
526 source_ref_id,
527 source_scm,
527 source_scm,
528 target_commit,
528 target_commit,
529 target_ref_id,
529 target_ref_id,
530 target_scm)
530 target_scm)
531
531
532 # register our commit range
532 # register our commit range
533 for comm in commit_cache.values():
533 for comm in commit_cache.values():
534 c.commit_ranges.append(comm)
534 c.commit_ranges.append(comm)
535
535
536 c.missing_requirements = missing_requirements
536 c.missing_requirements = missing_requirements
537 c.ancestor_commit = ancestor_commit
537 c.ancestor_commit = ancestor_commit
538 c.statuses = source_repo.statuses(
538 c.statuses = source_repo.statuses(
539 [x.raw_id for x in c.commit_ranges])
539 [x.raw_id for x in c.commit_ranges])
540
540
541 # auto collapse if we have more than limit
541 # auto collapse if we have more than limit
542 collapse_limit = diffs.DiffProcessor._collapse_commits_over
542 collapse_limit = diffs.DiffProcessor._collapse_commits_over
543 c.collapse_all_commits = len(c.commit_ranges) > collapse_limit
543 c.collapse_all_commits = len(c.commit_ranges) > collapse_limit
544 c.compare_mode = compare
544 c.compare_mode = compare
545
545
546 # diff_limit is the old behavior, will cut off the whole diff
546 # diff_limit is the old behavior, will cut off the whole diff
547 # if the limit is applied otherwise will just hide the
547 # if the limit is applied otherwise will just hide the
548 # big files from the front-end
548 # big files from the front-end
549 diff_limit = c.visual.cut_off_limit_diff
549 diff_limit = c.visual.cut_off_limit_diff
550 file_limit = c.visual.cut_off_limit_file
550 file_limit = c.visual.cut_off_limit_file
551
551
552 c.missing_commits = False
552 c.missing_commits = False
553 if (c.missing_requirements
553 if (c.missing_requirements
554 or isinstance(source_commit, EmptyCommit)
554 or isinstance(source_commit, EmptyCommit)
555 or source_commit == target_commit):
555 or source_commit == target_commit):
556
556
557 c.missing_commits = True
557 c.missing_commits = True
558 else:
558 else:
559 c.inline_comments = display_inline_comments
559 c.inline_comments = display_inline_comments
560
560
561 has_proper_diff_cache = cached_diff and cached_diff.get('commits')
561 has_proper_diff_cache = cached_diff and cached_diff.get('commits')
562 if not force_recache and has_proper_diff_cache:
562 if not force_recache and has_proper_diff_cache:
563 c.diffset = cached_diff['diff']
563 c.diffset = cached_diff['diff']
564 (ancestor_commit, commit_cache, missing_requirements,
564 (ancestor_commit, commit_cache, missing_requirements,
565 source_commit, target_commit) = cached_diff['commits']
565 source_commit, target_commit) = cached_diff['commits']
566 else:
566 else:
567 c.diffset = self._get_diffset(
567 c.diffset = self._get_diffset(
568 c.source_repo.repo_name, commits_source_repo,
568 c.source_repo.repo_name, commits_source_repo,
569 source_ref_id, target_ref_id,
569 source_ref_id, target_ref_id,
570 target_commit, source_commit,
570 target_commit, source_commit,
571 diff_limit, file_limit, c.fulldiff,
571 diff_limit, file_limit, c.fulldiff,
572 hide_whitespace_changes, diff_context)
572 hide_whitespace_changes, diff_context)
573
573
574 # save cached diff
574 # save cached diff
575 if caching_enabled:
575 if caching_enabled:
576 cache_diff(cache_file_path, c.diffset, diff_commit_cache)
576 cache_diff(cache_file_path, c.diffset, diff_commit_cache)
577
577
578 c.limited_diff = c.diffset.limited_diff
578 c.limited_diff = c.diffset.limited_diff
579
579
580 # calculate removed files that are bound to comments
580 # calculate removed files that are bound to comments
581 comment_deleted_files = [
581 comment_deleted_files = [
582 fname for fname in display_inline_comments
582 fname for fname in display_inline_comments
583 if fname not in c.diffset.file_stats]
583 if fname not in c.diffset.file_stats]
584
584
585 c.deleted_files_comments = collections.defaultdict(dict)
585 c.deleted_files_comments = collections.defaultdict(dict)
586 for fname, per_line_comments in display_inline_comments.items():
586 for fname, per_line_comments in display_inline_comments.items():
587 if fname in comment_deleted_files:
587 if fname in comment_deleted_files:
588 c.deleted_files_comments[fname]['stats'] = 0
588 c.deleted_files_comments[fname]['stats'] = 0
589 c.deleted_files_comments[fname]['comments'] = list()
589 c.deleted_files_comments[fname]['comments'] = list()
590 for lno, comments in per_line_comments.items():
590 for lno, comments in per_line_comments.items():
591 c.deleted_files_comments[fname]['comments'].extend(comments)
591 c.deleted_files_comments[fname]['comments'].extend(comments)
592
592
593 # maybe calculate the range diff
593 # maybe calculate the range diff
594 if c.range_diff_on:
594 if c.range_diff_on:
595 # TODO(marcink): set whitespace/context
595 # TODO(marcink): set whitespace/context
596 context_lcl = 3
596 context_lcl = 3
597 ign_whitespace_lcl = False
597 ign_whitespace_lcl = False
598
598
599 for commit in c.commit_ranges:
599 for commit in c.commit_ranges:
600 commit2 = commit
600 commit2 = commit
601 commit1 = commit.first_parent
601 commit1 = commit.first_parent
602
602
603 range_diff_cache_file_path = diff_cache_exist(
603 range_diff_cache_file_path = diff_cache_exist(
604 cache_path, 'diff', commit.raw_id,
604 cache_path, 'diff', commit.raw_id,
605 ign_whitespace_lcl, context_lcl, c.fulldiff)
605 ign_whitespace_lcl, context_lcl, c.fulldiff)
606
606
607 cached_diff = None
607 cached_diff = None
608 if caching_enabled:
608 if caching_enabled:
609 cached_diff = load_cached_diff(range_diff_cache_file_path)
609 cached_diff = load_cached_diff(range_diff_cache_file_path)
610
610
611 has_proper_diff_cache = cached_diff and cached_diff.get('diff')
611 has_proper_diff_cache = cached_diff and cached_diff.get('diff')
612 if not force_recache and has_proper_diff_cache:
612 if not force_recache and has_proper_diff_cache:
613 diffset = cached_diff['diff']
613 diffset = cached_diff['diff']
614 else:
614 else:
615 diffset = self._get_range_diffset(
615 diffset = self._get_range_diffset(
616 source_scm, source_repo,
616 source_scm, source_repo,
617 commit1, commit2, diff_limit, file_limit,
617 commit1, commit2, diff_limit, file_limit,
618 c.fulldiff, ign_whitespace_lcl, context_lcl
618 c.fulldiff, ign_whitespace_lcl, context_lcl
619 )
619 )
620
620
621 # save cached diff
621 # save cached diff
622 if caching_enabled:
622 if caching_enabled:
623 cache_diff(range_diff_cache_file_path, diffset, None)
623 cache_diff(range_diff_cache_file_path, diffset, None)
624
624
625 c.changes[commit.raw_id] = diffset
625 c.changes[commit.raw_id] = diffset
626
626
627 # this is a hack to properly display links, when creating PR, the
627 # this is a hack to properly display links, when creating PR, the
628 # compare view and others uses different notation, and
628 # compare view and others uses different notation, and
629 # compare_commits.mako renders links based on the target_repo.
629 # compare_commits.mako renders links based on the target_repo.
630 # We need to swap that here to generate it properly on the html side
630 # We need to swap that here to generate it properly on the html side
631 c.target_repo = c.source_repo
631 c.target_repo = c.source_repo
632
632
633 c.commit_statuses = ChangesetStatus.STATUSES
633 c.commit_statuses = ChangesetStatus.STATUSES
634
634
635 c.show_version_changes = not pr_closed
635 c.show_version_changes = not pr_closed
636 if c.show_version_changes:
636 if c.show_version_changes:
637 cur_obj = pull_request_at_ver
637 cur_obj = pull_request_at_ver
638 prev_obj = prev_pull_request_at_ver
638 prev_obj = prev_pull_request_at_ver
639
639
640 old_commit_ids = prev_obj.revisions
640 old_commit_ids = prev_obj.revisions
641 new_commit_ids = cur_obj.revisions
641 new_commit_ids = cur_obj.revisions
642 commit_changes = PullRequestModel()._calculate_commit_id_changes(
642 commit_changes = PullRequestModel()._calculate_commit_id_changes(
643 old_commit_ids, new_commit_ids)
643 old_commit_ids, new_commit_ids)
644 c.commit_changes_summary = commit_changes
644 c.commit_changes_summary = commit_changes
645
645
646 # calculate the diff for commits between versions
646 # calculate the diff for commits between versions
647 c.commit_changes = []
647 c.commit_changes = []
648 mark = lambda cs, fw: list(
648 mark = lambda cs, fw: list(
649 h.itertools.izip_longest([], cs, fillvalue=fw))
649 h.itertools.izip_longest([], cs, fillvalue=fw))
650 for c_type, raw_id in mark(commit_changes.added, 'a') \
650 for c_type, raw_id in mark(commit_changes.added, 'a') \
651 + mark(commit_changes.removed, 'r') \
651 + mark(commit_changes.removed, 'r') \
652 + mark(commit_changes.common, 'c'):
652 + mark(commit_changes.common, 'c'):
653
653
654 if raw_id in commit_cache:
654 if raw_id in commit_cache:
655 commit = commit_cache[raw_id]
655 commit = commit_cache[raw_id]
656 else:
656 else:
657 try:
657 try:
658 commit = commits_source_repo.get_commit(raw_id)
658 commit = commits_source_repo.get_commit(raw_id)
659 except CommitDoesNotExistError:
659 except CommitDoesNotExistError:
660 # in case we fail extracting still use "dummy" commit
660 # in case we fail extracting still use "dummy" commit
661 # for display in commit diff
661 # for display in commit diff
662 commit = h.AttributeDict(
662 commit = h.AttributeDict(
663 {'raw_id': raw_id,
663 {'raw_id': raw_id,
664 'message': 'EMPTY or MISSING COMMIT'})
664 'message': 'EMPTY or MISSING COMMIT'})
665 c.commit_changes.append([c_type, commit])
665 c.commit_changes.append([c_type, commit])
666
666
667 # current user review statuses for each version
667 # current user review statuses for each version
668 c.review_versions = {}
668 c.review_versions = {}
669 if self._rhodecode_user.user_id in allowed_reviewers:
669 if self._rhodecode_user.user_id in allowed_reviewers:
670 for co in general_comments:
670 for co in general_comments:
671 if co.author.user_id == self._rhodecode_user.user_id:
671 if co.author.user_id == self._rhodecode_user.user_id:
672 status = co.status_change
672 status = co.status_change
673 if status:
673 if status:
674 _ver_pr = status[0].comment.pull_request_version_id
674 _ver_pr = status[0].comment.pull_request_version_id
675 c.review_versions[_ver_pr] = status[0]
675 c.review_versions[_ver_pr] = status[0]
676
676
677 return self._get_template_context(c)
677 return self._get_template_context(c)
678
678
679 def get_commits(
679 def get_commits(
680 self, commits_source_repo, pull_request_at_ver, source_commit,
680 self, commits_source_repo, pull_request_at_ver, source_commit,
681 source_ref_id, source_scm, target_commit, target_ref_id, target_scm):
681 source_ref_id, source_scm, target_commit, target_ref_id, target_scm):
682 commit_cache = collections.OrderedDict()
682 commit_cache = collections.OrderedDict()
683 missing_requirements = False
683 missing_requirements = False
684 try:
684 try:
685 pre_load = ["author", "branch", "date", "message", "parents"]
685 pre_load = ["author", "branch", "date", "message", "parents"]
686 show_revs = pull_request_at_ver.revisions
686 show_revs = pull_request_at_ver.revisions
687 for rev in show_revs:
687 for rev in show_revs:
688 comm = commits_source_repo.get_commit(
688 comm = commits_source_repo.get_commit(
689 commit_id=rev, pre_load=pre_load)
689 commit_id=rev, pre_load=pre_load)
690 commit_cache[comm.raw_id] = comm
690 commit_cache[comm.raw_id] = comm
691
691
692 # Order here matters, we first need to get target, and then
692 # Order here matters, we first need to get target, and then
693 # the source
693 # the source
694 target_commit = commits_source_repo.get_commit(
694 target_commit = commits_source_repo.get_commit(
695 commit_id=safe_str(target_ref_id))
695 commit_id=safe_str(target_ref_id))
696
696
697 source_commit = commits_source_repo.get_commit(
697 source_commit = commits_source_repo.get_commit(
698 commit_id=safe_str(source_ref_id))
698 commit_id=safe_str(source_ref_id))
699 except CommitDoesNotExistError:
699 except CommitDoesNotExistError:
700 log.warning(
700 log.warning(
701 'Failed to get commit from `{}` repo'.format(
701 'Failed to get commit from `{}` repo'.format(
702 commits_source_repo), exc_info=True)
702 commits_source_repo), exc_info=True)
703 except RepositoryRequirementError:
703 except RepositoryRequirementError:
704 log.warning(
704 log.warning(
705 'Failed to get all required data from repo', exc_info=True)
705 'Failed to get all required data from repo', exc_info=True)
706 missing_requirements = True
706 missing_requirements = True
707 ancestor_commit = None
707 ancestor_commit = None
708 try:
708 try:
709 ancestor_id = source_scm.get_common_ancestor(
709 ancestor_id = source_scm.get_common_ancestor(
710 source_commit.raw_id, target_commit.raw_id, target_scm)
710 source_commit.raw_id, target_commit.raw_id, target_scm)
711 ancestor_commit = source_scm.get_commit(ancestor_id)
711 ancestor_commit = source_scm.get_commit(ancestor_id)
712 except Exception:
712 except Exception:
713 ancestor_commit = None
713 ancestor_commit = None
714 return ancestor_commit, commit_cache, missing_requirements, source_commit, target_commit
714 return ancestor_commit, commit_cache, missing_requirements, source_commit, target_commit
715
715
716 def assure_not_empty_repo(self):
716 def assure_not_empty_repo(self):
717 _ = self.request.translate
717 _ = self.request.translate
718
718
719 try:
719 try:
720 self.db_repo.scm_instance().get_commit()
720 self.db_repo.scm_instance().get_commit()
721 except EmptyRepositoryError:
721 except EmptyRepositoryError:
722 h.flash(h.literal(_('There are no commits yet')),
722 h.flash(h.literal(_('There are no commits yet')),
723 category='warning')
723 category='warning')
724 raise HTTPFound(
724 raise HTTPFound(
725 h.route_path('repo_summary', repo_name=self.db_repo.repo_name))
725 h.route_path('repo_summary', repo_name=self.db_repo.repo_name))
726
726
727 @LoginRequired()
727 @LoginRequired()
728 @NotAnonymous()
728 @NotAnonymous()
729 @HasRepoPermissionAnyDecorator(
729 @HasRepoPermissionAnyDecorator(
730 'repository.read', 'repository.write', 'repository.admin')
730 'repository.read', 'repository.write', 'repository.admin')
731 @view_config(
731 @view_config(
732 route_name='pullrequest_new', request_method='GET',
732 route_name='pullrequest_new', request_method='GET',
733 renderer='rhodecode:templates/pullrequests/pullrequest.mako')
733 renderer='rhodecode:templates/pullrequests/pullrequest.mako')
734 def pull_request_new(self):
734 def pull_request_new(self):
735 _ = self.request.translate
735 _ = self.request.translate
736 c = self.load_default_context()
736 c = self.load_default_context()
737
737
738 self.assure_not_empty_repo()
738 self.assure_not_empty_repo()
739 source_repo = self.db_repo
739 source_repo = self.db_repo
740
740
741 commit_id = self.request.GET.get('commit')
741 commit_id = self.request.GET.get('commit')
742 branch_ref = self.request.GET.get('branch')
742 branch_ref = self.request.GET.get('branch')
743 bookmark_ref = self.request.GET.get('bookmark')
743 bookmark_ref = self.request.GET.get('bookmark')
744
744
745 try:
745 try:
746 source_repo_data = PullRequestModel().generate_repo_data(
746 source_repo_data = PullRequestModel().generate_repo_data(
747 source_repo, commit_id=commit_id,
747 source_repo, commit_id=commit_id,
748 branch=branch_ref, bookmark=bookmark_ref,
748 branch=branch_ref, bookmark=bookmark_ref,
749 translator=self.request.translate)
749 translator=self.request.translate)
750 except CommitDoesNotExistError as e:
750 except CommitDoesNotExistError as e:
751 log.exception(e)
751 log.exception(e)
752 h.flash(_('Commit does not exist'), 'error')
752 h.flash(_('Commit does not exist'), 'error')
753 raise HTTPFound(
753 raise HTTPFound(
754 h.route_path('pullrequest_new', repo_name=source_repo.repo_name))
754 h.route_path('pullrequest_new', repo_name=source_repo.repo_name))
755
755
756 default_target_repo = source_repo
756 default_target_repo = source_repo
757
757
758 if source_repo.parent:
758 if source_repo.parent and c.has_origin_repo_read_perm:
759 parent_vcs_obj = source_repo.parent.scm_instance()
759 parent_vcs_obj = source_repo.parent.scm_instance()
760 if parent_vcs_obj and not parent_vcs_obj.is_empty():
760 if parent_vcs_obj and not parent_vcs_obj.is_empty():
761 # change default if we have a parent repo
761 # change default if we have a parent repo
762 default_target_repo = source_repo.parent
762 default_target_repo = source_repo.parent
763
763
764 target_repo_data = PullRequestModel().generate_repo_data(
764 target_repo_data = PullRequestModel().generate_repo_data(
765 default_target_repo, translator=self.request.translate)
765 default_target_repo, translator=self.request.translate)
766
766
767 selected_source_ref = source_repo_data['refs']['selected_ref']
767 selected_source_ref = source_repo_data['refs']['selected_ref']
768 title_source_ref = ''
768 title_source_ref = ''
769 if selected_source_ref:
769 if selected_source_ref:
770 title_source_ref = selected_source_ref.split(':', 2)[1]
770 title_source_ref = selected_source_ref.split(':', 2)[1]
771 c.default_title = PullRequestModel().generate_pullrequest_title(
771 c.default_title = PullRequestModel().generate_pullrequest_title(
772 source=source_repo.repo_name,
772 source=source_repo.repo_name,
773 source_ref=title_source_ref,
773 source_ref=title_source_ref,
774 target=default_target_repo.repo_name
774 target=default_target_repo.repo_name
775 )
775 )
776
776
777 c.default_repo_data = {
777 c.default_repo_data = {
778 'source_repo_name': source_repo.repo_name,
778 'source_repo_name': source_repo.repo_name,
779 'source_refs_json': json.dumps(source_repo_data),
779 'source_refs_json': json.dumps(source_repo_data),
780 'target_repo_name': default_target_repo.repo_name,
780 'target_repo_name': default_target_repo.repo_name,
781 'target_refs_json': json.dumps(target_repo_data),
781 'target_refs_json': json.dumps(target_repo_data),
782 }
782 }
783 c.default_source_ref = selected_source_ref
783 c.default_source_ref = selected_source_ref
784
784
785 return self._get_template_context(c)
785 return self._get_template_context(c)
786
786
787 @LoginRequired()
787 @LoginRequired()
788 @NotAnonymous()
788 @NotAnonymous()
789 @HasRepoPermissionAnyDecorator(
789 @HasRepoPermissionAnyDecorator(
790 'repository.read', 'repository.write', 'repository.admin')
790 'repository.read', 'repository.write', 'repository.admin')
791 @view_config(
791 @view_config(
792 route_name='pullrequest_repo_refs', request_method='GET',
792 route_name='pullrequest_repo_refs', request_method='GET',
793 renderer='json_ext', xhr=True)
793 renderer='json_ext', xhr=True)
794 def pull_request_repo_refs(self):
794 def pull_request_repo_refs(self):
795 self.load_default_context()
795 self.load_default_context()
796 target_repo_name = self.request.matchdict['target_repo_name']
796 target_repo_name = self.request.matchdict['target_repo_name']
797 repo = Repository.get_by_repo_name(target_repo_name)
797 repo = Repository.get_by_repo_name(target_repo_name)
798 if not repo:
798 if not repo:
799 raise HTTPNotFound()
799 raise HTTPNotFound()
800
800
801 target_perm = HasRepoPermissionAny(
801 target_perm = HasRepoPermissionAny(
802 'repository.read', 'repository.write', 'repository.admin')(
802 'repository.read', 'repository.write', 'repository.admin')(
803 target_repo_name)
803 target_repo_name)
804 if not target_perm:
804 if not target_perm:
805 raise HTTPNotFound()
805 raise HTTPNotFound()
806
806
807 return PullRequestModel().generate_repo_data(
807 return PullRequestModel().generate_repo_data(
808 repo, translator=self.request.translate)
808 repo, translator=self.request.translate)
809
809
810 @LoginRequired()
810 @LoginRequired()
811 @NotAnonymous()
811 @NotAnonymous()
812 @HasRepoPermissionAnyDecorator(
812 @HasRepoPermissionAnyDecorator(
813 'repository.read', 'repository.write', 'repository.admin')
813 'repository.read', 'repository.write', 'repository.admin')
814 @view_config(
814 @view_config(
815 route_name='pullrequest_repo_targets', request_method='GET',
815 route_name='pullrequest_repo_targets', request_method='GET',
816 renderer='json_ext', xhr=True)
816 renderer='json_ext', xhr=True)
817 def pullrequest_repo_targets(self):
817 def pullrequest_repo_targets(self):
818 _ = self.request.translate
818 _ = self.request.translate
819 filter_query = self.request.GET.get('query')
819 filter_query = self.request.GET.get('query')
820
820
821 # get the parents
821 # get the parents
822 parent_target_repos = []
822 parent_target_repos = []
823 if self.db_repo.parent:
823 if self.db_repo.parent:
824 parents_query = Repository.query() \
824 parents_query = Repository.query() \
825 .order_by(func.length(Repository.repo_name)) \
825 .order_by(func.length(Repository.repo_name)) \
826 .filter(Repository.fork_id == self.db_repo.parent.repo_id)
826 .filter(Repository.fork_id == self.db_repo.parent.repo_id)
827
827
828 if filter_query:
828 if filter_query:
829 ilike_expression = u'%{}%'.format(safe_unicode(filter_query))
829 ilike_expression = u'%{}%'.format(safe_unicode(filter_query))
830 parents_query = parents_query.filter(
830 parents_query = parents_query.filter(
831 Repository.repo_name.ilike(ilike_expression))
831 Repository.repo_name.ilike(ilike_expression))
832 parents = parents_query.limit(20).all()
832 parents = parents_query.limit(20).all()
833
833
834 for parent in parents:
834 for parent in parents:
835 parent_vcs_obj = parent.scm_instance()
835 parent_vcs_obj = parent.scm_instance()
836 if parent_vcs_obj and not parent_vcs_obj.is_empty():
836 if parent_vcs_obj and not parent_vcs_obj.is_empty():
837 parent_target_repos.append(parent)
837 parent_target_repos.append(parent)
838
838
839 # get other forks, and repo itself
839 # get other forks, and repo itself
840 query = Repository.query() \
840 query = Repository.query() \
841 .order_by(func.length(Repository.repo_name)) \
841 .order_by(func.length(Repository.repo_name)) \
842 .filter(
842 .filter(
843 or_(Repository.repo_id == self.db_repo.repo_id, # repo itself
843 or_(Repository.repo_id == self.db_repo.repo_id, # repo itself
844 Repository.fork_id == self.db_repo.repo_id) # forks of this repo
844 Repository.fork_id == self.db_repo.repo_id) # forks of this repo
845 ) \
845 ) \
846 .filter(~Repository.repo_id.in_([x.repo_id for x in parent_target_repos]))
846 .filter(~Repository.repo_id.in_([x.repo_id for x in parent_target_repos]))
847
847
848 if filter_query:
848 if filter_query:
849 ilike_expression = u'%{}%'.format(safe_unicode(filter_query))
849 ilike_expression = u'%{}%'.format(safe_unicode(filter_query))
850 query = query.filter(Repository.repo_name.ilike(ilike_expression))
850 query = query.filter(Repository.repo_name.ilike(ilike_expression))
851
851
852 limit = max(20 - len(parent_target_repos), 5) # not less then 5
852 limit = max(20 - len(parent_target_repos), 5) # not less then 5
853 target_repos = query.limit(limit).all()
853 target_repos = query.limit(limit).all()
854
854
855 all_target_repos = target_repos + parent_target_repos
855 all_target_repos = target_repos + parent_target_repos
856
856
857 repos = []
857 repos = []
858 # This checks permissions to the repositories
858 for obj in ScmModel().get_repos(all_target_repos):
859 for obj in ScmModel().get_repos(all_target_repos):
859 repos.append({
860 repos.append({
860 'id': obj['name'],
861 'id': obj['name'],
861 'text': obj['name'],
862 'text': obj['name'],
862 'type': 'repo',
863 'type': 'repo',
863 'repo_id': obj['dbrepo']['repo_id'],
864 'repo_id': obj['dbrepo']['repo_id'],
864 'repo_type': obj['dbrepo']['repo_type'],
865 'repo_type': obj['dbrepo']['repo_type'],
865 'private': obj['dbrepo']['private'],
866 'private': obj['dbrepo']['private'],
866
867
867 })
868 })
868
869
869 data = {
870 data = {
870 'more': False,
871 'more': False,
871 'results': [{
872 'results': [{
872 'text': _('Repositories'),
873 'text': _('Repositories'),
873 'children': repos
874 'children': repos
874 }] if repos else []
875 }] if repos else []
875 }
876 }
876 return data
877 return data
877
878
878 @LoginRequired()
879 @LoginRequired()
879 @NotAnonymous()
880 @NotAnonymous()
880 @HasRepoPermissionAnyDecorator(
881 @HasRepoPermissionAnyDecorator(
881 'repository.read', 'repository.write', 'repository.admin')
882 'repository.read', 'repository.write', 'repository.admin')
882 @CSRFRequired()
883 @CSRFRequired()
883 @view_config(
884 @view_config(
884 route_name='pullrequest_create', request_method='POST',
885 route_name='pullrequest_create', request_method='POST',
885 renderer=None)
886 renderer=None)
886 def pull_request_create(self):
887 def pull_request_create(self):
887 _ = self.request.translate
888 _ = self.request.translate
888 self.assure_not_empty_repo()
889 self.assure_not_empty_repo()
889 self.load_default_context()
890 self.load_default_context()
890
891
891 controls = peppercorn.parse(self.request.POST.items())
892 controls = peppercorn.parse(self.request.POST.items())
892
893
893 try:
894 try:
894 form = PullRequestForm(
895 form = PullRequestForm(
895 self.request.translate, self.db_repo.repo_id)()
896 self.request.translate, self.db_repo.repo_id)()
896 _form = form.to_python(controls)
897 _form = form.to_python(controls)
897 except formencode.Invalid as errors:
898 except formencode.Invalid as errors:
898 if errors.error_dict.get('revisions'):
899 if errors.error_dict.get('revisions'):
899 msg = 'Revisions: %s' % errors.error_dict['revisions']
900 msg = 'Revisions: %s' % errors.error_dict['revisions']
900 elif errors.error_dict.get('pullrequest_title'):
901 elif errors.error_dict.get('pullrequest_title'):
901 msg = errors.error_dict.get('pullrequest_title')
902 msg = errors.error_dict.get('pullrequest_title')
902 else:
903 else:
903 msg = _('Error creating pull request: {}').format(errors)
904 msg = _('Error creating pull request: {}').format(errors)
904 log.exception(msg)
905 log.exception(msg)
905 h.flash(msg, 'error')
906 h.flash(msg, 'error')
906
907
907 # would rather just go back to form ...
908 # would rather just go back to form ...
908 raise HTTPFound(
909 raise HTTPFound(
909 h.route_path('pullrequest_new', repo_name=self.db_repo_name))
910 h.route_path('pullrequest_new', repo_name=self.db_repo_name))
910
911
911 source_repo = _form['source_repo']
912 source_repo = _form['source_repo']
912 source_ref = _form['source_ref']
913 source_ref = _form['source_ref']
913 target_repo = _form['target_repo']
914 target_repo = _form['target_repo']
914 target_ref = _form['target_ref']
915 target_ref = _form['target_ref']
915 commit_ids = _form['revisions'][::-1]
916 commit_ids = _form['revisions'][::-1]
916
917
917 # find the ancestor for this pr
918 # find the ancestor for this pr
918 source_db_repo = Repository.get_by_repo_name(_form['source_repo'])
919 source_db_repo = Repository.get_by_repo_name(_form['source_repo'])
919 target_db_repo = Repository.get_by_repo_name(_form['target_repo'])
920 target_db_repo = Repository.get_by_repo_name(_form['target_repo'])
920
921
921 # re-check permissions again here
922 # re-check permissions again here
922 # source_repo we must have read permissions
923 # source_repo we must have read permissions
923
924
924 source_perm = HasRepoPermissionAny(
925 source_perm = HasRepoPermissionAny(
925 'repository.read',
926 'repository.read',
926 'repository.write', 'repository.admin')(source_db_repo.repo_name)
927 'repository.write', 'repository.admin')(source_db_repo.repo_name)
927 if not source_perm:
928 if not source_perm:
928 msg = _('Not Enough permissions to source repo `{}`.'.format(
929 msg = _('Not Enough permissions to source repo `{}`.'.format(
929 source_db_repo.repo_name))
930 source_db_repo.repo_name))
930 h.flash(msg, category='error')
931 h.flash(msg, category='error')
931 # copy the args back to redirect
932 # copy the args back to redirect
932 org_query = self.request.GET.mixed()
933 org_query = self.request.GET.mixed()
933 raise HTTPFound(
934 raise HTTPFound(
934 h.route_path('pullrequest_new', repo_name=self.db_repo_name,
935 h.route_path('pullrequest_new', repo_name=self.db_repo_name,
935 _query=org_query))
936 _query=org_query))
936
937
937 # target repo we must have read permissions, and also later on
938 # target repo we must have read permissions, and also later on
938 # we want to check branch permissions here
939 # we want to check branch permissions here
939 target_perm = HasRepoPermissionAny(
940 target_perm = HasRepoPermissionAny(
940 'repository.read',
941 'repository.read',
941 'repository.write', 'repository.admin')(target_db_repo.repo_name)
942 'repository.write', 'repository.admin')(target_db_repo.repo_name)
942 if not target_perm:
943 if not target_perm:
943 msg = _('Not Enough permissions to target repo `{}`.'.format(
944 msg = _('Not Enough permissions to target repo `{}`.'.format(
944 target_db_repo.repo_name))
945 target_db_repo.repo_name))
945 h.flash(msg, category='error')
946 h.flash(msg, category='error')
946 # copy the args back to redirect
947 # copy the args back to redirect
947 org_query = self.request.GET.mixed()
948 org_query = self.request.GET.mixed()
948 raise HTTPFound(
949 raise HTTPFound(
949 h.route_path('pullrequest_new', repo_name=self.db_repo_name,
950 h.route_path('pullrequest_new', repo_name=self.db_repo_name,
950 _query=org_query))
951 _query=org_query))
951
952
952 source_scm = source_db_repo.scm_instance()
953 source_scm = source_db_repo.scm_instance()
953 target_scm = target_db_repo.scm_instance()
954 target_scm = target_db_repo.scm_instance()
954
955
955 source_commit = source_scm.get_commit(source_ref.split(':')[-1])
956 source_commit = source_scm.get_commit(source_ref.split(':')[-1])
956 target_commit = target_scm.get_commit(target_ref.split(':')[-1])
957 target_commit = target_scm.get_commit(target_ref.split(':')[-1])
957
958
958 ancestor = source_scm.get_common_ancestor(
959 ancestor = source_scm.get_common_ancestor(
959 source_commit.raw_id, target_commit.raw_id, target_scm)
960 source_commit.raw_id, target_commit.raw_id, target_scm)
960
961
961 # recalculate target ref based on ancestor
962 # recalculate target ref based on ancestor
962 target_ref_type, target_ref_name, __ = _form['target_ref'].split(':')
963 target_ref_type, target_ref_name, __ = _form['target_ref'].split(':')
963 target_ref = ':'.join((target_ref_type, target_ref_name, ancestor))
964 target_ref = ':'.join((target_ref_type, target_ref_name, ancestor))
964
965
965 get_default_reviewers_data, validate_default_reviewers = \
966 get_default_reviewers_data, validate_default_reviewers = \
966 PullRequestModel().get_reviewer_functions()
967 PullRequestModel().get_reviewer_functions()
967
968
968 # recalculate reviewers logic, to make sure we can validate this
969 # recalculate reviewers logic, to make sure we can validate this
969 reviewer_rules = get_default_reviewers_data(
970 reviewer_rules = get_default_reviewers_data(
970 self._rhodecode_db_user, source_db_repo,
971 self._rhodecode_db_user, source_db_repo,
971 source_commit, target_db_repo, target_commit)
972 source_commit, target_db_repo, target_commit)
972
973
973 given_reviewers = _form['review_members']
974 given_reviewers = _form['review_members']
974 reviewers = validate_default_reviewers(
975 reviewers = validate_default_reviewers(
975 given_reviewers, reviewer_rules)
976 given_reviewers, reviewer_rules)
976
977
977 pullrequest_title = _form['pullrequest_title']
978 pullrequest_title = _form['pullrequest_title']
978 title_source_ref = source_ref.split(':', 2)[1]
979 title_source_ref = source_ref.split(':', 2)[1]
979 if not pullrequest_title:
980 if not pullrequest_title:
980 pullrequest_title = PullRequestModel().generate_pullrequest_title(
981 pullrequest_title = PullRequestModel().generate_pullrequest_title(
981 source=source_repo,
982 source=source_repo,
982 source_ref=title_source_ref,
983 source_ref=title_source_ref,
983 target=target_repo
984 target=target_repo
984 )
985 )
985
986
986 description = _form['pullrequest_desc']
987 description = _form['pullrequest_desc']
987 description_renderer = _form['description_renderer']
988 description_renderer = _form['description_renderer']
988
989
989 try:
990 try:
990 pull_request = PullRequestModel().create(
991 pull_request = PullRequestModel().create(
991 created_by=self._rhodecode_user.user_id,
992 created_by=self._rhodecode_user.user_id,
992 source_repo=source_repo,
993 source_repo=source_repo,
993 source_ref=source_ref,
994 source_ref=source_ref,
994 target_repo=target_repo,
995 target_repo=target_repo,
995 target_ref=target_ref,
996 target_ref=target_ref,
996 revisions=commit_ids,
997 revisions=commit_ids,
997 reviewers=reviewers,
998 reviewers=reviewers,
998 title=pullrequest_title,
999 title=pullrequest_title,
999 description=description,
1000 description=description,
1000 description_renderer=description_renderer,
1001 description_renderer=description_renderer,
1001 reviewer_data=reviewer_rules,
1002 reviewer_data=reviewer_rules,
1002 auth_user=self._rhodecode_user
1003 auth_user=self._rhodecode_user
1003 )
1004 )
1004 Session().commit()
1005 Session().commit()
1005
1006
1006 h.flash(_('Successfully opened new pull request'),
1007 h.flash(_('Successfully opened new pull request'),
1007 category='success')
1008 category='success')
1008 except Exception:
1009 except Exception:
1009 msg = _('Error occurred during creation of this pull request.')
1010 msg = _('Error occurred during creation of this pull request.')
1010 log.exception(msg)
1011 log.exception(msg)
1011 h.flash(msg, category='error')
1012 h.flash(msg, category='error')
1012
1013
1013 # copy the args back to redirect
1014 # copy the args back to redirect
1014 org_query = self.request.GET.mixed()
1015 org_query = self.request.GET.mixed()
1015 raise HTTPFound(
1016 raise HTTPFound(
1016 h.route_path('pullrequest_new', repo_name=self.db_repo_name,
1017 h.route_path('pullrequest_new', repo_name=self.db_repo_name,
1017 _query=org_query))
1018 _query=org_query))
1018
1019
1019 raise HTTPFound(
1020 raise HTTPFound(
1020 h.route_path('pullrequest_show', repo_name=target_repo,
1021 h.route_path('pullrequest_show', repo_name=target_repo,
1021 pull_request_id=pull_request.pull_request_id))
1022 pull_request_id=pull_request.pull_request_id))
1022
1023
1023 @LoginRequired()
1024 @LoginRequired()
1024 @NotAnonymous()
1025 @NotAnonymous()
1025 @HasRepoPermissionAnyDecorator(
1026 @HasRepoPermissionAnyDecorator(
1026 'repository.read', 'repository.write', 'repository.admin')
1027 'repository.read', 'repository.write', 'repository.admin')
1027 @CSRFRequired()
1028 @CSRFRequired()
1028 @view_config(
1029 @view_config(
1029 route_name='pullrequest_update', request_method='POST',
1030 route_name='pullrequest_update', request_method='POST',
1030 renderer='json_ext')
1031 renderer='json_ext')
1031 def pull_request_update(self):
1032 def pull_request_update(self):
1032 pull_request = PullRequest.get_or_404(
1033 pull_request = PullRequest.get_or_404(
1033 self.request.matchdict['pull_request_id'])
1034 self.request.matchdict['pull_request_id'])
1034 _ = self.request.translate
1035 _ = self.request.translate
1035
1036
1036 self.load_default_context()
1037 self.load_default_context()
1037
1038
1038 if pull_request.is_closed():
1039 if pull_request.is_closed():
1039 log.debug('update: forbidden because pull request is closed')
1040 log.debug('update: forbidden because pull request is closed')
1040 msg = _(u'Cannot update closed pull requests.')
1041 msg = _(u'Cannot update closed pull requests.')
1041 h.flash(msg, category='error')
1042 h.flash(msg, category='error')
1042 return True
1043 return True
1043
1044
1044 # only owner or admin can update it
1045 # only owner or admin can update it
1045 allowed_to_update = PullRequestModel().check_user_update(
1046 allowed_to_update = PullRequestModel().check_user_update(
1046 pull_request, self._rhodecode_user)
1047 pull_request, self._rhodecode_user)
1047 if allowed_to_update:
1048 if allowed_to_update:
1048 controls = peppercorn.parse(self.request.POST.items())
1049 controls = peppercorn.parse(self.request.POST.items())
1049
1050
1050 if 'review_members' in controls:
1051 if 'review_members' in controls:
1051 self._update_reviewers(
1052 self._update_reviewers(
1052 pull_request, controls['review_members'],
1053 pull_request, controls['review_members'],
1053 pull_request.reviewer_data)
1054 pull_request.reviewer_data)
1054 elif str2bool(self.request.POST.get('update_commits', 'false')):
1055 elif str2bool(self.request.POST.get('update_commits', 'false')):
1055 self._update_commits(pull_request)
1056 self._update_commits(pull_request)
1056 elif str2bool(self.request.POST.get('edit_pull_request', 'false')):
1057 elif str2bool(self.request.POST.get('edit_pull_request', 'false')):
1057 self._edit_pull_request(pull_request)
1058 self._edit_pull_request(pull_request)
1058 else:
1059 else:
1059 raise HTTPBadRequest()
1060 raise HTTPBadRequest()
1060 return True
1061 return True
1061 raise HTTPForbidden()
1062 raise HTTPForbidden()
1062
1063
1063 def _edit_pull_request(self, pull_request):
1064 def _edit_pull_request(self, pull_request):
1064 _ = self.request.translate
1065 _ = self.request.translate
1065
1066
1066 try:
1067 try:
1067 PullRequestModel().edit(
1068 PullRequestModel().edit(
1068 pull_request,
1069 pull_request,
1069 self.request.POST.get('title'),
1070 self.request.POST.get('title'),
1070 self.request.POST.get('description'),
1071 self.request.POST.get('description'),
1071 self.request.POST.get('description_renderer'),
1072 self.request.POST.get('description_renderer'),
1072 self._rhodecode_user)
1073 self._rhodecode_user)
1073 except ValueError:
1074 except ValueError:
1074 msg = _(u'Cannot update closed pull requests.')
1075 msg = _(u'Cannot update closed pull requests.')
1075 h.flash(msg, category='error')
1076 h.flash(msg, category='error')
1076 return
1077 return
1077 else:
1078 else:
1078 Session().commit()
1079 Session().commit()
1079
1080
1080 msg = _(u'Pull request title & description updated.')
1081 msg = _(u'Pull request title & description updated.')
1081 h.flash(msg, category='success')
1082 h.flash(msg, category='success')
1082 return
1083 return
1083
1084
1084 def _update_commits(self, pull_request):
1085 def _update_commits(self, pull_request):
1085 _ = self.request.translate
1086 _ = self.request.translate
1086 resp = PullRequestModel().update_commits(pull_request)
1087 resp = PullRequestModel().update_commits(pull_request)
1087
1088
1088 if resp.executed:
1089 if resp.executed:
1089
1090
1090 if resp.target_changed and resp.source_changed:
1091 if resp.target_changed and resp.source_changed:
1091 changed = 'target and source repositories'
1092 changed = 'target and source repositories'
1092 elif resp.target_changed and not resp.source_changed:
1093 elif resp.target_changed and not resp.source_changed:
1093 changed = 'target repository'
1094 changed = 'target repository'
1094 elif not resp.target_changed and resp.source_changed:
1095 elif not resp.target_changed and resp.source_changed:
1095 changed = 'source repository'
1096 changed = 'source repository'
1096 else:
1097 else:
1097 changed = 'nothing'
1098 changed = 'nothing'
1098
1099
1099 msg = _(
1100 msg = _(
1100 u'Pull request updated to "{source_commit_id}" with '
1101 u'Pull request updated to "{source_commit_id}" with '
1101 u'{count_added} added, {count_removed} removed commits. '
1102 u'{count_added} added, {count_removed} removed commits. '
1102 u'Source of changes: {change_source}')
1103 u'Source of changes: {change_source}')
1103 msg = msg.format(
1104 msg = msg.format(
1104 source_commit_id=pull_request.source_ref_parts.commit_id,
1105 source_commit_id=pull_request.source_ref_parts.commit_id,
1105 count_added=len(resp.changes.added),
1106 count_added=len(resp.changes.added),
1106 count_removed=len(resp.changes.removed),
1107 count_removed=len(resp.changes.removed),
1107 change_source=changed)
1108 change_source=changed)
1108 h.flash(msg, category='success')
1109 h.flash(msg, category='success')
1109
1110
1110 channel = '/repo${}$/pr/{}'.format(
1111 channel = '/repo${}$/pr/{}'.format(
1111 pull_request.target_repo.repo_name,
1112 pull_request.target_repo.repo_name,
1112 pull_request.pull_request_id)
1113 pull_request.pull_request_id)
1113 message = msg + (
1114 message = msg + (
1114 ' - <a onclick="window.location.reload()">'
1115 ' - <a onclick="window.location.reload()">'
1115 '<strong>{}</strong></a>'.format(_('Reload page')))
1116 '<strong>{}</strong></a>'.format(_('Reload page')))
1116 channelstream.post_message(
1117 channelstream.post_message(
1117 channel, message, self._rhodecode_user.username,
1118 channel, message, self._rhodecode_user.username,
1118 registry=self.request.registry)
1119 registry=self.request.registry)
1119 else:
1120 else:
1120 msg = PullRequestModel.UPDATE_STATUS_MESSAGES[resp.reason]
1121 msg = PullRequestModel.UPDATE_STATUS_MESSAGES[resp.reason]
1121 warning_reasons = [
1122 warning_reasons = [
1122 UpdateFailureReason.NO_CHANGE,
1123 UpdateFailureReason.NO_CHANGE,
1123 UpdateFailureReason.WRONG_REF_TYPE,
1124 UpdateFailureReason.WRONG_REF_TYPE,
1124 ]
1125 ]
1125 category = 'warning' if resp.reason in warning_reasons else 'error'
1126 category = 'warning' if resp.reason in warning_reasons else 'error'
1126 h.flash(msg, category=category)
1127 h.flash(msg, category=category)
1127
1128
1128 @LoginRequired()
1129 @LoginRequired()
1129 @NotAnonymous()
1130 @NotAnonymous()
1130 @HasRepoPermissionAnyDecorator(
1131 @HasRepoPermissionAnyDecorator(
1131 'repository.read', 'repository.write', 'repository.admin')
1132 'repository.read', 'repository.write', 'repository.admin')
1132 @CSRFRequired()
1133 @CSRFRequired()
1133 @view_config(
1134 @view_config(
1134 route_name='pullrequest_merge', request_method='POST',
1135 route_name='pullrequest_merge', request_method='POST',
1135 renderer='json_ext')
1136 renderer='json_ext')
1136 def pull_request_merge(self):
1137 def pull_request_merge(self):
1137 """
1138 """
1138 Merge will perform a server-side merge of the specified
1139 Merge will perform a server-side merge of the specified
1139 pull request, if the pull request is approved and mergeable.
1140 pull request, if the pull request is approved and mergeable.
1140 After successful merging, the pull request is automatically
1141 After successful merging, the pull request is automatically
1141 closed, with a relevant comment.
1142 closed, with a relevant comment.
1142 """
1143 """
1143 pull_request = PullRequest.get_or_404(
1144 pull_request = PullRequest.get_or_404(
1144 self.request.matchdict['pull_request_id'])
1145 self.request.matchdict['pull_request_id'])
1145
1146
1146 self.load_default_context()
1147 self.load_default_context()
1147 check = MergeCheck.validate(
1148 check = MergeCheck.validate(
1148 pull_request, auth_user=self._rhodecode_user,
1149 pull_request, auth_user=self._rhodecode_user,
1149 translator=self.request.translate)
1150 translator=self.request.translate)
1150 merge_possible = not check.failed
1151 merge_possible = not check.failed
1151
1152
1152 for err_type, error_msg in check.errors:
1153 for err_type, error_msg in check.errors:
1153 h.flash(error_msg, category=err_type)
1154 h.flash(error_msg, category=err_type)
1154
1155
1155 if merge_possible:
1156 if merge_possible:
1156 log.debug("Pre-conditions checked, trying to merge.")
1157 log.debug("Pre-conditions checked, trying to merge.")
1157 extras = vcs_operation_context(
1158 extras = vcs_operation_context(
1158 self.request.environ, repo_name=pull_request.target_repo.repo_name,
1159 self.request.environ, repo_name=pull_request.target_repo.repo_name,
1159 username=self._rhodecode_db_user.username, action='push',
1160 username=self._rhodecode_db_user.username, action='push',
1160 scm=pull_request.target_repo.repo_type)
1161 scm=pull_request.target_repo.repo_type)
1161 self._merge_pull_request(
1162 self._merge_pull_request(
1162 pull_request, self._rhodecode_db_user, extras)
1163 pull_request, self._rhodecode_db_user, extras)
1163 else:
1164 else:
1164 log.debug("Pre-conditions failed, NOT merging.")
1165 log.debug("Pre-conditions failed, NOT merging.")
1165
1166
1166 raise HTTPFound(
1167 raise HTTPFound(
1167 h.route_path('pullrequest_show',
1168 h.route_path('pullrequest_show',
1168 repo_name=pull_request.target_repo.repo_name,
1169 repo_name=pull_request.target_repo.repo_name,
1169 pull_request_id=pull_request.pull_request_id))
1170 pull_request_id=pull_request.pull_request_id))
1170
1171
1171 def _merge_pull_request(self, pull_request, user, extras):
1172 def _merge_pull_request(self, pull_request, user, extras):
1172 _ = self.request.translate
1173 _ = self.request.translate
1173 merge_resp = PullRequestModel().merge_repo(pull_request, user, extras=extras)
1174 merge_resp = PullRequestModel().merge_repo(pull_request, user, extras=extras)
1174
1175
1175 if merge_resp.executed:
1176 if merge_resp.executed:
1176 log.debug("The merge was successful, closing the pull request.")
1177 log.debug("The merge was successful, closing the pull request.")
1177 PullRequestModel().close_pull_request(
1178 PullRequestModel().close_pull_request(
1178 pull_request.pull_request_id, user)
1179 pull_request.pull_request_id, user)
1179 Session().commit()
1180 Session().commit()
1180 msg = _('Pull request was successfully merged and closed.')
1181 msg = _('Pull request was successfully merged and closed.')
1181 h.flash(msg, category='success')
1182 h.flash(msg, category='success')
1182 else:
1183 else:
1183 log.debug(
1184 log.debug(
1184 "The merge was not successful. Merge response: %s", merge_resp)
1185 "The merge was not successful. Merge response: %s", merge_resp)
1185 msg = merge_resp.merge_status_message
1186 msg = merge_resp.merge_status_message
1186 h.flash(msg, category='error')
1187 h.flash(msg, category='error')
1187
1188
1188 def _update_reviewers(self, pull_request, review_members, reviewer_rules):
1189 def _update_reviewers(self, pull_request, review_members, reviewer_rules):
1189 _ = self.request.translate
1190 _ = self.request.translate
1190 get_default_reviewers_data, validate_default_reviewers = \
1191 get_default_reviewers_data, validate_default_reviewers = \
1191 PullRequestModel().get_reviewer_functions()
1192 PullRequestModel().get_reviewer_functions()
1192
1193
1193 try:
1194 try:
1194 reviewers = validate_default_reviewers(review_members, reviewer_rules)
1195 reviewers = validate_default_reviewers(review_members, reviewer_rules)
1195 except ValueError as e:
1196 except ValueError as e:
1196 log.error('Reviewers Validation: {}'.format(e))
1197 log.error('Reviewers Validation: {}'.format(e))
1197 h.flash(e, category='error')
1198 h.flash(e, category='error')
1198 return
1199 return
1199
1200
1200 PullRequestModel().update_reviewers(
1201 PullRequestModel().update_reviewers(
1201 pull_request, reviewers, self._rhodecode_user)
1202 pull_request, reviewers, self._rhodecode_user)
1202 h.flash(_('Pull request reviewers updated.'), category='success')
1203 h.flash(_('Pull request reviewers updated.'), category='success')
1203 Session().commit()
1204 Session().commit()
1204
1205
1205 @LoginRequired()
1206 @LoginRequired()
1206 @NotAnonymous()
1207 @NotAnonymous()
1207 @HasRepoPermissionAnyDecorator(
1208 @HasRepoPermissionAnyDecorator(
1208 'repository.read', 'repository.write', 'repository.admin')
1209 'repository.read', 'repository.write', 'repository.admin')
1209 @CSRFRequired()
1210 @CSRFRequired()
1210 @view_config(
1211 @view_config(
1211 route_name='pullrequest_delete', request_method='POST',
1212 route_name='pullrequest_delete', request_method='POST',
1212 renderer='json_ext')
1213 renderer='json_ext')
1213 def pull_request_delete(self):
1214 def pull_request_delete(self):
1214 _ = self.request.translate
1215 _ = self.request.translate
1215
1216
1216 pull_request = PullRequest.get_or_404(
1217 pull_request = PullRequest.get_or_404(
1217 self.request.matchdict['pull_request_id'])
1218 self.request.matchdict['pull_request_id'])
1218 self.load_default_context()
1219 self.load_default_context()
1219
1220
1220 pr_closed = pull_request.is_closed()
1221 pr_closed = pull_request.is_closed()
1221 allowed_to_delete = PullRequestModel().check_user_delete(
1222 allowed_to_delete = PullRequestModel().check_user_delete(
1222 pull_request, self._rhodecode_user) and not pr_closed
1223 pull_request, self._rhodecode_user) and not pr_closed
1223
1224
1224 # only owner can delete it !
1225 # only owner can delete it !
1225 if allowed_to_delete:
1226 if allowed_to_delete:
1226 PullRequestModel().delete(pull_request, self._rhodecode_user)
1227 PullRequestModel().delete(pull_request, self._rhodecode_user)
1227 Session().commit()
1228 Session().commit()
1228 h.flash(_('Successfully deleted pull request'),
1229 h.flash(_('Successfully deleted pull request'),
1229 category='success')
1230 category='success')
1230 raise HTTPFound(h.route_path('pullrequest_show_all',
1231 raise HTTPFound(h.route_path('pullrequest_show_all',
1231 repo_name=self.db_repo_name))
1232 repo_name=self.db_repo_name))
1232
1233
1233 log.warning('user %s tried to delete pull request without access',
1234 log.warning('user %s tried to delete pull request without access',
1234 self._rhodecode_user)
1235 self._rhodecode_user)
1235 raise HTTPNotFound()
1236 raise HTTPNotFound()
1236
1237
1237 @LoginRequired()
1238 @LoginRequired()
1238 @NotAnonymous()
1239 @NotAnonymous()
1239 @HasRepoPermissionAnyDecorator(
1240 @HasRepoPermissionAnyDecorator(
1240 'repository.read', 'repository.write', 'repository.admin')
1241 'repository.read', 'repository.write', 'repository.admin')
1241 @CSRFRequired()
1242 @CSRFRequired()
1242 @view_config(
1243 @view_config(
1243 route_name='pullrequest_comment_create', request_method='POST',
1244 route_name='pullrequest_comment_create', request_method='POST',
1244 renderer='json_ext')
1245 renderer='json_ext')
1245 def pull_request_comment_create(self):
1246 def pull_request_comment_create(self):
1246 _ = self.request.translate
1247 _ = self.request.translate
1247
1248
1248 pull_request = PullRequest.get_or_404(
1249 pull_request = PullRequest.get_or_404(
1249 self.request.matchdict['pull_request_id'])
1250 self.request.matchdict['pull_request_id'])
1250 pull_request_id = pull_request.pull_request_id
1251 pull_request_id = pull_request.pull_request_id
1251
1252
1252 if pull_request.is_closed():
1253 if pull_request.is_closed():
1253 log.debug('comment: forbidden because pull request is closed')
1254 log.debug('comment: forbidden because pull request is closed')
1254 raise HTTPForbidden()
1255 raise HTTPForbidden()
1255
1256
1256 allowed_to_comment = PullRequestModel().check_user_comment(
1257 allowed_to_comment = PullRequestModel().check_user_comment(
1257 pull_request, self._rhodecode_user)
1258 pull_request, self._rhodecode_user)
1258 if not allowed_to_comment:
1259 if not allowed_to_comment:
1259 log.debug(
1260 log.debug(
1260 'comment: forbidden because pull request is from forbidden repo')
1261 'comment: forbidden because pull request is from forbidden repo')
1261 raise HTTPForbidden()
1262 raise HTTPForbidden()
1262
1263
1263 c = self.load_default_context()
1264 c = self.load_default_context()
1264
1265
1265 status = self.request.POST.get('changeset_status', None)
1266 status = self.request.POST.get('changeset_status', None)
1266 text = self.request.POST.get('text')
1267 text = self.request.POST.get('text')
1267 comment_type = self.request.POST.get('comment_type')
1268 comment_type = self.request.POST.get('comment_type')
1268 resolves_comment_id = self.request.POST.get('resolves_comment_id', None)
1269 resolves_comment_id = self.request.POST.get('resolves_comment_id', None)
1269 close_pull_request = self.request.POST.get('close_pull_request')
1270 close_pull_request = self.request.POST.get('close_pull_request')
1270
1271
1271 # the logic here should work like following, if we submit close
1272 # the logic here should work like following, if we submit close
1272 # pr comment, use `close_pull_request_with_comment` function
1273 # pr comment, use `close_pull_request_with_comment` function
1273 # else handle regular comment logic
1274 # else handle regular comment logic
1274
1275
1275 if close_pull_request:
1276 if close_pull_request:
1276 # only owner or admin or person with write permissions
1277 # only owner or admin or person with write permissions
1277 allowed_to_close = PullRequestModel().check_user_update(
1278 allowed_to_close = PullRequestModel().check_user_update(
1278 pull_request, self._rhodecode_user)
1279 pull_request, self._rhodecode_user)
1279 if not allowed_to_close:
1280 if not allowed_to_close:
1280 log.debug('comment: forbidden because not allowed to close '
1281 log.debug('comment: forbidden because not allowed to close '
1281 'pull request %s', pull_request_id)
1282 'pull request %s', pull_request_id)
1282 raise HTTPForbidden()
1283 raise HTTPForbidden()
1283 comment, status = PullRequestModel().close_pull_request_with_comment(
1284 comment, status = PullRequestModel().close_pull_request_with_comment(
1284 pull_request, self._rhodecode_user, self.db_repo, message=text,
1285 pull_request, self._rhodecode_user, self.db_repo, message=text,
1285 auth_user=self._rhodecode_user)
1286 auth_user=self._rhodecode_user)
1286 Session().flush()
1287 Session().flush()
1287 events.trigger(
1288 events.trigger(
1288 events.PullRequestCommentEvent(pull_request, comment))
1289 events.PullRequestCommentEvent(pull_request, comment))
1289
1290
1290 else:
1291 else:
1291 # regular comment case, could be inline, or one with status.
1292 # regular comment case, could be inline, or one with status.
1292 # for that one we check also permissions
1293 # for that one we check also permissions
1293
1294
1294 allowed_to_change_status = PullRequestModel().check_user_change_status(
1295 allowed_to_change_status = PullRequestModel().check_user_change_status(
1295 pull_request, self._rhodecode_user)
1296 pull_request, self._rhodecode_user)
1296
1297
1297 if status and allowed_to_change_status:
1298 if status and allowed_to_change_status:
1298 message = (_('Status change %(transition_icon)s %(status)s')
1299 message = (_('Status change %(transition_icon)s %(status)s')
1299 % {'transition_icon': '>',
1300 % {'transition_icon': '>',
1300 'status': ChangesetStatus.get_status_lbl(status)})
1301 'status': ChangesetStatus.get_status_lbl(status)})
1301 text = text or message
1302 text = text or message
1302
1303
1303 comment = CommentsModel().create(
1304 comment = CommentsModel().create(
1304 text=text,
1305 text=text,
1305 repo=self.db_repo.repo_id,
1306 repo=self.db_repo.repo_id,
1306 user=self._rhodecode_user.user_id,
1307 user=self._rhodecode_user.user_id,
1307 pull_request=pull_request,
1308 pull_request=pull_request,
1308 f_path=self.request.POST.get('f_path'),
1309 f_path=self.request.POST.get('f_path'),
1309 line_no=self.request.POST.get('line'),
1310 line_no=self.request.POST.get('line'),
1310 status_change=(ChangesetStatus.get_status_lbl(status)
1311 status_change=(ChangesetStatus.get_status_lbl(status)
1311 if status and allowed_to_change_status else None),
1312 if status and allowed_to_change_status else None),
1312 status_change_type=(status
1313 status_change_type=(status
1313 if status and allowed_to_change_status else None),
1314 if status and allowed_to_change_status else None),
1314 comment_type=comment_type,
1315 comment_type=comment_type,
1315 resolves_comment_id=resolves_comment_id,
1316 resolves_comment_id=resolves_comment_id,
1316 auth_user=self._rhodecode_user
1317 auth_user=self._rhodecode_user
1317 )
1318 )
1318
1319
1319 if allowed_to_change_status:
1320 if allowed_to_change_status:
1320 # calculate old status before we change it
1321 # calculate old status before we change it
1321 old_calculated_status = pull_request.calculated_review_status()
1322 old_calculated_status = pull_request.calculated_review_status()
1322
1323
1323 # get status if set !
1324 # get status if set !
1324 if status:
1325 if status:
1325 ChangesetStatusModel().set_status(
1326 ChangesetStatusModel().set_status(
1326 self.db_repo.repo_id,
1327 self.db_repo.repo_id,
1327 status,
1328 status,
1328 self._rhodecode_user.user_id,
1329 self._rhodecode_user.user_id,
1329 comment,
1330 comment,
1330 pull_request=pull_request
1331 pull_request=pull_request
1331 )
1332 )
1332
1333
1333 Session().flush()
1334 Session().flush()
1334 # this is somehow required to get access to some relationship
1335 # this is somehow required to get access to some relationship
1335 # loaded on comment
1336 # loaded on comment
1336 Session().refresh(comment)
1337 Session().refresh(comment)
1337
1338
1338 events.trigger(
1339 events.trigger(
1339 events.PullRequestCommentEvent(pull_request, comment))
1340 events.PullRequestCommentEvent(pull_request, comment))
1340
1341
1341 # we now calculate the status of pull request, and based on that
1342 # we now calculate the status of pull request, and based on that
1342 # calculation we set the commits status
1343 # calculation we set the commits status
1343 calculated_status = pull_request.calculated_review_status()
1344 calculated_status = pull_request.calculated_review_status()
1344 if old_calculated_status != calculated_status:
1345 if old_calculated_status != calculated_status:
1345 PullRequestModel()._trigger_pull_request_hook(
1346 PullRequestModel()._trigger_pull_request_hook(
1346 pull_request, self._rhodecode_user, 'review_status_change')
1347 pull_request, self._rhodecode_user, 'review_status_change')
1347
1348
1348 Session().commit()
1349 Session().commit()
1349
1350
1350 data = {
1351 data = {
1351 'target_id': h.safeid(h.safe_unicode(
1352 'target_id': h.safeid(h.safe_unicode(
1352 self.request.POST.get('f_path'))),
1353 self.request.POST.get('f_path'))),
1353 }
1354 }
1354 if comment:
1355 if comment:
1355 c.co = comment
1356 c.co = comment
1356 rendered_comment = render(
1357 rendered_comment = render(
1357 'rhodecode:templates/changeset/changeset_comment_block.mako',
1358 'rhodecode:templates/changeset/changeset_comment_block.mako',
1358 self._get_template_context(c), self.request)
1359 self._get_template_context(c), self.request)
1359
1360
1360 data.update(comment.get_dict())
1361 data.update(comment.get_dict())
1361 data.update({'rendered_text': rendered_comment})
1362 data.update({'rendered_text': rendered_comment})
1362
1363
1363 return data
1364 return data
1364
1365
1365 @LoginRequired()
1366 @LoginRequired()
1366 @NotAnonymous()
1367 @NotAnonymous()
1367 @HasRepoPermissionAnyDecorator(
1368 @HasRepoPermissionAnyDecorator(
1368 'repository.read', 'repository.write', 'repository.admin')
1369 'repository.read', 'repository.write', 'repository.admin')
1369 @CSRFRequired()
1370 @CSRFRequired()
1370 @view_config(
1371 @view_config(
1371 route_name='pullrequest_comment_delete', request_method='POST',
1372 route_name='pullrequest_comment_delete', request_method='POST',
1372 renderer='json_ext')
1373 renderer='json_ext')
1373 def pull_request_comment_delete(self):
1374 def pull_request_comment_delete(self):
1374 pull_request = PullRequest.get_or_404(
1375 pull_request = PullRequest.get_or_404(
1375 self.request.matchdict['pull_request_id'])
1376 self.request.matchdict['pull_request_id'])
1376
1377
1377 comment = ChangesetComment.get_or_404(
1378 comment = ChangesetComment.get_or_404(
1378 self.request.matchdict['comment_id'])
1379 self.request.matchdict['comment_id'])
1379 comment_id = comment.comment_id
1380 comment_id = comment.comment_id
1380
1381
1381 if pull_request.is_closed():
1382 if pull_request.is_closed():
1382 log.debug('comment: forbidden because pull request is closed')
1383 log.debug('comment: forbidden because pull request is closed')
1383 raise HTTPForbidden()
1384 raise HTTPForbidden()
1384
1385
1385 if not comment:
1386 if not comment:
1386 log.debug('Comment with id:%s not found, skipping', comment_id)
1387 log.debug('Comment with id:%s not found, skipping', comment_id)
1387 # comment already deleted in another call probably
1388 # comment already deleted in another call probably
1388 return True
1389 return True
1389
1390
1390 if comment.pull_request.is_closed():
1391 if comment.pull_request.is_closed():
1391 # don't allow deleting comments on closed pull request
1392 # don't allow deleting comments on closed pull request
1392 raise HTTPForbidden()
1393 raise HTTPForbidden()
1393
1394
1394 is_repo_admin = h.HasRepoPermissionAny('repository.admin')(self.db_repo_name)
1395 is_repo_admin = h.HasRepoPermissionAny('repository.admin')(self.db_repo_name)
1395 super_admin = h.HasPermissionAny('hg.admin')()
1396 super_admin = h.HasPermissionAny('hg.admin')()
1396 comment_owner = comment.author.user_id == self._rhodecode_user.user_id
1397 comment_owner = comment.author.user_id == self._rhodecode_user.user_id
1397 is_repo_comment = comment.repo.repo_name == self.db_repo_name
1398 is_repo_comment = comment.repo.repo_name == self.db_repo_name
1398 comment_repo_admin = is_repo_admin and is_repo_comment
1399 comment_repo_admin = is_repo_admin and is_repo_comment
1399
1400
1400 if super_admin or comment_owner or comment_repo_admin:
1401 if super_admin or comment_owner or comment_repo_admin:
1401 old_calculated_status = comment.pull_request.calculated_review_status()
1402 old_calculated_status = comment.pull_request.calculated_review_status()
1402 CommentsModel().delete(comment=comment, auth_user=self._rhodecode_user)
1403 CommentsModel().delete(comment=comment, auth_user=self._rhodecode_user)
1403 Session().commit()
1404 Session().commit()
1404 calculated_status = comment.pull_request.calculated_review_status()
1405 calculated_status = comment.pull_request.calculated_review_status()
1405 if old_calculated_status != calculated_status:
1406 if old_calculated_status != calculated_status:
1406 PullRequestModel()._trigger_pull_request_hook(
1407 PullRequestModel()._trigger_pull_request_hook(
1407 comment.pull_request, self._rhodecode_user, 'review_status_change')
1408 comment.pull_request, self._rhodecode_user, 'review_status_change')
1408 return True
1409 return True
1409 else:
1410 else:
1410 log.warning('No permissions for user %s to delete comment_id: %s',
1411 log.warning('No permissions for user %s to delete comment_id: %s',
1411 self._rhodecode_db_user, comment_id)
1412 self._rhodecode_db_user, comment_id)
1412 raise HTTPNotFound()
1413 raise HTTPNotFound()
@@ -1,696 +1,696 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="root.mako"/>
2 <%inherit file="root.mako"/>
3
3
4 <%include file="/ejs_templates/templates.html"/>
4 <%include file="/ejs_templates/templates.html"/>
5
5
6 <div class="outerwrapper">
6 <div class="outerwrapper">
7 <!-- HEADER -->
7 <!-- HEADER -->
8 <div class="header">
8 <div class="header">
9 <div id="header-inner" class="wrapper">
9 <div id="header-inner" class="wrapper">
10 <div id="logo">
10 <div id="logo">
11 <div class="logo-wrapper">
11 <div class="logo-wrapper">
12 <a href="${h.route_path('home')}"><img src="${h.asset('images/rhodecode-logo-white-216x60.png')}" alt="RhodeCode"/></a>
12 <a href="${h.route_path('home')}"><img src="${h.asset('images/rhodecode-logo-white-216x60.png')}" alt="RhodeCode"/></a>
13 </div>
13 </div>
14 %if c.rhodecode_name:
14 %if c.rhodecode_name:
15 <div class="branding">- ${h.branding(c.rhodecode_name)}</div>
15 <div class="branding">- ${h.branding(c.rhodecode_name)}</div>
16 %endif
16 %endif
17 </div>
17 </div>
18 <!-- MENU BAR NAV -->
18 <!-- MENU BAR NAV -->
19 ${self.menu_bar_nav()}
19 ${self.menu_bar_nav()}
20 <!-- END MENU BAR NAV -->
20 <!-- END MENU BAR NAV -->
21 </div>
21 </div>
22 </div>
22 </div>
23 ${self.menu_bar_subnav()}
23 ${self.menu_bar_subnav()}
24 <!-- END HEADER -->
24 <!-- END HEADER -->
25
25
26 <!-- CONTENT -->
26 <!-- CONTENT -->
27 <div id="content" class="wrapper">
27 <div id="content" class="wrapper">
28
28
29 <rhodecode-toast id="notifications"></rhodecode-toast>
29 <rhodecode-toast id="notifications"></rhodecode-toast>
30
30
31 <div class="main">
31 <div class="main">
32 ${next.main()}
32 ${next.main()}
33 </div>
33 </div>
34 </div>
34 </div>
35 <!-- END CONTENT -->
35 <!-- END CONTENT -->
36
36
37 </div>
37 </div>
38 <!-- FOOTER -->
38 <!-- FOOTER -->
39 <div id="footer">
39 <div id="footer">
40 <div id="footer-inner" class="title wrapper">
40 <div id="footer-inner" class="title wrapper">
41 <div>
41 <div>
42 <p class="footer-link-right">
42 <p class="footer-link-right">
43 % if c.visual.show_version:
43 % if c.visual.show_version:
44 RhodeCode Enterprise ${c.rhodecode_version} ${c.rhodecode_edition}
44 RhodeCode Enterprise ${c.rhodecode_version} ${c.rhodecode_edition}
45 % endif
45 % endif
46 &copy; 2010-${h.datetime.today().year}, <a href="${h.route_url('rhodecode_official')}" target="_blank">RhodeCode GmbH</a>. All rights reserved.
46 &copy; 2010-${h.datetime.today().year}, <a href="${h.route_url('rhodecode_official')}" target="_blank">RhodeCode GmbH</a>. All rights reserved.
47 % if c.visual.rhodecode_support_url:
47 % if c.visual.rhodecode_support_url:
48 <a href="${c.visual.rhodecode_support_url}" target="_blank">${_('Support')}</a>
48 <a href="${c.visual.rhodecode_support_url}" target="_blank">${_('Support')}</a>
49 % endif
49 % endif
50 </p>
50 </p>
51 <% sid = 'block' if request.GET.get('showrcid') else 'none' %>
51 <% sid = 'block' if request.GET.get('showrcid') else 'none' %>
52 <p class="server-instance" style="display:${sid}">
52 <p class="server-instance" style="display:${sid}">
53 ## display hidden instance ID if specially defined
53 ## display hidden instance ID if specially defined
54 % if c.rhodecode_instanceid:
54 % if c.rhodecode_instanceid:
55 ${_('RhodeCode instance id: %s') % c.rhodecode_instanceid}
55 ${_('RhodeCode instance id: %s') % c.rhodecode_instanceid}
56 % endif
56 % endif
57 </p>
57 </p>
58 </div>
58 </div>
59 </div>
59 </div>
60 </div>
60 </div>
61
61
62 <!-- END FOOTER -->
62 <!-- END FOOTER -->
63
63
64 ### MAKO DEFS ###
64 ### MAKO DEFS ###
65
65
66 <%def name="menu_bar_subnav()">
66 <%def name="menu_bar_subnav()">
67 </%def>
67 </%def>
68
68
69 <%def name="breadcrumbs(class_='breadcrumbs')">
69 <%def name="breadcrumbs(class_='breadcrumbs')">
70 <div class="${class_}">
70 <div class="${class_}">
71 ${self.breadcrumbs_links()}
71 ${self.breadcrumbs_links()}
72 </div>
72 </div>
73 </%def>
73 </%def>
74
74
75 <%def name="admin_menu()">
75 <%def name="admin_menu()">
76 <ul class="admin_menu submenu">
76 <ul class="admin_menu submenu">
77 <li><a href="${h.route_path('admin_audit_logs')}">${_('Admin audit logs')}</a></li>
77 <li><a href="${h.route_path('admin_audit_logs')}">${_('Admin audit logs')}</a></li>
78 <li><a href="${h.route_path('repos')}">${_('Repositories')}</a></li>
78 <li><a href="${h.route_path('repos')}">${_('Repositories')}</a></li>
79 <li><a href="${h.route_path('repo_groups')}">${_('Repository groups')}</a></li>
79 <li><a href="${h.route_path('repo_groups')}">${_('Repository groups')}</a></li>
80 <li><a href="${h.route_path('users')}">${_('Users')}</a></li>
80 <li><a href="${h.route_path('users')}">${_('Users')}</a></li>
81 <li><a href="${h.route_path('user_groups')}">${_('User groups')}</a></li>
81 <li><a href="${h.route_path('user_groups')}">${_('User groups')}</a></li>
82 <li><a href="${h.route_path('admin_permissions_application')}">${_('Permissions')}</a></li>
82 <li><a href="${h.route_path('admin_permissions_application')}">${_('Permissions')}</a></li>
83 <li><a href="${h.route_path('auth_home', traverse='')}">${_('Authentication')}</a></li>
83 <li><a href="${h.route_path('auth_home', traverse='')}">${_('Authentication')}</a></li>
84 <li><a href="${h.route_path('global_integrations_home')}">${_('Integrations')}</a></li>
84 <li><a href="${h.route_path('global_integrations_home')}">${_('Integrations')}</a></li>
85 <li><a href="${h.route_path('admin_defaults_repositories')}">${_('Defaults')}</a></li>
85 <li><a href="${h.route_path('admin_defaults_repositories')}">${_('Defaults')}</a></li>
86 <li class="last"><a href="${h.route_path('admin_settings')}">${_('Settings')}</a></li>
86 <li class="last"><a href="${h.route_path('admin_settings')}">${_('Settings')}</a></li>
87 </ul>
87 </ul>
88 </%def>
88 </%def>
89
89
90
90
91 <%def name="dt_info_panel(elements)">
91 <%def name="dt_info_panel(elements)">
92 <dl class="dl-horizontal">
92 <dl class="dl-horizontal">
93 %for dt, dd, title, show_items in elements:
93 %for dt, dd, title, show_items in elements:
94 <dt>${dt}:</dt>
94 <dt>${dt}:</dt>
95 <dd title="${h.tooltip(title)}">
95 <dd title="${h.tooltip(title)}">
96 %if callable(dd):
96 %if callable(dd):
97 ## allow lazy evaluation of elements
97 ## allow lazy evaluation of elements
98 ${dd()}
98 ${dd()}
99 %else:
99 %else:
100 ${dd}
100 ${dd}
101 %endif
101 %endif
102 %if show_items:
102 %if show_items:
103 <span class="btn-collapse" data-toggle="item-${h.md5_safe(dt)[:6]}-details">${_('Show More')} </span>
103 <span class="btn-collapse" data-toggle="item-${h.md5_safe(dt)[:6]}-details">${_('Show More')} </span>
104 %endif
104 %endif
105 </dd>
105 </dd>
106
106
107 %if show_items:
107 %if show_items:
108 <div class="collapsable-content" data-toggle="item-${h.md5_safe(dt)[:6]}-details" style="display: none">
108 <div class="collapsable-content" data-toggle="item-${h.md5_safe(dt)[:6]}-details" style="display: none">
109 %for item in show_items:
109 %for item in show_items:
110 <dt></dt>
110 <dt></dt>
111 <dd>${item}</dd>
111 <dd>${item}</dd>
112 %endfor
112 %endfor
113 </div>
113 </div>
114 %endif
114 %endif
115
115
116 %endfor
116 %endfor
117 </dl>
117 </dl>
118 </%def>
118 </%def>
119
119
120
120
121 <%def name="gravatar(email, size=16)">
121 <%def name="gravatar(email, size=16)">
122 <%
122 <%
123 if (size > 16):
123 if (size > 16):
124 gravatar_class = 'gravatar gravatar-large'
124 gravatar_class = 'gravatar gravatar-large'
125 else:
125 else:
126 gravatar_class = 'gravatar'
126 gravatar_class = 'gravatar'
127 %>
127 %>
128 <%doc>
128 <%doc>
129 TODO: johbo: For now we serve double size images to make it smooth
129 TODO: johbo: For now we serve double size images to make it smooth
130 for retina. This is how it worked until now. Should be replaced
130 for retina. This is how it worked until now. Should be replaced
131 with a better solution at some point.
131 with a better solution at some point.
132 </%doc>
132 </%doc>
133 <img class="${gravatar_class}" src="${h.gravatar_url(email, size * 2)}" height="${size}" width="${size}">
133 <img class="${gravatar_class}" src="${h.gravatar_url(email, size * 2)}" height="${size}" width="${size}">
134 </%def>
134 </%def>
135
135
136
136
137 <%def name="gravatar_with_user(contact, size=16, show_disabled=False)">
137 <%def name="gravatar_with_user(contact, size=16, show_disabled=False)">
138 <% email = h.email_or_none(contact) %>
138 <% email = h.email_or_none(contact) %>
139 <div class="rc-user tooltip" title="${h.tooltip(h.author_string(email))}">
139 <div class="rc-user tooltip" title="${h.tooltip(h.author_string(email))}">
140 ${self.gravatar(email, size)}
140 ${self.gravatar(email, size)}
141 <span class="${'user user-disabled' if show_disabled else 'user'}"> ${h.link_to_user(contact)}</span>
141 <span class="${'user user-disabled' if show_disabled else 'user'}"> ${h.link_to_user(contact)}</span>
142 </div>
142 </div>
143 </%def>
143 </%def>
144
144
145
145
146 ## admin menu used for people that have some admin resources
146 ## admin menu used for people that have some admin resources
147 <%def name="admin_menu_simple(repositories=None, repository_groups=None, user_groups=None)">
147 <%def name="admin_menu_simple(repositories=None, repository_groups=None, user_groups=None)">
148 <ul class="submenu">
148 <ul class="submenu">
149 %if repositories:
149 %if repositories:
150 <li class="local-admin-repos"><a href="${h.route_path('repos')}">${_('Repositories')}</a></li>
150 <li class="local-admin-repos"><a href="${h.route_path('repos')}">${_('Repositories')}</a></li>
151 %endif
151 %endif
152 %if repository_groups:
152 %if repository_groups:
153 <li class="local-admin-repo-groups"><a href="${h.route_path('repo_groups')}">${_('Repository groups')}</a></li>
153 <li class="local-admin-repo-groups"><a href="${h.route_path('repo_groups')}">${_('Repository groups')}</a></li>
154 %endif
154 %endif
155 %if user_groups:
155 %if user_groups:
156 <li class="local-admin-user-groups"><a href="${h.route_path('user_groups')}">${_('User groups')}</a></li>
156 <li class="local-admin-user-groups"><a href="${h.route_path('user_groups')}">${_('User groups')}</a></li>
157 %endif
157 %endif
158 </ul>
158 </ul>
159 </%def>
159 </%def>
160
160
161 <%def name="repo_page_title(repo_instance)">
161 <%def name="repo_page_title(repo_instance)">
162 <div class="title-content">
162 <div class="title-content">
163 <div class="title-main">
163 <div class="title-main">
164 ## SVN/HG/GIT icons
164 ## SVN/HG/GIT icons
165 %if h.is_hg(repo_instance):
165 %if h.is_hg(repo_instance):
166 <i class="icon-hg"></i>
166 <i class="icon-hg"></i>
167 %endif
167 %endif
168 %if h.is_git(repo_instance):
168 %if h.is_git(repo_instance):
169 <i class="icon-git"></i>
169 <i class="icon-git"></i>
170 %endif
170 %endif
171 %if h.is_svn(repo_instance):
171 %if h.is_svn(repo_instance):
172 <i class="icon-svn"></i>
172 <i class="icon-svn"></i>
173 %endif
173 %endif
174
174
175 ## public/private
175 ## public/private
176 %if repo_instance.private:
176 %if repo_instance.private:
177 <i class="icon-repo-private"></i>
177 <i class="icon-repo-private"></i>
178 %else:
178 %else:
179 <i class="icon-repo-public"></i>
179 <i class="icon-repo-public"></i>
180 %endif
180 %endif
181
181
182 ## repo name with group name
182 ## repo name with group name
183 ${h.breadcrumb_repo_link(c.rhodecode_db_repo)}
183 ${h.breadcrumb_repo_link(c.rhodecode_db_repo)}
184
184
185 </div>
185 </div>
186
186
187 ## FORKED
187 ## FORKED
188 %if repo_instance.fork:
188 %if repo_instance.fork:
189 <p>
189 <p>
190 <i class="icon-code-fork"></i> ${_('Fork of')}
190 <i class="icon-code-fork"></i> ${_('Fork of')}
191 <a href="${h.route_path('repo_summary',repo_name=repo_instance.fork.repo_name)}">${repo_instance.fork.repo_name}</a>
191 ${h.link_to_if(c.has_origin_repo_read_perm,repo_instance.fork.repo_name, h.route_path('repo_summary', repo_name=repo_instance.fork.repo_name))}
192 </p>
192 </p>
193 %endif
193 %endif
194
194
195 ## IMPORTED FROM REMOTE
195 ## IMPORTED FROM REMOTE
196 %if repo_instance.clone_uri:
196 %if repo_instance.clone_uri:
197 <p>
197 <p>
198 <i class="icon-code-fork"></i> ${_('Clone from')}
198 <i class="icon-code-fork"></i> ${_('Clone from')}
199 <a href="${h.safe_str(h.hide_credentials(repo_instance.clone_uri))}">${h.hide_credentials(repo_instance.clone_uri)}</a>
199 <a href="${h.safe_str(h.hide_credentials(repo_instance.clone_uri))}">${h.hide_credentials(repo_instance.clone_uri)}</a>
200 </p>
200 </p>
201 %endif
201 %endif
202
202
203 ## LOCKING STATUS
203 ## LOCKING STATUS
204 %if repo_instance.locked[0]:
204 %if repo_instance.locked[0]:
205 <p class="locking_locked">
205 <p class="locking_locked">
206 <i class="icon-repo-lock"></i>
206 <i class="icon-repo-lock"></i>
207 ${_('Repository locked by %(user)s') % {'user': h.person_by_id(repo_instance.locked[0])}}
207 ${_('Repository locked by %(user)s') % {'user': h.person_by_id(repo_instance.locked[0])}}
208 </p>
208 </p>
209 %elif repo_instance.enable_locking:
209 %elif repo_instance.enable_locking:
210 <p class="locking_unlocked">
210 <p class="locking_unlocked">
211 <i class="icon-repo-unlock"></i>
211 <i class="icon-repo-unlock"></i>
212 ${_('Repository not locked. Pull repository to lock it.')}
212 ${_('Repository not locked. Pull repository to lock it.')}
213 </p>
213 </p>
214 %endif
214 %endif
215
215
216 </div>
216 </div>
217 </%def>
217 </%def>
218
218
219 <%def name="repo_menu(active=None)">
219 <%def name="repo_menu(active=None)">
220 <%
220 <%
221 def is_active(selected):
221 def is_active(selected):
222 if selected == active:
222 if selected == active:
223 return "active"
223 return "active"
224 %>
224 %>
225
225
226 <!--- CONTEXT BAR -->
226 <!--- CONTEXT BAR -->
227 <div id="context-bar">
227 <div id="context-bar">
228 <div class="wrapper">
228 <div class="wrapper">
229 <ul id="context-pages" class="navigation horizontal-list">
229 <ul id="context-pages" class="navigation horizontal-list">
230 <li class="${is_active('summary')}"><a class="menulink" href="${h.route_path('repo_summary', repo_name=c.repo_name)}"><div class="menulabel">${_('Summary')}</div></a></li>
230 <li class="${is_active('summary')}"><a class="menulink" href="${h.route_path('repo_summary', repo_name=c.repo_name)}"><div class="menulabel">${_('Summary')}</div></a></li>
231 <li class="${is_active('changelog')}"><a class="menulink" href="${h.route_path('repo_changelog', repo_name=c.repo_name)}"><div class="menulabel">${_('Changelog')}</div></a></li>
231 <li class="${is_active('changelog')}"><a class="menulink" href="${h.route_path('repo_changelog', repo_name=c.repo_name)}"><div class="menulabel">${_('Changelog')}</div></a></li>
232 <li class="${is_active('files')}"><a class="menulink" href="${h.route_path('repo_files', repo_name=c.repo_name, commit_id=c.rhodecode_db_repo.landing_rev[1], f_path='')}"><div class="menulabel">${_('Files')}</div></a></li>
232 <li class="${is_active('files')}"><a class="menulink" href="${h.route_path('repo_files', repo_name=c.repo_name, commit_id=c.rhodecode_db_repo.landing_rev[1], f_path='')}"><div class="menulabel">${_('Files')}</div></a></li>
233 <li class="${is_active('compare')}"><a class="menulink" href="${h.route_path('repo_compare_select',repo_name=c.repo_name)}"><div class="menulabel">${_('Compare')}</div></a></li>
233 <li class="${is_active('compare')}"><a class="menulink" href="${h.route_path('repo_compare_select',repo_name=c.repo_name)}"><div class="menulabel">${_('Compare')}</div></a></li>
234 ## TODO: anderson: ideally it would have a function on the scm_instance "enable_pullrequest() and enable_fork()"
234 ## TODO: anderson: ideally it would have a function on the scm_instance "enable_pullrequest() and enable_fork()"
235 %if c.rhodecode_db_repo.repo_type in ['git','hg']:
235 %if c.rhodecode_db_repo.repo_type in ['git','hg']:
236 <li class="${is_active('showpullrequest')}">
236 <li class="${is_active('showpullrequest')}">
237 <a class="menulink" href="${h.route_path('pullrequest_show_all', repo_name=c.repo_name)}" title="${h.tooltip(_('Show Pull Requests for %s') % c.repo_name)}">
237 <a class="menulink" href="${h.route_path('pullrequest_show_all', repo_name=c.repo_name)}" title="${h.tooltip(_('Show Pull Requests for %s') % c.repo_name)}">
238 %if c.repository_pull_requests:
238 %if c.repository_pull_requests:
239 <span class="pr_notifications">${c.repository_pull_requests}</span>
239 <span class="pr_notifications">${c.repository_pull_requests}</span>
240 %endif
240 %endif
241 <div class="menulabel">${_('Pull Requests')}</div>
241 <div class="menulabel">${_('Pull Requests')}</div>
242 </a>
242 </a>
243 </li>
243 </li>
244 %endif
244 %endif
245 <li class="${is_active('options')}">
245 <li class="${is_active('options')}">
246 <a class="menulink dropdown">
246 <a class="menulink dropdown">
247 <div class="menulabel">${_('Options')} <div class="show_more"></div></div>
247 <div class="menulabel">${_('Options')} <div class="show_more"></div></div>
248 </a>
248 </a>
249 <ul class="submenu">
249 <ul class="submenu">
250 %if h.HasRepoPermissionAll('repository.admin')(c.repo_name):
250 %if h.HasRepoPermissionAll('repository.admin')(c.repo_name):
251 <li><a href="${h.route_path('edit_repo',repo_name=c.repo_name)}">${_('Settings')}</a></li>
251 <li><a href="${h.route_path('edit_repo',repo_name=c.repo_name)}">${_('Settings')}</a></li>
252 %endif
252 %endif
253 %if c.rhodecode_db_repo.fork:
253 %if c.rhodecode_db_repo.fork:
254 <li>
254 <li>
255 <a title="${h.tooltip(_('Compare fork with %s' % c.rhodecode_db_repo.fork.repo_name))}"
255 <a title="${h.tooltip(_('Compare fork with %s' % c.rhodecode_db_repo.fork.repo_name))}"
256 href="${h.route_path('repo_compare',
256 href="${h.route_path('repo_compare',
257 repo_name=c.rhodecode_db_repo.fork.repo_name,
257 repo_name=c.rhodecode_db_repo.fork.repo_name,
258 source_ref_type=c.rhodecode_db_repo.landing_rev[0],
258 source_ref_type=c.rhodecode_db_repo.landing_rev[0],
259 source_ref=c.rhodecode_db_repo.landing_rev[1],
259 source_ref=c.rhodecode_db_repo.landing_rev[1],
260 target_repo=c.repo_name,target_ref_type='branch' if request.GET.get('branch') else c.rhodecode_db_repo.landing_rev[0],
260 target_repo=c.repo_name,target_ref_type='branch' if request.GET.get('branch') else c.rhodecode_db_repo.landing_rev[0],
261 target_ref=request.GET.get('branch') or c.rhodecode_db_repo.landing_rev[1],
261 target_ref=request.GET.get('branch') or c.rhodecode_db_repo.landing_rev[1],
262 _query=dict(merge=1))}"
262 _query=dict(merge=1))}"
263 >
263 >
264 ${_('Compare fork')}
264 ${_('Compare fork')}
265 </a>
265 </a>
266 </li>
266 </li>
267 %endif
267 %endif
268
268
269 <li><a href="${h.route_path('search_repo',repo_name=c.repo_name)}">${_('Search')}</a></li>
269 <li><a href="${h.route_path('search_repo',repo_name=c.repo_name)}">${_('Search')}</a></li>
270
270
271 %if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name) and c.rhodecode_db_repo.enable_locking:
271 %if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name) and c.rhodecode_db_repo.enable_locking:
272 %if c.rhodecode_db_repo.locked[0]:
272 %if c.rhodecode_db_repo.locked[0]:
273 <li><a class="locking_del" href="${h.route_path('repo_edit_toggle_locking',repo_name=c.repo_name)}">${_('Unlock')}</a></li>
273 <li><a class="locking_del" href="${h.route_path('repo_edit_toggle_locking',repo_name=c.repo_name)}">${_('Unlock')}</a></li>
274 %else:
274 %else:
275 <li><a class="locking_add" href="${h.route_path('repo_edit_toggle_locking',repo_name=c.repo_name)}">${_('Lock')}</a></li>
275 <li><a class="locking_add" href="${h.route_path('repo_edit_toggle_locking',repo_name=c.repo_name)}">${_('Lock')}</a></li>
276 %endif
276 %endif
277 %endif
277 %endif
278 %if c.rhodecode_user.username != h.DEFAULT_USER:
278 %if c.rhodecode_user.username != h.DEFAULT_USER:
279 %if c.rhodecode_db_repo.repo_type in ['git','hg']:
279 %if c.rhodecode_db_repo.repo_type in ['git','hg']:
280 <li><a href="${h.route_path('repo_fork_new',repo_name=c.repo_name)}">${_('Fork')}</a></li>
280 <li><a href="${h.route_path('repo_fork_new',repo_name=c.repo_name)}">${_('Fork')}</a></li>
281 <li><a href="${h.route_path('pullrequest_new',repo_name=c.repo_name)}">${_('Create Pull Request')}</a></li>
281 <li><a href="${h.route_path('pullrequest_new',repo_name=c.repo_name)}">${_('Create Pull Request')}</a></li>
282 %endif
282 %endif
283 %endif
283 %endif
284 </ul>
284 </ul>
285 </li>
285 </li>
286 </ul>
286 </ul>
287 </div>
287 </div>
288 <div class="clear"></div>
288 <div class="clear"></div>
289 </div>
289 </div>
290 % if c.rhodecode_db_repo.archived:
290 % if c.rhodecode_db_repo.archived:
291 <div class="alert alert-warning text-center">
291 <div class="alert alert-warning text-center">
292 <strong>${_('This repository has been archived. It is now read-only.')}</strong>
292 <strong>${_('This repository has been archived. It is now read-only.')}</strong>
293 </div>
293 </div>
294 % endif
294 % endif
295 <!--- END CONTEXT BAR -->
295 <!--- END CONTEXT BAR -->
296
296
297 </%def>
297 </%def>
298
298
299 <%def name="usermenu(active=False)">
299 <%def name="usermenu(active=False)">
300 ## USER MENU
300 ## USER MENU
301 <li id="quick_login_li" class="${'active' if active else ''}">
301 <li id="quick_login_li" class="${'active' if active else ''}">
302 <a id="quick_login_link" class="menulink childs">
302 <a id="quick_login_link" class="menulink childs">
303 ${gravatar(c.rhodecode_user.email, 20)}
303 ${gravatar(c.rhodecode_user.email, 20)}
304 <span class="user">
304 <span class="user">
305 %if c.rhodecode_user.username != h.DEFAULT_USER:
305 %if c.rhodecode_user.username != h.DEFAULT_USER:
306 <span class="menu_link_user">${c.rhodecode_user.username}</span><div class="show_more"></div>
306 <span class="menu_link_user">${c.rhodecode_user.username}</span><div class="show_more"></div>
307 %else:
307 %else:
308 <span>${_('Sign in')}</span>
308 <span>${_('Sign in')}</span>
309 %endif
309 %endif
310 </span>
310 </span>
311 </a>
311 </a>
312
312
313 <div class="user-menu submenu">
313 <div class="user-menu submenu">
314 <div id="quick_login">
314 <div id="quick_login">
315 %if c.rhodecode_user.username == h.DEFAULT_USER:
315 %if c.rhodecode_user.username == h.DEFAULT_USER:
316 <h4>${_('Sign in to your account')}</h4>
316 <h4>${_('Sign in to your account')}</h4>
317 ${h.form(h.route_path('login', _query={'came_from': h.current_route_path(request)}), needs_csrf_token=False)}
317 ${h.form(h.route_path('login', _query={'came_from': h.current_route_path(request)}), needs_csrf_token=False)}
318 <div class="form form-vertical">
318 <div class="form form-vertical">
319 <div class="fields">
319 <div class="fields">
320 <div class="field">
320 <div class="field">
321 <div class="label">
321 <div class="label">
322 <label for="username">${_('Username')}:</label>
322 <label for="username">${_('Username')}:</label>
323 </div>
323 </div>
324 <div class="input">
324 <div class="input">
325 ${h.text('username',class_='focus',tabindex=1)}
325 ${h.text('username',class_='focus',tabindex=1)}
326 </div>
326 </div>
327
327
328 </div>
328 </div>
329 <div class="field">
329 <div class="field">
330 <div class="label">
330 <div class="label">
331 <label for="password">${_('Password')}:</label>
331 <label for="password">${_('Password')}:</label>
332 %if h.HasPermissionAny('hg.password_reset.enabled')():
332 %if h.HasPermissionAny('hg.password_reset.enabled')():
333 <span class="forgot_password">${h.link_to(_('(Forgot password?)'),h.route_path('reset_password'), class_='pwd_reset')}</span>
333 <span class="forgot_password">${h.link_to(_('(Forgot password?)'),h.route_path('reset_password'), class_='pwd_reset')}</span>
334 %endif
334 %endif
335 </div>
335 </div>
336 <div class="input">
336 <div class="input">
337 ${h.password('password',class_='focus',tabindex=2)}
337 ${h.password('password',class_='focus',tabindex=2)}
338 </div>
338 </div>
339 </div>
339 </div>
340 <div class="buttons">
340 <div class="buttons">
341 <div class="register">
341 <div class="register">
342 %if h.HasPermissionAny('hg.admin', 'hg.register.auto_activate', 'hg.register.manual_activate')():
342 %if h.HasPermissionAny('hg.admin', 'hg.register.auto_activate', 'hg.register.manual_activate')():
343 ${h.link_to(_("Don't have an account?"),h.route_path('register'))} <br/>
343 ${h.link_to(_("Don't have an account?"),h.route_path('register'))} <br/>
344 %endif
344 %endif
345 ${h.link_to(_("Using external auth? Sign In here."),h.route_path('login'))}
345 ${h.link_to(_("Using external auth? Sign In here."),h.route_path('login'))}
346 </div>
346 </div>
347 <div class="submit">
347 <div class="submit">
348 ${h.submit('sign_in',_('Sign In'),class_="btn btn-small",tabindex=3)}
348 ${h.submit('sign_in',_('Sign In'),class_="btn btn-small",tabindex=3)}
349 </div>
349 </div>
350 </div>
350 </div>
351 </div>
351 </div>
352 </div>
352 </div>
353 ${h.end_form()}
353 ${h.end_form()}
354 %else:
354 %else:
355 <div class="">
355 <div class="">
356 <div class="big_gravatar">${gravatar(c.rhodecode_user.email, 48)}</div>
356 <div class="big_gravatar">${gravatar(c.rhodecode_user.email, 48)}</div>
357 <div class="full_name">${c.rhodecode_user.full_name_or_username}</div>
357 <div class="full_name">${c.rhodecode_user.full_name_or_username}</div>
358 <div class="email">${c.rhodecode_user.email}</div>
358 <div class="email">${c.rhodecode_user.email}</div>
359 </div>
359 </div>
360 <div class="">
360 <div class="">
361 <ol class="links">
361 <ol class="links">
362 <li>${h.link_to(_(u'My account'),h.route_path('my_account_profile'))}</li>
362 <li>${h.link_to(_(u'My account'),h.route_path('my_account_profile'))}</li>
363 % if c.rhodecode_user.personal_repo_group:
363 % if c.rhodecode_user.personal_repo_group:
364 <li>${h.link_to(_(u'My personal group'), h.route_path('repo_group_home', repo_group_name=c.rhodecode_user.personal_repo_group.group_name))}</li>
364 <li>${h.link_to(_(u'My personal group'), h.route_path('repo_group_home', repo_group_name=c.rhodecode_user.personal_repo_group.group_name))}</li>
365 % endif
365 % endif
366 <li>${h.link_to(_(u'Pull Requests'), h.route_path('my_account_pullrequests'))}</li>
366 <li>${h.link_to(_(u'Pull Requests'), h.route_path('my_account_pullrequests'))}</li>
367
367
368 <li class="logout">
368 <li class="logout">
369 ${h.secure_form(h.route_path('logout'), request=request)}
369 ${h.secure_form(h.route_path('logout'), request=request)}
370 ${h.submit('log_out', _(u'Sign Out'),class_="btn btn-primary")}
370 ${h.submit('log_out', _(u'Sign Out'),class_="btn btn-primary")}
371 ${h.end_form()}
371 ${h.end_form()}
372 </li>
372 </li>
373 </ol>
373 </ol>
374 </div>
374 </div>
375 %endif
375 %endif
376 </div>
376 </div>
377 </div>
377 </div>
378 %if c.rhodecode_user.username != h.DEFAULT_USER:
378 %if c.rhodecode_user.username != h.DEFAULT_USER:
379 <div class="pill_container">
379 <div class="pill_container">
380 <a class="menu_link_notifications ${'empty' if c.unread_notifications == 0 else ''}" href="${h.route_path('notifications_show_all')}">${c.unread_notifications}</a>
380 <a class="menu_link_notifications ${'empty' if c.unread_notifications == 0 else ''}" href="${h.route_path('notifications_show_all')}">${c.unread_notifications}</a>
381 </div>
381 </div>
382 % endif
382 % endif
383 </li>
383 </li>
384 </%def>
384 </%def>
385
385
386 <%def name="menu_items(active=None)">
386 <%def name="menu_items(active=None)">
387 <%
387 <%
388 def is_active(selected):
388 def is_active(selected):
389 if selected == active:
389 if selected == active:
390 return "active"
390 return "active"
391 return ""
391 return ""
392 %>
392 %>
393
393
394 <ul id="quick" class="main_nav navigation horizontal-list">
394 <ul id="quick" class="main_nav navigation horizontal-list">
395 ## notice box for important system messages
395 ## notice box for important system messages
396 <li style="display: none">
396 <li style="display: none">
397 <a class="notice-box" href="#openNotice" onclick="showNoticeBox(); return false">
397 <a class="notice-box" href="#openNotice" onclick="showNoticeBox(); return false">
398 <div class="menulabel-notice" >
398 <div class="menulabel-notice" >
399 0
399 0
400 </div>
400 </div>
401 </a>
401 </a>
402 </li>
402 </li>
403
403
404 ## Main filter
404 ## Main filter
405 <li>
405 <li>
406 <div class="menulabel main_filter_box">
406 <div class="menulabel main_filter_box">
407 <div class="main_filter_input_box">
407 <div class="main_filter_input_box">
408 <input class="main_filter_input" id="main_filter" size="15" type="text" name="main_filter" placeholder="${_('search / go to...')}" value=""/>
408 <input class="main_filter_input" id="main_filter" size="15" type="text" name="main_filter" placeholder="${_('search / go to...')}" value=""/>
409 </div>
409 </div>
410 <div class="main_filter_help_box">
410 <div class="main_filter_help_box">
411 <a href="#showFilterHelp" onclick="showMainFilterBox(); return false">?</a>
411 <a href="#showFilterHelp" onclick="showMainFilterBox(); return false">?</a>
412 </div>
412 </div>
413 </div>
413 </div>
414
414
415 <div id="main_filter_help" style="display: none">
415 <div id="main_filter_help" style="display: none">
416 Use '/' key to quickly access this field.
416 Use '/' key to quickly access this field.
417 Enter name of repository, or repository group for quick search.
417 Enter name of repository, or repository group for quick search.
418
418
419 Prefix query to allow special search:
419 Prefix query to allow special search:
420
420
421 user:admin, to search for usernames
421 user:admin, to search for usernames
422
422
423 user_group:devops, to search for user groups
423 user_group:devops, to search for user groups
424
424
425 commit:efced4, to search for commits
425 commit:efced4, to search for commits
426
426
427 </div>
427 </div>
428 </li>
428 </li>
429
429
430 ## ROOT MENU
430 ## ROOT MENU
431 %if c.rhodecode_user.username != h.DEFAULT_USER:
431 %if c.rhodecode_user.username != h.DEFAULT_USER:
432 <li class="${is_active('journal')}">
432 <li class="${is_active('journal')}">
433 <a class="menulink" title="${_('Show activity journal')}" href="${h.route_path('journal')}">
433 <a class="menulink" title="${_('Show activity journal')}" href="${h.route_path('journal')}">
434 <div class="menulabel">${_('Journal')}</div>
434 <div class="menulabel">${_('Journal')}</div>
435 </a>
435 </a>
436 </li>
436 </li>
437 %else:
437 %else:
438 <li class="${is_active('journal')}">
438 <li class="${is_active('journal')}">
439 <a class="menulink" title="${_('Show Public activity journal')}" href="${h.route_path('journal_public')}">
439 <a class="menulink" title="${_('Show Public activity journal')}" href="${h.route_path('journal_public')}">
440 <div class="menulabel">${_('Public journal')}</div>
440 <div class="menulabel">${_('Public journal')}</div>
441 </a>
441 </a>
442 </li>
442 </li>
443 %endif
443 %endif
444 <li class="${is_active('gists')}">
444 <li class="${is_active('gists')}">
445 <a class="menulink childs" title="${_('Show Gists')}" href="${h.route_path('gists_show')}">
445 <a class="menulink childs" title="${_('Show Gists')}" href="${h.route_path('gists_show')}">
446 <div class="menulabel">${_('Gists')}</div>
446 <div class="menulabel">${_('Gists')}</div>
447 </a>
447 </a>
448 </li>
448 </li>
449 <li class="${is_active('search')}">
449 <li class="${is_active('search')}">
450 <a class="menulink" title="${_('Search in repositories you have access to')}" href="${h.route_path('search')}">
450 <a class="menulink" title="${_('Search in repositories you have access to')}" href="${h.route_path('search')}">
451 <div class="menulabel">${_('Search')}</div>
451 <div class="menulabel">${_('Search')}</div>
452 </a>
452 </a>
453 </li>
453 </li>
454 % if h.HasPermissionAll('hg.admin')('access admin main page'):
454 % if h.HasPermissionAll('hg.admin')('access admin main page'):
455 <li class="${is_active('admin')}">
455 <li class="${is_active('admin')}">
456 <a class="menulink childs" title="${_('Admin settings')}" href="#" onclick="return false;">
456 <a class="menulink childs" title="${_('Admin settings')}" href="#" onclick="return false;">
457 <div class="menulabel">${_('Admin')} <div class="show_more"></div></div>
457 <div class="menulabel">${_('Admin')} <div class="show_more"></div></div>
458 </a>
458 </a>
459 ${admin_menu()}
459 ${admin_menu()}
460 </li>
460 </li>
461 % elif c.rhodecode_user.repositories_admin or c.rhodecode_user.repository_groups_admin or c.rhodecode_user.user_groups_admin:
461 % elif c.rhodecode_user.repositories_admin or c.rhodecode_user.repository_groups_admin or c.rhodecode_user.user_groups_admin:
462 <li class="${is_active('admin')}">
462 <li class="${is_active('admin')}">
463 <a class="menulink childs" title="${_('Delegated Admin settings')}">
463 <a class="menulink childs" title="${_('Delegated Admin settings')}">
464 <div class="menulabel">${_('Admin')} <div class="show_more"></div></div>
464 <div class="menulabel">${_('Admin')} <div class="show_more"></div></div>
465 </a>
465 </a>
466 ${admin_menu_simple(c.rhodecode_user.repositories_admin,
466 ${admin_menu_simple(c.rhodecode_user.repositories_admin,
467 c.rhodecode_user.repository_groups_admin,
467 c.rhodecode_user.repository_groups_admin,
468 c.rhodecode_user.user_groups_admin or h.HasPermissionAny('hg.usergroup.create.true')())}
468 c.rhodecode_user.user_groups_admin or h.HasPermissionAny('hg.usergroup.create.true')())}
469 </li>
469 </li>
470 % endif
470 % endif
471 ## render extra user menu
471 ## render extra user menu
472 ${usermenu(active=(active=='my_account'))}
472 ${usermenu(active=(active=='my_account'))}
473
473
474 % if c.debug_style:
474 % if c.debug_style:
475 <li>
475 <li>
476 <a class="menulink" title="${_('Style')}" href="${h.route_path('debug_style_home')}">
476 <a class="menulink" title="${_('Style')}" href="${h.route_path('debug_style_home')}">
477 <div class="menulabel">${_('[Style]')}</div>
477 <div class="menulabel">${_('[Style]')}</div>
478 </a>
478 </a>
479 </li>
479 </li>
480 % endif
480 % endif
481 </ul>
481 </ul>
482
482
483 <script type="text/javascript">
483 <script type="text/javascript">
484 var visualShowPublicIcon = "${c.visual.show_public_icon}" == "True";
484 var visualShowPublicIcon = "${c.visual.show_public_icon}" == "True";
485
485
486 var formatRepoResult = function(result, container, query, escapeMarkup) {
486 var formatRepoResult = function(result, container, query, escapeMarkup) {
487 return function(data, escapeMarkup) {
487 return function(data, escapeMarkup) {
488 if (!data.repo_id){
488 if (!data.repo_id){
489 return data.text; // optgroup text Repositories
489 return data.text; // optgroup text Repositories
490 }
490 }
491
491
492 var tmpl = '';
492 var tmpl = '';
493 var repoType = data['repo_type'];
493 var repoType = data['repo_type'];
494 var repoName = data['text'];
494 var repoName = data['text'];
495
495
496 if(data && data.type == 'repo'){
496 if(data && data.type == 'repo'){
497 if(repoType === 'hg'){
497 if(repoType === 'hg'){
498 tmpl += '<i class="icon-hg"></i> ';
498 tmpl += '<i class="icon-hg"></i> ';
499 }
499 }
500 else if(repoType === 'git'){
500 else if(repoType === 'git'){
501 tmpl += '<i class="icon-git"></i> ';
501 tmpl += '<i class="icon-git"></i> ';
502 }
502 }
503 else if(repoType === 'svn'){
503 else if(repoType === 'svn'){
504 tmpl += '<i class="icon-svn"></i> ';
504 tmpl += '<i class="icon-svn"></i> ';
505 }
505 }
506 if(data['private']){
506 if(data['private']){
507 tmpl += '<i class="icon-lock" ></i> ';
507 tmpl += '<i class="icon-lock" ></i> ';
508 }
508 }
509 else if(visualShowPublicIcon){
509 else if(visualShowPublicIcon){
510 tmpl += '<i class="icon-unlock-alt"></i> ';
510 tmpl += '<i class="icon-unlock-alt"></i> ';
511 }
511 }
512 }
512 }
513 tmpl += escapeMarkup(repoName);
513 tmpl += escapeMarkup(repoName);
514 return tmpl;
514 return tmpl;
515
515
516 }(result, escapeMarkup);
516 }(result, escapeMarkup);
517 };
517 };
518
518
519
519
520 var autocompleteMainFilterFormatResult = function (data, value, org_formatter) {
520 var autocompleteMainFilterFormatResult = function (data, value, org_formatter) {
521
521
522 if (value.split(':').length === 2) {
522 if (value.split(':').length === 2) {
523 value = value.split(':')[1]
523 value = value.split(':')[1]
524 }
524 }
525
525
526 var searchType = data['type'];
526 var searchType = data['type'];
527 var valueDisplay = data['value_display'];
527 var valueDisplay = data['value_display'];
528
528
529 var escapeRegExChars = function (value) {
529 var escapeRegExChars = function (value) {
530 return value.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
530 return value.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
531 };
531 };
532 var pattern = '(' + escapeRegExChars(value) + ')';
532 var pattern = '(' + escapeRegExChars(value) + ')';
533
533
534 // highlight match
534 // highlight match
535 valueDisplay = Select2.util.escapeMarkup(valueDisplay);
535 valueDisplay = Select2.util.escapeMarkup(valueDisplay);
536 valueDisplay = valueDisplay.replace(new RegExp(pattern, 'gi'), '<strong>$1<\/strong>');
536 valueDisplay = valueDisplay.replace(new RegExp(pattern, 'gi'), '<strong>$1<\/strong>');
537
537
538 var icon = '';
538 var icon = '';
539
539
540 if (searchType === 'hint') {
540 if (searchType === 'hint') {
541 icon += '<i class="icon-folder-close"></i> ';
541 icon += '<i class="icon-folder-close"></i> ';
542 }
542 }
543 else if (searchType === 'search') {
543 else if (searchType === 'search') {
544 icon += '<i class="icon-more"></i> ';
544 icon += '<i class="icon-more"></i> ';
545 }
545 }
546 else if (searchType === 'repo') {
546 else if (searchType === 'repo') {
547 if (data['repo_type'] === 'hg') {
547 if (data['repo_type'] === 'hg') {
548 icon += '<i class="icon-hg"></i> ';
548 icon += '<i class="icon-hg"></i> ';
549 }
549 }
550 else if (data['repo_type'] === 'git') {
550 else if (data['repo_type'] === 'git') {
551 icon += '<i class="icon-git"></i> ';
551 icon += '<i class="icon-git"></i> ';
552 }
552 }
553 else if (data['repo_type'] === 'svn') {
553 else if (data['repo_type'] === 'svn') {
554 icon += '<i class="icon-svn"></i> ';
554 icon += '<i class="icon-svn"></i> ';
555 }
555 }
556 if (data['private']) {
556 if (data['private']) {
557 icon += '<i class="icon-lock" ></i> ';
557 icon += '<i class="icon-lock" ></i> ';
558 }
558 }
559 else if (visualShowPublicIcon) {
559 else if (visualShowPublicIcon) {
560 icon += '<i class="icon-unlock-alt"></i> ';
560 icon += '<i class="icon-unlock-alt"></i> ';
561 }
561 }
562 }
562 }
563 else if (searchType === 'repo_group') {
563 else if (searchType === 'repo_group') {
564 icon += '<i class="icon-folder-close"></i> ';
564 icon += '<i class="icon-folder-close"></i> ';
565 }
565 }
566 else if (searchType === 'user_group') {
566 else if (searchType === 'user_group') {
567 icon += '<i class="icon-group"></i> ';
567 icon += '<i class="icon-group"></i> ';
568 }
568 }
569 else if (searchType === 'user') {
569 else if (searchType === 'user') {
570 icon += '<img class="gravatar" src="{0}"/>'.format(data['icon_link']);
570 icon += '<img class="gravatar" src="{0}"/>'.format(data['icon_link']);
571 }
571 }
572 else if (searchType === 'commit') {
572 else if (searchType === 'commit') {
573 icon += '<i class="icon-tag"></i>';
573 icon += '<i class="icon-tag"></i>';
574 }
574 }
575
575
576 var tmpl = '<div class="ac-container-wrap">{0}{1}</div>';
576 var tmpl = '<div class="ac-container-wrap">{0}{1}</div>';
577 return tmpl.format(icon, valueDisplay);
577 return tmpl.format(icon, valueDisplay);
578 };
578 };
579
579
580 var handleSelect = function(element, suggestion) {
580 var handleSelect = function(element, suggestion) {
581 if (suggestion.type === "hint") {
581 if (suggestion.type === "hint") {
582 // we skip action
582 // we skip action
583 $('#main_filter').focus();
583 $('#main_filter').focus();
584 } else {
584 } else {
585 window.location = suggestion['url'];
585 window.location = suggestion['url'];
586 }
586 }
587 };
587 };
588 var autocompleteMainFilterResult = function (suggestion, originalQuery, queryLowerCase) {
588 var autocompleteMainFilterResult = function (suggestion, originalQuery, queryLowerCase) {
589 if (queryLowerCase.split(':').length === 2) {
589 if (queryLowerCase.split(':').length === 2) {
590 queryLowerCase = queryLowerCase.split(':')[1]
590 queryLowerCase = queryLowerCase.split(':')[1]
591 }
591 }
592 return suggestion.value_display.toLowerCase().indexOf(queryLowerCase) !== -1;
592 return suggestion.value_display.toLowerCase().indexOf(queryLowerCase) !== -1;
593 };
593 };
594
594
595 $('#main_filter').autocomplete({
595 $('#main_filter').autocomplete({
596 serviceUrl: pyroutes.url('goto_switcher_data'),
596 serviceUrl: pyroutes.url('goto_switcher_data'),
597 params: {"search_context": templateContext.search_context},
597 params: {"search_context": templateContext.search_context},
598 minChars:2,
598 minChars:2,
599 maxHeight:400,
599 maxHeight:400,
600 deferRequestBy: 300, //miliseconds
600 deferRequestBy: 300, //miliseconds
601 tabDisabled: true,
601 tabDisabled: true,
602 autoSelectFirst: true,
602 autoSelectFirst: true,
603 formatResult: autocompleteMainFilterFormatResult,
603 formatResult: autocompleteMainFilterFormatResult,
604 lookupFilter: autocompleteMainFilterResult,
604 lookupFilter: autocompleteMainFilterResult,
605 onSelect: function (element, suggestion) {
605 onSelect: function (element, suggestion) {
606 handleSelect(element, suggestion);
606 handleSelect(element, suggestion);
607 return false;
607 return false;
608 },
608 },
609 onSearchError: function (element, query, jqXHR, textStatus, errorThrown) {
609 onSearchError: function (element, query, jqXHR, textStatus, errorThrown) {
610 if (jqXHR !== 'abort') {
610 if (jqXHR !== 'abort') {
611 alert("Error during search.\nError code: {0}".format(textStatus));
611 alert("Error during search.\nError code: {0}".format(textStatus));
612 window.location = '';
612 window.location = '';
613 }
613 }
614 }
614 }
615 });
615 });
616
616
617 showMainFilterBox = function () {
617 showMainFilterBox = function () {
618 $('#main_filter_help').toggle();
618 $('#main_filter_help').toggle();
619 }
619 }
620
620
621 </script>
621 </script>
622 <script src="${h.asset('js/rhodecode/base/keyboard-bindings.js', ver=c.rhodecode_version_hash)}"></script>
622 <script src="${h.asset('js/rhodecode/base/keyboard-bindings.js', ver=c.rhodecode_version_hash)}"></script>
623 </%def>
623 </%def>
624
624
625 <div class="modal" id="help_kb" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
625 <div class="modal" id="help_kb" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
626 <div class="modal-dialog">
626 <div class="modal-dialog">
627 <div class="modal-content">
627 <div class="modal-content">
628 <div class="modal-header">
628 <div class="modal-header">
629 <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
629 <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
630 <h4 class="modal-title" id="myModalLabel">${_('Keyboard shortcuts')}</h4>
630 <h4 class="modal-title" id="myModalLabel">${_('Keyboard shortcuts')}</h4>
631 </div>
631 </div>
632 <div class="modal-body">
632 <div class="modal-body">
633 <div class="block-left">
633 <div class="block-left">
634 <table class="keyboard-mappings">
634 <table class="keyboard-mappings">
635 <tbody>
635 <tbody>
636 <tr>
636 <tr>
637 <th></th>
637 <th></th>
638 <th>${_('Site-wide shortcuts')}</th>
638 <th>${_('Site-wide shortcuts')}</th>
639 </tr>
639 </tr>
640 <%
640 <%
641 elems = [
641 elems = [
642 ('/', 'Use quick search box'),
642 ('/', 'Use quick search box'),
643 ('g h', 'Goto home page'),
643 ('g h', 'Goto home page'),
644 ('g g', 'Goto my private gists page'),
644 ('g g', 'Goto my private gists page'),
645 ('g G', 'Goto my public gists page'),
645 ('g G', 'Goto my public gists page'),
646 ('n r', 'New repository page'),
646 ('n r', 'New repository page'),
647 ('n g', 'New gist page'),
647 ('n g', 'New gist page'),
648 ]
648 ]
649 %>
649 %>
650 %for key, desc in elems:
650 %for key, desc in elems:
651 <tr>
651 <tr>
652 <td class="keys">
652 <td class="keys">
653 <span class="key tag">${key}</span>
653 <span class="key tag">${key}</span>
654 </td>
654 </td>
655 <td>${desc}</td>
655 <td>${desc}</td>
656 </tr>
656 </tr>
657 %endfor
657 %endfor
658 </tbody>
658 </tbody>
659 </table>
659 </table>
660 </div>
660 </div>
661 <div class="block-left">
661 <div class="block-left">
662 <table class="keyboard-mappings">
662 <table class="keyboard-mappings">
663 <tbody>
663 <tbody>
664 <tr>
664 <tr>
665 <th></th>
665 <th></th>
666 <th>${_('Repositories')}</th>
666 <th>${_('Repositories')}</th>
667 </tr>
667 </tr>
668 <%
668 <%
669 elems = [
669 elems = [
670 ('g s', 'Goto summary page'),
670 ('g s', 'Goto summary page'),
671 ('g c', 'Goto changelog page'),
671 ('g c', 'Goto changelog page'),
672 ('g f', 'Goto files page'),
672 ('g f', 'Goto files page'),
673 ('g F', 'Goto files page with file search activated'),
673 ('g F', 'Goto files page with file search activated'),
674 ('g p', 'Goto pull requests page'),
674 ('g p', 'Goto pull requests page'),
675 ('g o', 'Goto repository settings'),
675 ('g o', 'Goto repository settings'),
676 ('g O', 'Goto repository permissions settings'),
676 ('g O', 'Goto repository permissions settings'),
677 ]
677 ]
678 %>
678 %>
679 %for key, desc in elems:
679 %for key, desc in elems:
680 <tr>
680 <tr>
681 <td class="keys">
681 <td class="keys">
682 <span class="key tag">${key}</span>
682 <span class="key tag">${key}</span>
683 </td>
683 </td>
684 <td>${desc}</td>
684 <td>${desc}</td>
685 </tr>
685 </tr>
686 %endfor
686 %endfor
687 </tbody>
687 </tbody>
688 </table>
688 </table>
689 </div>
689 </div>
690 </div>
690 </div>
691 <div class="modal-footer">
691 <div class="modal-footer">
692 </div>
692 </div>
693 </div><!-- /.modal-content -->
693 </div><!-- /.modal-content -->
694 </div><!-- /.modal-dialog -->
694 </div><!-- /.modal-dialog -->
695 </div><!-- /.modal -->
695 </div><!-- /.modal -->
696
696
General Comments 0
You need to be logged in to leave comments. Login now