##// END OF EJS Templates
menu: expose artifacts count into menu
marcink -
r3984:9bf54585 default
parent child Browse files
Show More
@@ -1,800 +1,802 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 import compat
25 from pyramid import compat
26 from pyramid.httpexceptions import HTTPFound, HTTPForbidden, HTTPBadRequest
26 from pyramid.httpexceptions import HTTPFound, HTTPForbidden, HTTPBadRequest
27
27
28 from rhodecode.lib import helpers as h, diffs, rc_cache
28 from rhodecode.lib import helpers as h, diffs, rc_cache
29 from rhodecode.lib.utils2 import (
29 from rhodecode.lib.utils2 import (
30 StrictAttributeDict, str2bool, safe_int, datetime_to_time, safe_unicode)
30 StrictAttributeDict, str2bool, safe_int, datetime_to_time, safe_unicode)
31 from rhodecode.lib.markup_renderer import MarkupRenderer, relative_links
31 from rhodecode.lib.markup_renderer import MarkupRenderer, relative_links
32 from rhodecode.lib.vcs.backends.base import EmptyCommit
32 from rhodecode.lib.vcs.backends.base import EmptyCommit
33 from rhodecode.lib.vcs.exceptions import RepositoryRequirementError
33 from rhodecode.lib.vcs.exceptions import RepositoryRequirementError
34 from rhodecode.model import repo
34 from rhodecode.model import repo
35 from rhodecode.model import repo_group
35 from rhodecode.model import repo_group
36 from rhodecode.model import user_group
36 from rhodecode.model import user_group
37 from rhodecode.model import user
37 from rhodecode.model import user
38 from rhodecode.model.db import User
38 from rhodecode.model.db import User
39 from rhodecode.model.scm import ScmModel
39 from rhodecode.model.scm import ScmModel
40 from rhodecode.model.settings import VcsSettingsModel
40 from rhodecode.model.settings import VcsSettingsModel
41 from rhodecode.model.repo import ReadmeFinder
41 from rhodecode.model.repo import ReadmeFinder
42
42
43 log = logging.getLogger(__name__)
43 log = logging.getLogger(__name__)
44
44
45
45
46 ADMIN_PREFIX = '/_admin'
46 ADMIN_PREFIX = '/_admin'
47 STATIC_FILE_PREFIX = '/_static'
47 STATIC_FILE_PREFIX = '/_static'
48
48
49 URL_NAME_REQUIREMENTS = {
49 URL_NAME_REQUIREMENTS = {
50 # group name can have a slash in them, but they must not end with a slash
50 # group name can have a slash in them, but they must not end with a slash
51 'group_name': r'.*?[^/]',
51 'group_name': r'.*?[^/]',
52 'repo_group_name': r'.*?[^/]',
52 'repo_group_name': r'.*?[^/]',
53 # repo names can have a slash in them, but they must not end with a slash
53 # repo names can have a slash in them, but they must not end with a slash
54 'repo_name': r'.*?[^/]',
54 'repo_name': r'.*?[^/]',
55 # file path eats up everything at the end
55 # file path eats up everything at the end
56 'f_path': r'.*',
56 'f_path': r'.*',
57 # reference types
57 # reference types
58 'source_ref_type': '(branch|book|tag|rev|\%\(source_ref_type\)s)',
58 'source_ref_type': '(branch|book|tag|rev|\%\(source_ref_type\)s)',
59 'target_ref_type': '(branch|book|tag|rev|\%\(target_ref_type\)s)',
59 'target_ref_type': '(branch|book|tag|rev|\%\(target_ref_type\)s)',
60 }
60 }
61
61
62
62
63 def add_route_with_slash(config,name, pattern, **kw):
63 def add_route_with_slash(config,name, pattern, **kw):
64 config.add_route(name, pattern, **kw)
64 config.add_route(name, pattern, **kw)
65 if not pattern.endswith('/'):
65 if not pattern.endswith('/'):
66 config.add_route(name + '_slash', pattern + '/', **kw)
66 config.add_route(name + '_slash', pattern + '/', **kw)
67
67
68
68
69 def add_route_requirements(route_path, requirements=None):
69 def add_route_requirements(route_path, requirements=None):
70 """
70 """
71 Adds regex requirements to pyramid routes using a mapping dict
71 Adds regex requirements to pyramid routes using a mapping dict
72 e.g::
72 e.g::
73 add_route_requirements('{repo_name}/settings')
73 add_route_requirements('{repo_name}/settings')
74 """
74 """
75 requirements = requirements or URL_NAME_REQUIREMENTS
75 requirements = requirements or URL_NAME_REQUIREMENTS
76 for key, regex in requirements.items():
76 for key, regex in requirements.items():
77 route_path = route_path.replace('{%s}' % key, '{%s:%s}' % (key, regex))
77 route_path = route_path.replace('{%s}' % key, '{%s:%s}' % (key, regex))
78 return route_path
78 return route_path
79
79
80
80
81 def get_format_ref_id(repo):
81 def get_format_ref_id(repo):
82 """Returns a `repo` specific reference formatter function"""
82 """Returns a `repo` specific reference formatter function"""
83 if h.is_svn(repo):
83 if h.is_svn(repo):
84 return _format_ref_id_svn
84 return _format_ref_id_svn
85 else:
85 else:
86 return _format_ref_id
86 return _format_ref_id
87
87
88
88
89 def _format_ref_id(name, raw_id):
89 def _format_ref_id(name, raw_id):
90 """Default formatting of a given reference `name`"""
90 """Default formatting of a given reference `name`"""
91 return name
91 return name
92
92
93
93
94 def _format_ref_id_svn(name, raw_id):
94 def _format_ref_id_svn(name, raw_id):
95 """Special way of formatting a reference for Subversion including path"""
95 """Special way of formatting a reference for Subversion including path"""
96 return '%s@%s' % (name, raw_id)
96 return '%s@%s' % (name, raw_id)
97
97
98
98
99 class TemplateArgs(StrictAttributeDict):
99 class TemplateArgs(StrictAttributeDict):
100 pass
100 pass
101
101
102
102
103 class BaseAppView(object):
103 class BaseAppView(object):
104
104
105 def __init__(self, context, request):
105 def __init__(self, context, request):
106 self.request = request
106 self.request = request
107 self.context = context
107 self.context = context
108 self.session = request.session
108 self.session = request.session
109 if not hasattr(request, 'user'):
109 if not hasattr(request, 'user'):
110 # NOTE(marcink): edge case, we ended up in matched route
110 # NOTE(marcink): edge case, we ended up in matched route
111 # but probably of web-app context, e.g API CALL/VCS CALL
111 # but probably of web-app context, e.g API CALL/VCS CALL
112 if hasattr(request, 'vcs_call') or hasattr(request, 'rpc_method'):
112 if hasattr(request, 'vcs_call') or hasattr(request, 'rpc_method'):
113 log.warning('Unable to process request `%s` in this scope', request)
113 log.warning('Unable to process request `%s` in this scope', request)
114 raise HTTPBadRequest()
114 raise HTTPBadRequest()
115
115
116 self._rhodecode_user = request.user # auth user
116 self._rhodecode_user = request.user # auth user
117 self._rhodecode_db_user = self._rhodecode_user.get_instance()
117 self._rhodecode_db_user = self._rhodecode_user.get_instance()
118 self._maybe_needs_password_change(
118 self._maybe_needs_password_change(
119 request.matched_route.name, self._rhodecode_db_user)
119 request.matched_route.name, self._rhodecode_db_user)
120
120
121 def _maybe_needs_password_change(self, view_name, user_obj):
121 def _maybe_needs_password_change(self, view_name, user_obj):
122 log.debug('Checking if user %s needs password change on view %s',
122 log.debug('Checking if user %s needs password change on view %s',
123 user_obj, view_name)
123 user_obj, view_name)
124 skip_user_views = [
124 skip_user_views = [
125 'logout', 'login',
125 'logout', 'login',
126 'my_account_password', 'my_account_password_update'
126 'my_account_password', 'my_account_password_update'
127 ]
127 ]
128
128
129 if not user_obj:
129 if not user_obj:
130 return
130 return
131
131
132 if user_obj.username == User.DEFAULT_USER:
132 if user_obj.username == User.DEFAULT_USER:
133 return
133 return
134
134
135 now = time.time()
135 now = time.time()
136 should_change = user_obj.user_data.get('force_password_change')
136 should_change = user_obj.user_data.get('force_password_change')
137 change_after = safe_int(should_change) or 0
137 change_after = safe_int(should_change) or 0
138 if should_change and now > change_after:
138 if should_change and now > change_after:
139 log.debug('User %s requires password change', user_obj)
139 log.debug('User %s requires password change', user_obj)
140 h.flash('You are required to change your password', 'warning',
140 h.flash('You are required to change your password', 'warning',
141 ignore_duplicate=True)
141 ignore_duplicate=True)
142
142
143 if view_name not in skip_user_views:
143 if view_name not in skip_user_views:
144 raise HTTPFound(
144 raise HTTPFound(
145 self.request.route_path('my_account_password'))
145 self.request.route_path('my_account_password'))
146
146
147 def _log_creation_exception(self, e, repo_name):
147 def _log_creation_exception(self, e, repo_name):
148 _ = self.request.translate
148 _ = self.request.translate
149 reason = None
149 reason = None
150 if len(e.args) == 2:
150 if len(e.args) == 2:
151 reason = e.args[1]
151 reason = e.args[1]
152
152
153 if reason == 'INVALID_CERTIFICATE':
153 if reason == 'INVALID_CERTIFICATE':
154 log.exception(
154 log.exception(
155 'Exception creating a repository: invalid certificate')
155 'Exception creating a repository: invalid certificate')
156 msg = (_('Error creating repository %s: invalid certificate')
156 msg = (_('Error creating repository %s: invalid certificate')
157 % repo_name)
157 % repo_name)
158 else:
158 else:
159 log.exception("Exception creating a repository")
159 log.exception("Exception creating a repository")
160 msg = (_('Error creating repository %s')
160 msg = (_('Error creating repository %s')
161 % repo_name)
161 % repo_name)
162 return msg
162 return msg
163
163
164 def _get_local_tmpl_context(self, include_app_defaults=True):
164 def _get_local_tmpl_context(self, include_app_defaults=True):
165 c = TemplateArgs()
165 c = TemplateArgs()
166 c.auth_user = self.request.user
166 c.auth_user = self.request.user
167 # TODO(marcink): migrate the usage of c.rhodecode_user to c.auth_user
167 # TODO(marcink): migrate the usage of c.rhodecode_user to c.auth_user
168 c.rhodecode_user = self.request.user
168 c.rhodecode_user = self.request.user
169
169
170 if include_app_defaults:
170 if include_app_defaults:
171 from rhodecode.lib.base import attach_context_attributes
171 from rhodecode.lib.base import attach_context_attributes
172 attach_context_attributes(c, self.request, self.request.user.user_id)
172 attach_context_attributes(c, self.request, self.request.user.user_id)
173
173
174 c.is_super_admin = c.auth_user.is_admin
174 c.is_super_admin = c.auth_user.is_admin
175
175
176 c.can_create_repo = c.is_super_admin
176 c.can_create_repo = c.is_super_admin
177 c.can_create_repo_group = c.is_super_admin
177 c.can_create_repo_group = c.is_super_admin
178 c.can_create_user_group = c.is_super_admin
178 c.can_create_user_group = c.is_super_admin
179
179
180 c.is_delegated_admin = False
180 c.is_delegated_admin = False
181
181
182 if not c.auth_user.is_default and not c.is_super_admin:
182 if not c.auth_user.is_default and not c.is_super_admin:
183 c.can_create_repo = h.HasPermissionAny('hg.create.repository')(
183 c.can_create_repo = h.HasPermissionAny('hg.create.repository')(
184 user=self.request.user)
184 user=self.request.user)
185 repositories = c.auth_user.repositories_admin or c.can_create_repo
185 repositories = c.auth_user.repositories_admin or c.can_create_repo
186
186
187 c.can_create_repo_group = h.HasPermissionAny('hg.repogroup.create.true')(
187 c.can_create_repo_group = h.HasPermissionAny('hg.repogroup.create.true')(
188 user=self.request.user)
188 user=self.request.user)
189 repository_groups = c.auth_user.repository_groups_admin or c.can_create_repo_group
189 repository_groups = c.auth_user.repository_groups_admin or c.can_create_repo_group
190
190
191 c.can_create_user_group = h.HasPermissionAny('hg.usergroup.create.true')(
191 c.can_create_user_group = h.HasPermissionAny('hg.usergroup.create.true')(
192 user=self.request.user)
192 user=self.request.user)
193 user_groups = c.auth_user.user_groups_admin or c.can_create_user_group
193 user_groups = c.auth_user.user_groups_admin or c.can_create_user_group
194 # delegated admin can create, or manage some objects
194 # delegated admin can create, or manage some objects
195 c.is_delegated_admin = repositories or repository_groups or user_groups
195 c.is_delegated_admin = repositories or repository_groups or user_groups
196 return c
196 return c
197
197
198 def _get_template_context(self, tmpl_args, **kwargs):
198 def _get_template_context(self, tmpl_args, **kwargs):
199
199
200 local_tmpl_args = {
200 local_tmpl_args = {
201 'defaults': {},
201 'defaults': {},
202 'errors': {},
202 'errors': {},
203 'c': tmpl_args
203 'c': tmpl_args
204 }
204 }
205 local_tmpl_args.update(kwargs)
205 local_tmpl_args.update(kwargs)
206 return local_tmpl_args
206 return local_tmpl_args
207
207
208 def load_default_context(self):
208 def load_default_context(self):
209 """
209 """
210 example:
210 example:
211
211
212 def load_default_context(self):
212 def load_default_context(self):
213 c = self._get_local_tmpl_context()
213 c = self._get_local_tmpl_context()
214 c.custom_var = 'foobar'
214 c.custom_var = 'foobar'
215
215
216 return c
216 return c
217 """
217 """
218 raise NotImplementedError('Needs implementation in view class')
218 raise NotImplementedError('Needs implementation in view class')
219
219
220
220
221 class RepoAppView(BaseAppView):
221 class RepoAppView(BaseAppView):
222
222
223 def __init__(self, context, request):
223 def __init__(self, context, request):
224 super(RepoAppView, self).__init__(context, request)
224 super(RepoAppView, self).__init__(context, request)
225 self.db_repo = request.db_repo
225 self.db_repo = request.db_repo
226 self.db_repo_name = self.db_repo.repo_name
226 self.db_repo_name = self.db_repo.repo_name
227 self.db_repo_pull_requests = ScmModel().get_pull_requests(self.db_repo)
227 self.db_repo_pull_requests = ScmModel().get_pull_requests(self.db_repo)
228 self.db_repo_artifacts = ScmModel().get_artifacts(self.db_repo)
228
229
229 def _handle_missing_requirements(self, error):
230 def _handle_missing_requirements(self, error):
230 log.error(
231 log.error(
231 'Requirements are missing for repository %s: %s',
232 'Requirements are missing for repository %s: %s',
232 self.db_repo_name, safe_unicode(error))
233 self.db_repo_name, safe_unicode(error))
233
234
234 def _get_local_tmpl_context(self, include_app_defaults=True):
235 def _get_local_tmpl_context(self, include_app_defaults=True):
235 _ = self.request.translate
236 _ = self.request.translate
236 c = super(RepoAppView, self)._get_local_tmpl_context(
237 c = super(RepoAppView, self)._get_local_tmpl_context(
237 include_app_defaults=include_app_defaults)
238 include_app_defaults=include_app_defaults)
238
239
239 # register common vars for this type of view
240 # register common vars for this type of view
240 c.rhodecode_db_repo = self.db_repo
241 c.rhodecode_db_repo = self.db_repo
241 c.repo_name = self.db_repo_name
242 c.repo_name = self.db_repo_name
242 c.repository_pull_requests = self.db_repo_pull_requests
243 c.repository_pull_requests = self.db_repo_pull_requests
244 c.repository_artifacts = self.db_repo_artifacts
243 c.repository_is_user_following = ScmModel().is_following_repo(
245 c.repository_is_user_following = ScmModel().is_following_repo(
244 self.db_repo_name, self._rhodecode_user.user_id)
246 self.db_repo_name, self._rhodecode_user.user_id)
245 self.path_filter = PathFilter(None)
247 self.path_filter = PathFilter(None)
246
248
247 c.repository_requirements_missing = {}
249 c.repository_requirements_missing = {}
248 try:
250 try:
249 self.rhodecode_vcs_repo = self.db_repo.scm_instance()
251 self.rhodecode_vcs_repo = self.db_repo.scm_instance()
250 # NOTE(marcink):
252 # NOTE(marcink):
251 # comparison to None since if it's an object __bool__ is expensive to
253 # comparison to None since if it's an object __bool__ is expensive to
252 # calculate
254 # calculate
253 if self.rhodecode_vcs_repo is not None:
255 if self.rhodecode_vcs_repo is not None:
254 path_perms = self.rhodecode_vcs_repo.get_path_permissions(
256 path_perms = self.rhodecode_vcs_repo.get_path_permissions(
255 c.auth_user.username)
257 c.auth_user.username)
256 self.path_filter = PathFilter(path_perms)
258 self.path_filter = PathFilter(path_perms)
257 except RepositoryRequirementError as e:
259 except RepositoryRequirementError as e:
258 c.repository_requirements_missing = {'error': str(e)}
260 c.repository_requirements_missing = {'error': str(e)}
259 self._handle_missing_requirements(e)
261 self._handle_missing_requirements(e)
260 self.rhodecode_vcs_repo = None
262 self.rhodecode_vcs_repo = None
261
263
262 c.path_filter = self.path_filter # used by atom_feed_entry.mako
264 c.path_filter = self.path_filter # used by atom_feed_entry.mako
263
265
264 if self.rhodecode_vcs_repo is None:
266 if self.rhodecode_vcs_repo is None:
265 # unable to fetch this repo as vcs instance, report back to user
267 # unable to fetch this repo as vcs instance, report back to user
266 h.flash(_(
268 h.flash(_(
267 "The repository `%(repo_name)s` cannot be loaded in filesystem. "
269 "The repository `%(repo_name)s` cannot be loaded in filesystem. "
268 "Please check if it exist, or is not damaged.") %
270 "Please check if it exist, or is not damaged.") %
269 {'repo_name': c.repo_name},
271 {'repo_name': c.repo_name},
270 category='error', ignore_duplicate=True)
272 category='error', ignore_duplicate=True)
271 if c.repository_requirements_missing:
273 if c.repository_requirements_missing:
272 route = self.request.matched_route.name
274 route = self.request.matched_route.name
273 if route.startswith(('edit_repo', 'repo_summary')):
275 if route.startswith(('edit_repo', 'repo_summary')):
274 # allow summary and edit repo on missing requirements
276 # allow summary and edit repo on missing requirements
275 return c
277 return c
276
278
277 raise HTTPFound(
279 raise HTTPFound(
278 h.route_path('repo_summary', repo_name=self.db_repo_name))
280 h.route_path('repo_summary', repo_name=self.db_repo_name))
279
281
280 else: # redirect if we don't show missing requirements
282 else: # redirect if we don't show missing requirements
281 raise HTTPFound(h.route_path('home'))
283 raise HTTPFound(h.route_path('home'))
282
284
283 c.has_origin_repo_read_perm = False
285 c.has_origin_repo_read_perm = False
284 if self.db_repo.fork:
286 if self.db_repo.fork:
285 c.has_origin_repo_read_perm = h.HasRepoPermissionAny(
287 c.has_origin_repo_read_perm = h.HasRepoPermissionAny(
286 'repository.write', 'repository.read', 'repository.admin')(
288 'repository.write', 'repository.read', 'repository.admin')(
287 self.db_repo.fork.repo_name, 'summary fork link')
289 self.db_repo.fork.repo_name, 'summary fork link')
288
290
289 return c
291 return c
290
292
291 def _get_f_path_unchecked(self, matchdict, default=None):
293 def _get_f_path_unchecked(self, matchdict, default=None):
292 """
294 """
293 Should only be used by redirects, everything else should call _get_f_path
295 Should only be used by redirects, everything else should call _get_f_path
294 """
296 """
295 f_path = matchdict.get('f_path')
297 f_path = matchdict.get('f_path')
296 if f_path:
298 if f_path:
297 # fix for multiple initial slashes that causes errors for GIT
299 # fix for multiple initial slashes that causes errors for GIT
298 return f_path.lstrip('/')
300 return f_path.lstrip('/')
299
301
300 return default
302 return default
301
303
302 def _get_f_path(self, matchdict, default=None):
304 def _get_f_path(self, matchdict, default=None):
303 f_path_match = self._get_f_path_unchecked(matchdict, default)
305 f_path_match = self._get_f_path_unchecked(matchdict, default)
304 return self.path_filter.assert_path_permissions(f_path_match)
306 return self.path_filter.assert_path_permissions(f_path_match)
305
307
306 def _get_general_setting(self, target_repo, settings_key, default=False):
308 def _get_general_setting(self, target_repo, settings_key, default=False):
307 settings_model = VcsSettingsModel(repo=target_repo)
309 settings_model = VcsSettingsModel(repo=target_repo)
308 settings = settings_model.get_general_settings()
310 settings = settings_model.get_general_settings()
309 return settings.get(settings_key, default)
311 return settings.get(settings_key, default)
310
312
311 def _get_repo_setting(self, target_repo, settings_key, default=False):
313 def _get_repo_setting(self, target_repo, settings_key, default=False):
312 settings_model = VcsSettingsModel(repo=target_repo)
314 settings_model = VcsSettingsModel(repo=target_repo)
313 settings = settings_model.get_repo_settings_inherited()
315 settings = settings_model.get_repo_settings_inherited()
314 return settings.get(settings_key, default)
316 return settings.get(settings_key, default)
315
317
316 def _get_readme_data(self, db_repo, renderer_type, commit_id=None, path='/'):
318 def _get_readme_data(self, db_repo, renderer_type, commit_id=None, path='/'):
317 log.debug('Looking for README file at path %s', path)
319 log.debug('Looking for README file at path %s', path)
318 if commit_id:
320 if commit_id:
319 landing_commit_id = commit_id
321 landing_commit_id = commit_id
320 else:
322 else:
321 landing_commit = db_repo.get_landing_commit()
323 landing_commit = db_repo.get_landing_commit()
322 if isinstance(landing_commit, EmptyCommit):
324 if isinstance(landing_commit, EmptyCommit):
323 return None, None
325 return None, None
324 landing_commit_id = landing_commit.raw_id
326 landing_commit_id = landing_commit.raw_id
325
327
326 cache_namespace_uid = 'cache_repo.{}'.format(db_repo.repo_id)
328 cache_namespace_uid = 'cache_repo.{}'.format(db_repo.repo_id)
327 region = rc_cache.get_or_create_region('cache_repo', cache_namespace_uid)
329 region = rc_cache.get_or_create_region('cache_repo', cache_namespace_uid)
328 start = time.time()
330 start = time.time()
329
331
330 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid)
332 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid)
331 def generate_repo_readme(repo_id, _commit_id, _repo_name, _readme_search_path, _renderer_type):
333 def generate_repo_readme(repo_id, _commit_id, _repo_name, _readme_search_path, _renderer_type):
332 readme_data = None
334 readme_data = None
333 readme_filename = None
335 readme_filename = None
334
336
335 commit = db_repo.get_commit(_commit_id)
337 commit = db_repo.get_commit(_commit_id)
336 log.debug("Searching for a README file at commit %s.", _commit_id)
338 log.debug("Searching for a README file at commit %s.", _commit_id)
337 readme_node = ReadmeFinder(_renderer_type).search(commit, path=_readme_search_path)
339 readme_node = ReadmeFinder(_renderer_type).search(commit, path=_readme_search_path)
338
340
339 if readme_node:
341 if readme_node:
340 log.debug('Found README node: %s', readme_node)
342 log.debug('Found README node: %s', readme_node)
341 relative_urls = {
343 relative_urls = {
342 'raw': h.route_path(
344 'raw': h.route_path(
343 'repo_file_raw', repo_name=_repo_name,
345 'repo_file_raw', repo_name=_repo_name,
344 commit_id=commit.raw_id, f_path=readme_node.path),
346 commit_id=commit.raw_id, f_path=readme_node.path),
345 'standard': h.route_path(
347 'standard': h.route_path(
346 'repo_files', repo_name=_repo_name,
348 'repo_files', repo_name=_repo_name,
347 commit_id=commit.raw_id, f_path=readme_node.path),
349 commit_id=commit.raw_id, f_path=readme_node.path),
348 }
350 }
349 readme_data = self._render_readme_or_none(commit, readme_node, relative_urls)
351 readme_data = self._render_readme_or_none(commit, readme_node, relative_urls)
350 readme_filename = readme_node.unicode_path
352 readme_filename = readme_node.unicode_path
351
353
352 return readme_data, readme_filename
354 return readme_data, readme_filename
353
355
354 readme_data, readme_filename = generate_repo_readme(
356 readme_data, readme_filename = generate_repo_readme(
355 db_repo.repo_id, landing_commit_id, db_repo.repo_name, path, renderer_type,)
357 db_repo.repo_id, landing_commit_id, db_repo.repo_name, path, renderer_type,)
356 compute_time = time.time() - start
358 compute_time = time.time() - start
357 log.debug('Repo README for path %s generated and computed in %.4fs',
359 log.debug('Repo README for path %s generated and computed in %.4fs',
358 path, compute_time)
360 path, compute_time)
359 return readme_data, readme_filename
361 return readme_data, readme_filename
360
362
361 def _render_readme_or_none(self, commit, readme_node, relative_urls):
363 def _render_readme_or_none(self, commit, readme_node, relative_urls):
362 log.debug('Found README file `%s` rendering...', readme_node.path)
364 log.debug('Found README file `%s` rendering...', readme_node.path)
363 renderer = MarkupRenderer()
365 renderer = MarkupRenderer()
364 try:
366 try:
365 html_source = renderer.render(
367 html_source = renderer.render(
366 readme_node.content, filename=readme_node.path)
368 readme_node.content, filename=readme_node.path)
367 if relative_urls:
369 if relative_urls:
368 return relative_links(html_source, relative_urls)
370 return relative_links(html_source, relative_urls)
369 return html_source
371 return html_source
370 except Exception:
372 except Exception:
371 log.exception(
373 log.exception(
372 "Exception while trying to render the README")
374 "Exception while trying to render the README")
373
375
374 def get_recache_flag(self):
376 def get_recache_flag(self):
375 for flag_name in ['force_recache', 'force-recache', 'no-cache']:
377 for flag_name in ['force_recache', 'force-recache', 'no-cache']:
376 flag_val = self.request.GET.get(flag_name)
378 flag_val = self.request.GET.get(flag_name)
377 if str2bool(flag_val):
379 if str2bool(flag_val):
378 return True
380 return True
379 return False
381 return False
380
382
381
383
382 class PathFilter(object):
384 class PathFilter(object):
383
385
384 # Expects and instance of BasePathPermissionChecker or None
386 # Expects and instance of BasePathPermissionChecker or None
385 def __init__(self, permission_checker):
387 def __init__(self, permission_checker):
386 self.permission_checker = permission_checker
388 self.permission_checker = permission_checker
387
389
388 def assert_path_permissions(self, path):
390 def assert_path_permissions(self, path):
389 if self.path_access_allowed(path):
391 if self.path_access_allowed(path):
390 return path
392 return path
391 raise HTTPForbidden()
393 raise HTTPForbidden()
392
394
393 def path_access_allowed(self, path):
395 def path_access_allowed(self, path):
394 log.debug('Checking ACL permissions for PathFilter for `%s`', path)
396 log.debug('Checking ACL permissions for PathFilter for `%s`', path)
395 if self.permission_checker:
397 if self.permission_checker:
396 return path and self.permission_checker.has_access(path)
398 return path and self.permission_checker.has_access(path)
397 return True
399 return True
398
400
399 def filter_patchset(self, patchset):
401 def filter_patchset(self, patchset):
400 if not self.permission_checker or not patchset:
402 if not self.permission_checker or not patchset:
401 return patchset, False
403 return patchset, False
402 had_filtered = False
404 had_filtered = False
403 filtered_patchset = []
405 filtered_patchset = []
404 for patch in patchset:
406 for patch in patchset:
405 filename = patch.get('filename', None)
407 filename = patch.get('filename', None)
406 if not filename or self.permission_checker.has_access(filename):
408 if not filename or self.permission_checker.has_access(filename):
407 filtered_patchset.append(patch)
409 filtered_patchset.append(patch)
408 else:
410 else:
409 had_filtered = True
411 had_filtered = True
410 if had_filtered:
412 if had_filtered:
411 if isinstance(patchset, diffs.LimitedDiffContainer):
413 if isinstance(patchset, diffs.LimitedDiffContainer):
412 filtered_patchset = diffs.LimitedDiffContainer(patchset.diff_limit, patchset.cur_diff_size, filtered_patchset)
414 filtered_patchset = diffs.LimitedDiffContainer(patchset.diff_limit, patchset.cur_diff_size, filtered_patchset)
413 return filtered_patchset, True
415 return filtered_patchset, True
414 else:
416 else:
415 return patchset, False
417 return patchset, False
416
418
417 def render_patchset_filtered(self, diffset, patchset, source_ref=None, target_ref=None):
419 def render_patchset_filtered(self, diffset, patchset, source_ref=None, target_ref=None):
418 filtered_patchset, has_hidden_changes = self.filter_patchset(patchset)
420 filtered_patchset, has_hidden_changes = self.filter_patchset(patchset)
419 result = diffset.render_patchset(
421 result = diffset.render_patchset(
420 filtered_patchset, source_ref=source_ref, target_ref=target_ref)
422 filtered_patchset, source_ref=source_ref, target_ref=target_ref)
421 result.has_hidden_changes = has_hidden_changes
423 result.has_hidden_changes = has_hidden_changes
422 return result
424 return result
423
425
424 def get_raw_patch(self, diff_processor):
426 def get_raw_patch(self, diff_processor):
425 if self.permission_checker is None:
427 if self.permission_checker is None:
426 return diff_processor.as_raw()
428 return diff_processor.as_raw()
427 elif self.permission_checker.has_full_access:
429 elif self.permission_checker.has_full_access:
428 return diff_processor.as_raw()
430 return diff_processor.as_raw()
429 else:
431 else:
430 return '# Repository has user-specific filters, raw patch generation is disabled.'
432 return '# Repository has user-specific filters, raw patch generation is disabled.'
431
433
432 @property
434 @property
433 def is_enabled(self):
435 def is_enabled(self):
434 return self.permission_checker is not None
436 return self.permission_checker is not None
435
437
436
438
437 class RepoGroupAppView(BaseAppView):
439 class RepoGroupAppView(BaseAppView):
438 def __init__(self, context, request):
440 def __init__(self, context, request):
439 super(RepoGroupAppView, self).__init__(context, request)
441 super(RepoGroupAppView, self).__init__(context, request)
440 self.db_repo_group = request.db_repo_group
442 self.db_repo_group = request.db_repo_group
441 self.db_repo_group_name = self.db_repo_group.group_name
443 self.db_repo_group_name = self.db_repo_group.group_name
442
444
443 def _get_local_tmpl_context(self, include_app_defaults=True):
445 def _get_local_tmpl_context(self, include_app_defaults=True):
444 _ = self.request.translate
446 _ = self.request.translate
445 c = super(RepoGroupAppView, self)._get_local_tmpl_context(
447 c = super(RepoGroupAppView, self)._get_local_tmpl_context(
446 include_app_defaults=include_app_defaults)
448 include_app_defaults=include_app_defaults)
447 c.repo_group = self.db_repo_group
449 c.repo_group = self.db_repo_group
448 return c
450 return c
449
451
450 def _revoke_perms_on_yourself(self, form_result):
452 def _revoke_perms_on_yourself(self, form_result):
451 _updates = filter(lambda u: self._rhodecode_user.user_id == int(u[0]),
453 _updates = filter(lambda u: self._rhodecode_user.user_id == int(u[0]),
452 form_result['perm_updates'])
454 form_result['perm_updates'])
453 _additions = filter(lambda u: self._rhodecode_user.user_id == int(u[0]),
455 _additions = filter(lambda u: self._rhodecode_user.user_id == int(u[0]),
454 form_result['perm_additions'])
456 form_result['perm_additions'])
455 _deletions = filter(lambda u: self._rhodecode_user.user_id == int(u[0]),
457 _deletions = filter(lambda u: self._rhodecode_user.user_id == int(u[0]),
456 form_result['perm_deletions'])
458 form_result['perm_deletions'])
457 admin_perm = 'group.admin'
459 admin_perm = 'group.admin'
458 if _updates and _updates[0][1] != admin_perm or \
460 if _updates and _updates[0][1] != admin_perm or \
459 _additions and _additions[0][1] != admin_perm or \
461 _additions and _additions[0][1] != admin_perm or \
460 _deletions and _deletions[0][1] != admin_perm:
462 _deletions and _deletions[0][1] != admin_perm:
461 return True
463 return True
462 return False
464 return False
463
465
464
466
465 class UserGroupAppView(BaseAppView):
467 class UserGroupAppView(BaseAppView):
466 def __init__(self, context, request):
468 def __init__(self, context, request):
467 super(UserGroupAppView, self).__init__(context, request)
469 super(UserGroupAppView, self).__init__(context, request)
468 self.db_user_group = request.db_user_group
470 self.db_user_group = request.db_user_group
469 self.db_user_group_name = self.db_user_group.users_group_name
471 self.db_user_group_name = self.db_user_group.users_group_name
470
472
471
473
472 class UserAppView(BaseAppView):
474 class UserAppView(BaseAppView):
473 def __init__(self, context, request):
475 def __init__(self, context, request):
474 super(UserAppView, self).__init__(context, request)
476 super(UserAppView, self).__init__(context, request)
475 self.db_user = request.db_user
477 self.db_user = request.db_user
476 self.db_user_id = self.db_user.user_id
478 self.db_user_id = self.db_user.user_id
477
479
478 _ = self.request.translate
480 _ = self.request.translate
479 if not request.db_user_supports_default:
481 if not request.db_user_supports_default:
480 if self.db_user.username == User.DEFAULT_USER:
482 if self.db_user.username == User.DEFAULT_USER:
481 h.flash(_("Editing user `{}` is disabled.".format(
483 h.flash(_("Editing user `{}` is disabled.".format(
482 User.DEFAULT_USER)), category='warning')
484 User.DEFAULT_USER)), category='warning')
483 raise HTTPFound(h.route_path('users'))
485 raise HTTPFound(h.route_path('users'))
484
486
485
487
486 class DataGridAppView(object):
488 class DataGridAppView(object):
487 """
489 """
488 Common class to have re-usable grid rendering components
490 Common class to have re-usable grid rendering components
489 """
491 """
490
492
491 def _extract_ordering(self, request, column_map=None):
493 def _extract_ordering(self, request, column_map=None):
492 column_map = column_map or {}
494 column_map = column_map or {}
493 column_index = safe_int(request.GET.get('order[0][column]'))
495 column_index = safe_int(request.GET.get('order[0][column]'))
494 order_dir = request.GET.get(
496 order_dir = request.GET.get(
495 'order[0][dir]', 'desc')
497 'order[0][dir]', 'desc')
496 order_by = request.GET.get(
498 order_by = request.GET.get(
497 'columns[%s][data][sort]' % column_index, 'name_raw')
499 'columns[%s][data][sort]' % column_index, 'name_raw')
498
500
499 # translate datatable to DB columns
501 # translate datatable to DB columns
500 order_by = column_map.get(order_by) or order_by
502 order_by = column_map.get(order_by) or order_by
501
503
502 search_q = request.GET.get('search[value]')
504 search_q = request.GET.get('search[value]')
503 return search_q, order_by, order_dir
505 return search_q, order_by, order_dir
504
506
505 def _extract_chunk(self, request):
507 def _extract_chunk(self, request):
506 start = safe_int(request.GET.get('start'), 0)
508 start = safe_int(request.GET.get('start'), 0)
507 length = safe_int(request.GET.get('length'), 25)
509 length = safe_int(request.GET.get('length'), 25)
508 draw = safe_int(request.GET.get('draw'))
510 draw = safe_int(request.GET.get('draw'))
509 return draw, start, length
511 return draw, start, length
510
512
511 def _get_order_col(self, order_by, model):
513 def _get_order_col(self, order_by, model):
512 if isinstance(order_by, compat.string_types):
514 if isinstance(order_by, compat.string_types):
513 try:
515 try:
514 return operator.attrgetter(order_by)(model)
516 return operator.attrgetter(order_by)(model)
515 except AttributeError:
517 except AttributeError:
516 return None
518 return None
517 else:
519 else:
518 return order_by
520 return order_by
519
521
520
522
521 class BaseReferencesView(RepoAppView):
523 class BaseReferencesView(RepoAppView):
522 """
524 """
523 Base for reference view for branches, tags and bookmarks.
525 Base for reference view for branches, tags and bookmarks.
524 """
526 """
525 def load_default_context(self):
527 def load_default_context(self):
526 c = self._get_local_tmpl_context()
528 c = self._get_local_tmpl_context()
527
529
528
530
529 return c
531 return c
530
532
531 def load_refs_context(self, ref_items, partials_template):
533 def load_refs_context(self, ref_items, partials_template):
532 _render = self.request.get_partial_renderer(partials_template)
534 _render = self.request.get_partial_renderer(partials_template)
533 pre_load = ["author", "date", "message", "parents"]
535 pre_load = ["author", "date", "message", "parents"]
534
536
535 is_svn = h.is_svn(self.rhodecode_vcs_repo)
537 is_svn = h.is_svn(self.rhodecode_vcs_repo)
536 is_hg = h.is_hg(self.rhodecode_vcs_repo)
538 is_hg = h.is_hg(self.rhodecode_vcs_repo)
537
539
538 format_ref_id = get_format_ref_id(self.rhodecode_vcs_repo)
540 format_ref_id = get_format_ref_id(self.rhodecode_vcs_repo)
539
541
540 closed_refs = {}
542 closed_refs = {}
541 if is_hg:
543 if is_hg:
542 closed_refs = self.rhodecode_vcs_repo.branches_closed
544 closed_refs = self.rhodecode_vcs_repo.branches_closed
543
545
544 data = []
546 data = []
545 for ref_name, commit_id in ref_items:
547 for ref_name, commit_id in ref_items:
546 commit = self.rhodecode_vcs_repo.get_commit(
548 commit = self.rhodecode_vcs_repo.get_commit(
547 commit_id=commit_id, pre_load=pre_load)
549 commit_id=commit_id, pre_load=pre_load)
548 closed = ref_name in closed_refs
550 closed = ref_name in closed_refs
549
551
550 # TODO: johbo: Unify generation of reference links
552 # TODO: johbo: Unify generation of reference links
551 use_commit_id = '/' in ref_name or is_svn
553 use_commit_id = '/' in ref_name or is_svn
552
554
553 if use_commit_id:
555 if use_commit_id:
554 files_url = h.route_path(
556 files_url = h.route_path(
555 'repo_files',
557 'repo_files',
556 repo_name=self.db_repo_name,
558 repo_name=self.db_repo_name,
557 f_path=ref_name if is_svn else '',
559 f_path=ref_name if is_svn else '',
558 commit_id=commit_id)
560 commit_id=commit_id)
559
561
560 else:
562 else:
561 files_url = h.route_path(
563 files_url = h.route_path(
562 'repo_files',
564 'repo_files',
563 repo_name=self.db_repo_name,
565 repo_name=self.db_repo_name,
564 f_path=ref_name if is_svn else '',
566 f_path=ref_name if is_svn else '',
565 commit_id=ref_name,
567 commit_id=ref_name,
566 _query=dict(at=ref_name))
568 _query=dict(at=ref_name))
567
569
568 data.append({
570 data.append({
569 "name": _render('name', ref_name, files_url, closed),
571 "name": _render('name', ref_name, files_url, closed),
570 "name_raw": ref_name,
572 "name_raw": ref_name,
571 "date": _render('date', commit.date),
573 "date": _render('date', commit.date),
572 "date_raw": datetime_to_time(commit.date),
574 "date_raw": datetime_to_time(commit.date),
573 "author": _render('author', commit.author),
575 "author": _render('author', commit.author),
574 "commit": _render(
576 "commit": _render(
575 'commit', commit.message, commit.raw_id, commit.idx),
577 'commit', commit.message, commit.raw_id, commit.idx),
576 "commit_raw": commit.idx,
578 "commit_raw": commit.idx,
577 "compare": _render(
579 "compare": _render(
578 'compare', format_ref_id(ref_name, commit.raw_id)),
580 'compare', format_ref_id(ref_name, commit.raw_id)),
579 })
581 })
580
582
581 return data
583 return data
582
584
583
585
584 class RepoRoutePredicate(object):
586 class RepoRoutePredicate(object):
585 def __init__(self, val, config):
587 def __init__(self, val, config):
586 self.val = val
588 self.val = val
587
589
588 def text(self):
590 def text(self):
589 return 'repo_route = %s' % self.val
591 return 'repo_route = %s' % self.val
590
592
591 phash = text
593 phash = text
592
594
593 def __call__(self, info, request):
595 def __call__(self, info, request):
594 if hasattr(request, 'vcs_call'):
596 if hasattr(request, 'vcs_call'):
595 # skip vcs calls
597 # skip vcs calls
596 return
598 return
597
599
598 repo_name = info['match']['repo_name']
600 repo_name = info['match']['repo_name']
599 repo_model = repo.RepoModel()
601 repo_model = repo.RepoModel()
600
602
601 by_name_match = repo_model.get_by_repo_name(repo_name, cache=False)
603 by_name_match = repo_model.get_by_repo_name(repo_name, cache=False)
602
604
603 def redirect_if_creating(route_info, db_repo):
605 def redirect_if_creating(route_info, db_repo):
604 skip_views = ['edit_repo_advanced_delete']
606 skip_views = ['edit_repo_advanced_delete']
605 route = route_info['route']
607 route = route_info['route']
606 # we should skip delete view so we can actually "remove" repositories
608 # we should skip delete view so we can actually "remove" repositories
607 # if they get stuck in creating state.
609 # if they get stuck in creating state.
608 if route.name in skip_views:
610 if route.name in skip_views:
609 return
611 return
610
612
611 if db_repo.repo_state in [repo.Repository.STATE_PENDING]:
613 if db_repo.repo_state in [repo.Repository.STATE_PENDING]:
612 repo_creating_url = request.route_path(
614 repo_creating_url = request.route_path(
613 'repo_creating', repo_name=db_repo.repo_name)
615 'repo_creating', repo_name=db_repo.repo_name)
614 raise HTTPFound(repo_creating_url)
616 raise HTTPFound(repo_creating_url)
615
617
616 if by_name_match:
618 if by_name_match:
617 # register this as request object we can re-use later
619 # register this as request object we can re-use later
618 request.db_repo = by_name_match
620 request.db_repo = by_name_match
619 redirect_if_creating(info, by_name_match)
621 redirect_if_creating(info, by_name_match)
620 return True
622 return True
621
623
622 by_id_match = repo_model.get_repo_by_id(repo_name)
624 by_id_match = repo_model.get_repo_by_id(repo_name)
623 if by_id_match:
625 if by_id_match:
624 request.db_repo = by_id_match
626 request.db_repo = by_id_match
625 redirect_if_creating(info, by_id_match)
627 redirect_if_creating(info, by_id_match)
626 return True
628 return True
627
629
628 return False
630 return False
629
631
630
632
631 class RepoForbidArchivedRoutePredicate(object):
633 class RepoForbidArchivedRoutePredicate(object):
632 def __init__(self, val, config):
634 def __init__(self, val, config):
633 self.val = val
635 self.val = val
634
636
635 def text(self):
637 def text(self):
636 return 'repo_forbid_archived = %s' % self.val
638 return 'repo_forbid_archived = %s' % self.val
637
639
638 phash = text
640 phash = text
639
641
640 def __call__(self, info, request):
642 def __call__(self, info, request):
641 _ = request.translate
643 _ = request.translate
642 rhodecode_db_repo = request.db_repo
644 rhodecode_db_repo = request.db_repo
643
645
644 log.debug(
646 log.debug(
645 '%s checking if archived flag for repo for %s',
647 '%s checking if archived flag for repo for %s',
646 self.__class__.__name__, rhodecode_db_repo.repo_name)
648 self.__class__.__name__, rhodecode_db_repo.repo_name)
647
649
648 if rhodecode_db_repo.archived:
650 if rhodecode_db_repo.archived:
649 log.warning('Current view is not supported for archived repo:%s',
651 log.warning('Current view is not supported for archived repo:%s',
650 rhodecode_db_repo.repo_name)
652 rhodecode_db_repo.repo_name)
651
653
652 h.flash(
654 h.flash(
653 h.literal(_('Action not supported for archived repository.')),
655 h.literal(_('Action not supported for archived repository.')),
654 category='warning')
656 category='warning')
655 summary_url = request.route_path(
657 summary_url = request.route_path(
656 'repo_summary', repo_name=rhodecode_db_repo.repo_name)
658 'repo_summary', repo_name=rhodecode_db_repo.repo_name)
657 raise HTTPFound(summary_url)
659 raise HTTPFound(summary_url)
658 return True
660 return True
659
661
660
662
661 class RepoTypeRoutePredicate(object):
663 class RepoTypeRoutePredicate(object):
662 def __init__(self, val, config):
664 def __init__(self, val, config):
663 self.val = val or ['hg', 'git', 'svn']
665 self.val = val or ['hg', 'git', 'svn']
664
666
665 def text(self):
667 def text(self):
666 return 'repo_accepted_type = %s' % self.val
668 return 'repo_accepted_type = %s' % self.val
667
669
668 phash = text
670 phash = text
669
671
670 def __call__(self, info, request):
672 def __call__(self, info, request):
671 if hasattr(request, 'vcs_call'):
673 if hasattr(request, 'vcs_call'):
672 # skip vcs calls
674 # skip vcs calls
673 return
675 return
674
676
675 rhodecode_db_repo = request.db_repo
677 rhodecode_db_repo = request.db_repo
676
678
677 log.debug(
679 log.debug(
678 '%s checking repo type for %s in %s',
680 '%s checking repo type for %s in %s',
679 self.__class__.__name__, rhodecode_db_repo.repo_type, self.val)
681 self.__class__.__name__, rhodecode_db_repo.repo_type, self.val)
680
682
681 if rhodecode_db_repo.repo_type in self.val:
683 if rhodecode_db_repo.repo_type in self.val:
682 return True
684 return True
683 else:
685 else:
684 log.warning('Current view is not supported for repo type:%s',
686 log.warning('Current view is not supported for repo type:%s',
685 rhodecode_db_repo.repo_type)
687 rhodecode_db_repo.repo_type)
686 return False
688 return False
687
689
688
690
689 class RepoGroupRoutePredicate(object):
691 class RepoGroupRoutePredicate(object):
690 def __init__(self, val, config):
692 def __init__(self, val, config):
691 self.val = val
693 self.val = val
692
694
693 def text(self):
695 def text(self):
694 return 'repo_group_route = %s' % self.val
696 return 'repo_group_route = %s' % self.val
695
697
696 phash = text
698 phash = text
697
699
698 def __call__(self, info, request):
700 def __call__(self, info, request):
699 if hasattr(request, 'vcs_call'):
701 if hasattr(request, 'vcs_call'):
700 # skip vcs calls
702 # skip vcs calls
701 return
703 return
702
704
703 repo_group_name = info['match']['repo_group_name']
705 repo_group_name = info['match']['repo_group_name']
704 repo_group_model = repo_group.RepoGroupModel()
706 repo_group_model = repo_group.RepoGroupModel()
705 by_name_match = repo_group_model.get_by_group_name(repo_group_name, cache=False)
707 by_name_match = repo_group_model.get_by_group_name(repo_group_name, cache=False)
706
708
707 if by_name_match:
709 if by_name_match:
708 # register this as request object we can re-use later
710 # register this as request object we can re-use later
709 request.db_repo_group = by_name_match
711 request.db_repo_group = by_name_match
710 return True
712 return True
711
713
712 return False
714 return False
713
715
714
716
715 class UserGroupRoutePredicate(object):
717 class UserGroupRoutePredicate(object):
716 def __init__(self, val, config):
718 def __init__(self, val, config):
717 self.val = val
719 self.val = val
718
720
719 def text(self):
721 def text(self):
720 return 'user_group_route = %s' % self.val
722 return 'user_group_route = %s' % self.val
721
723
722 phash = text
724 phash = text
723
725
724 def __call__(self, info, request):
726 def __call__(self, info, request):
725 if hasattr(request, 'vcs_call'):
727 if hasattr(request, 'vcs_call'):
726 # skip vcs calls
728 # skip vcs calls
727 return
729 return
728
730
729 user_group_id = info['match']['user_group_id']
731 user_group_id = info['match']['user_group_id']
730 user_group_model = user_group.UserGroup()
732 user_group_model = user_group.UserGroup()
731 by_id_match = user_group_model.get(user_group_id, cache=False)
733 by_id_match = user_group_model.get(user_group_id, cache=False)
732
734
733 if by_id_match:
735 if by_id_match:
734 # register this as request object we can re-use later
736 # register this as request object we can re-use later
735 request.db_user_group = by_id_match
737 request.db_user_group = by_id_match
736 return True
738 return True
737
739
738 return False
740 return False
739
741
740
742
741 class UserRoutePredicateBase(object):
743 class UserRoutePredicateBase(object):
742 supports_default = None
744 supports_default = None
743
745
744 def __init__(self, val, config):
746 def __init__(self, val, config):
745 self.val = val
747 self.val = val
746
748
747 def text(self):
749 def text(self):
748 raise NotImplementedError()
750 raise NotImplementedError()
749
751
750 def __call__(self, info, request):
752 def __call__(self, info, request):
751 if hasattr(request, 'vcs_call'):
753 if hasattr(request, 'vcs_call'):
752 # skip vcs calls
754 # skip vcs calls
753 return
755 return
754
756
755 user_id = info['match']['user_id']
757 user_id = info['match']['user_id']
756 user_model = user.User()
758 user_model = user.User()
757 by_id_match = user_model.get(user_id, cache=False)
759 by_id_match = user_model.get(user_id, cache=False)
758
760
759 if by_id_match:
761 if by_id_match:
760 # register this as request object we can re-use later
762 # register this as request object we can re-use later
761 request.db_user = by_id_match
763 request.db_user = by_id_match
762 request.db_user_supports_default = self.supports_default
764 request.db_user_supports_default = self.supports_default
763 return True
765 return True
764
766
765 return False
767 return False
766
768
767
769
768 class UserRoutePredicate(UserRoutePredicateBase):
770 class UserRoutePredicate(UserRoutePredicateBase):
769 supports_default = False
771 supports_default = False
770
772
771 def text(self):
773 def text(self):
772 return 'user_route = %s' % self.val
774 return 'user_route = %s' % self.val
773
775
774 phash = text
776 phash = text
775
777
776
778
777 class UserRouteWithDefaultPredicate(UserRoutePredicateBase):
779 class UserRouteWithDefaultPredicate(UserRoutePredicateBase):
778 supports_default = True
780 supports_default = True
779
781
780 def text(self):
782 def text(self):
781 return 'user_with_default_route = %s' % self.val
783 return 'user_with_default_route = %s' % self.val
782
784
783 phash = text
785 phash = text
784
786
785
787
786 def includeme(config):
788 def includeme(config):
787 config.add_route_predicate(
789 config.add_route_predicate(
788 'repo_route', RepoRoutePredicate)
790 'repo_route', RepoRoutePredicate)
789 config.add_route_predicate(
791 config.add_route_predicate(
790 'repo_accepted_types', RepoTypeRoutePredicate)
792 'repo_accepted_types', RepoTypeRoutePredicate)
791 config.add_route_predicate(
793 config.add_route_predicate(
792 'repo_forbid_when_archived', RepoForbidArchivedRoutePredicate)
794 'repo_forbid_when_archived', RepoForbidArchivedRoutePredicate)
793 config.add_route_predicate(
795 config.add_route_predicate(
794 'repo_group_route', RepoGroupRoutePredicate)
796 'repo_group_route', RepoGroupRoutePredicate)
795 config.add_route_predicate(
797 config.add_route_predicate(
796 'user_group_route', UserGroupRoutePredicate)
798 'user_group_route', UserGroupRoutePredicate)
797 config.add_route_predicate(
799 config.add_route_predicate(
798 'user_route_with_default', UserRouteWithDefaultPredicate)
800 'user_route_with_default', UserRouteWithDefaultPredicate)
799 config.add_route_predicate(
801 config.add_route_predicate(
800 'user_route', UserRoutePredicate)
802 'user_route', UserRoutePredicate)
@@ -1,464 +1,465 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2012-2019 RhodeCode GmbH
3 # Copyright (C) 2012-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 deform
21 import deform
22 import logging
22 import logging
23 import peppercorn
23 import peppercorn
24 import webhelpers.paginate
24 import webhelpers.paginate
25
25
26 from pyramid.httpexceptions import HTTPFound, HTTPForbidden, HTTPNotFound
26 from pyramid.httpexceptions import HTTPFound, HTTPForbidden, HTTPNotFound
27
27
28 from rhodecode.integrations import integration_type_registry
28 from rhodecode.integrations import integration_type_registry
29 from rhodecode.apps._base import BaseAppView
29 from rhodecode.apps._base import BaseAppView
30 from rhodecode.apps._base.navigation import navigation_list
30 from rhodecode.apps._base.navigation import navigation_list
31 from rhodecode.lib.auth import (
31 from rhodecode.lib.auth import (
32 LoginRequired, CSRFRequired, HasPermissionAnyDecorator,
32 LoginRequired, CSRFRequired, HasPermissionAnyDecorator,
33 HasRepoPermissionAnyDecorator, HasRepoGroupPermissionAnyDecorator)
33 HasRepoPermissionAnyDecorator, HasRepoGroupPermissionAnyDecorator)
34 from rhodecode.lib.utils2 import safe_int
34 from rhodecode.lib.utils2 import safe_int
35 from rhodecode.lib import helpers as h
35 from rhodecode.lib import helpers as h
36 from rhodecode.model.db import Repository, RepoGroup, Session, Integration
36 from rhodecode.model.db import Repository, RepoGroup, Session, Integration
37 from rhodecode.model.scm import ScmModel
37 from rhodecode.model.scm import ScmModel
38 from rhodecode.model.integration import IntegrationModel
38 from rhodecode.model.integration import IntegrationModel
39 from rhodecode.model.validation_schema.schemas.integration_schema import (
39 from rhodecode.model.validation_schema.schemas.integration_schema import (
40 make_integration_schema, IntegrationScopeType)
40 make_integration_schema, IntegrationScopeType)
41
41
42 log = logging.getLogger(__name__)
42 log = logging.getLogger(__name__)
43
43
44
44
45 class IntegrationSettingsViewBase(BaseAppView):
45 class IntegrationSettingsViewBase(BaseAppView):
46 """
46 """
47 Base Integration settings view used by both repo / global settings
47 Base Integration settings view used by both repo / global settings
48 """
48 """
49
49
50 def __init__(self, context, request):
50 def __init__(self, context, request):
51 super(IntegrationSettingsViewBase, self).__init__(context, request)
51 super(IntegrationSettingsViewBase, self).__init__(context, request)
52 self._load_view_context()
52 self._load_view_context()
53
53
54 def _load_view_context(self):
54 def _load_view_context(self):
55 """
55 """
56 This avoids boilerplate for repo/global+list/edit+views/templates
56 This avoids boilerplate for repo/global+list/edit+views/templates
57 by doing all possible contexts at the same time however it should
57 by doing all possible contexts at the same time however it should
58 be split up into separate functions once more "contexts" exist
58 be split up into separate functions once more "contexts" exist
59 """
59 """
60
60
61 self.IntegrationType = None
61 self.IntegrationType = None
62 self.repo = None
62 self.repo = None
63 self.repo_group = None
63 self.repo_group = None
64 self.integration = None
64 self.integration = None
65 self.integrations = {}
65 self.integrations = {}
66
66
67 request = self.request
67 request = self.request
68
68
69 if 'repo_name' in request.matchdict: # in repo settings context
69 if 'repo_name' in request.matchdict: # in repo settings context
70 repo_name = request.matchdict['repo_name']
70 repo_name = request.matchdict['repo_name']
71 self.repo = Repository.get_by_repo_name(repo_name)
71 self.repo = Repository.get_by_repo_name(repo_name)
72
72
73 if 'repo_group_name' in request.matchdict: # in group settings context
73 if 'repo_group_name' in request.matchdict: # in group settings context
74 repo_group_name = request.matchdict['repo_group_name']
74 repo_group_name = request.matchdict['repo_group_name']
75 self.repo_group = RepoGroup.get_by_group_name(repo_group_name)
75 self.repo_group = RepoGroup.get_by_group_name(repo_group_name)
76
76
77 if 'integration' in request.matchdict: # integration type context
77 if 'integration' in request.matchdict: # integration type context
78 integration_type = request.matchdict['integration']
78 integration_type = request.matchdict['integration']
79 if integration_type not in integration_type_registry:
79 if integration_type not in integration_type_registry:
80 raise HTTPNotFound()
80 raise HTTPNotFound()
81
81
82 self.IntegrationType = integration_type_registry[integration_type]
82 self.IntegrationType = integration_type_registry[integration_type]
83 if self.IntegrationType.is_dummy:
83 if self.IntegrationType.is_dummy:
84 raise HTTPNotFound()
84 raise HTTPNotFound()
85
85
86 if 'integration_id' in request.matchdict: # single integration context
86 if 'integration_id' in request.matchdict: # single integration context
87 integration_id = request.matchdict['integration_id']
87 integration_id = request.matchdict['integration_id']
88 self.integration = Integration.get(integration_id)
88 self.integration = Integration.get(integration_id)
89
89
90 # extra perms check just in case
90 # extra perms check just in case
91 if not self._has_perms_for_integration(self.integration):
91 if not self._has_perms_for_integration(self.integration):
92 raise HTTPForbidden()
92 raise HTTPForbidden()
93
93
94 self.settings = self.integration and self.integration.settings or {}
94 self.settings = self.integration and self.integration.settings or {}
95 self.admin_view = not (self.repo or self.repo_group)
95 self.admin_view = not (self.repo or self.repo_group)
96
96
97 def _has_perms_for_integration(self, integration):
97 def _has_perms_for_integration(self, integration):
98 perms = self.request.user.permissions
98 perms = self.request.user.permissions
99
99
100 if 'hg.admin' in perms['global']:
100 if 'hg.admin' in perms['global']:
101 return True
101 return True
102
102
103 if integration.repo:
103 if integration.repo:
104 return perms['repositories'].get(
104 return perms['repositories'].get(
105 integration.repo.repo_name) == 'repository.admin'
105 integration.repo.repo_name) == 'repository.admin'
106
106
107 if integration.repo_group:
107 if integration.repo_group:
108 return perms['repositories_groups'].get(
108 return perms['repositories_groups'].get(
109 integration.repo_group.group_name) == 'group.admin'
109 integration.repo_group.group_name) == 'group.admin'
110
110
111 return False
111 return False
112
112
113 def _get_local_tmpl_context(self, include_app_defaults=True):
113 def _get_local_tmpl_context(self, include_app_defaults=True):
114 _ = self.request.translate
114 _ = self.request.translate
115 c = super(IntegrationSettingsViewBase, self)._get_local_tmpl_context(
115 c = super(IntegrationSettingsViewBase, self)._get_local_tmpl_context(
116 include_app_defaults=include_app_defaults)
116 include_app_defaults=include_app_defaults)
117 c.active = 'integrations'
117 c.active = 'integrations'
118
118
119 return c
119 return c
120
120
121 def _form_schema(self):
121 def _form_schema(self):
122 schema = make_integration_schema(IntegrationType=self.IntegrationType,
122 schema = make_integration_schema(IntegrationType=self.IntegrationType,
123 settings=self.settings)
123 settings=self.settings)
124
124
125 # returns a clone, important if mutating the schema later
125 # returns a clone, important if mutating the schema later
126 return schema.bind(
126 return schema.bind(
127 permissions=self.request.user.permissions,
127 permissions=self.request.user.permissions,
128 no_scope=not self.admin_view)
128 no_scope=not self.admin_view)
129
129
130 def _form_defaults(self):
130 def _form_defaults(self):
131 _ = self.request.translate
131 _ = self.request.translate
132 defaults = {}
132 defaults = {}
133
133
134 if self.integration:
134 if self.integration:
135 defaults['settings'] = self.integration.settings or {}
135 defaults['settings'] = self.integration.settings or {}
136 defaults['options'] = {
136 defaults['options'] = {
137 'name': self.integration.name,
137 'name': self.integration.name,
138 'enabled': self.integration.enabled,
138 'enabled': self.integration.enabled,
139 'scope': {
139 'scope': {
140 'repo': self.integration.repo,
140 'repo': self.integration.repo,
141 'repo_group': self.integration.repo_group,
141 'repo_group': self.integration.repo_group,
142 'child_repos_only': self.integration.child_repos_only,
142 'child_repos_only': self.integration.child_repos_only,
143 },
143 },
144 }
144 }
145 else:
145 else:
146 if self.repo:
146 if self.repo:
147 scope = _('{repo_name} repository').format(
147 scope = _('{repo_name} repository').format(
148 repo_name=self.repo.repo_name)
148 repo_name=self.repo.repo_name)
149 elif self.repo_group:
149 elif self.repo_group:
150 scope = _('{repo_group_name} repo group').format(
150 scope = _('{repo_group_name} repo group').format(
151 repo_group_name=self.repo_group.group_name)
151 repo_group_name=self.repo_group.group_name)
152 else:
152 else:
153 scope = _('Global')
153 scope = _('Global')
154
154
155 defaults['options'] = {
155 defaults['options'] = {
156 'enabled': True,
156 'enabled': True,
157 'name': _('{name} integration').format(
157 'name': _('{name} integration').format(
158 name=self.IntegrationType.display_name),
158 name=self.IntegrationType.display_name),
159 }
159 }
160 defaults['options']['scope'] = {
160 defaults['options']['scope'] = {
161 'repo': self.repo,
161 'repo': self.repo,
162 'repo_group': self.repo_group,
162 'repo_group': self.repo_group,
163 }
163 }
164
164
165 return defaults
165 return defaults
166
166
167 def _delete_integration(self, integration):
167 def _delete_integration(self, integration):
168 _ = self.request.translate
168 _ = self.request.translate
169 Session().delete(integration)
169 Session().delete(integration)
170 Session().commit()
170 Session().commit()
171 h.flash(
171 h.flash(
172 _('Integration {integration_name} deleted successfully.').format(
172 _('Integration {integration_name} deleted successfully.').format(
173 integration_name=integration.name),
173 integration_name=integration.name),
174 category='success')
174 category='success')
175
175
176 if self.repo:
176 if self.repo:
177 redirect_to = self.request.route_path(
177 redirect_to = self.request.route_path(
178 'repo_integrations_home', repo_name=self.repo.repo_name)
178 'repo_integrations_home', repo_name=self.repo.repo_name)
179 elif self.repo_group:
179 elif self.repo_group:
180 redirect_to = self.request.route_path(
180 redirect_to = self.request.route_path(
181 'repo_group_integrations_home',
181 'repo_group_integrations_home',
182 repo_group_name=self.repo_group.group_name)
182 repo_group_name=self.repo_group.group_name)
183 else:
183 else:
184 redirect_to = self.request.route_path('global_integrations_home')
184 redirect_to = self.request.route_path('global_integrations_home')
185 raise HTTPFound(redirect_to)
185 raise HTTPFound(redirect_to)
186
186
187 def _integration_list(self):
187 def _integration_list(self):
188 """ List integrations """
188 """ List integrations """
189
189
190 c = self.load_default_context()
190 c = self.load_default_context()
191 if self.repo:
191 if self.repo:
192 scope = self.repo
192 scope = self.repo
193 elif self.repo_group:
193 elif self.repo_group:
194 scope = self.repo_group
194 scope = self.repo_group
195 else:
195 else:
196 scope = 'all'
196 scope = 'all'
197
197
198 integrations = []
198 integrations = []
199
199
200 for IntType, integration in IntegrationModel().get_integrations(
200 for IntType, integration in IntegrationModel().get_integrations(
201 scope=scope, IntegrationType=self.IntegrationType):
201 scope=scope, IntegrationType=self.IntegrationType):
202
202
203 # extra permissions check *just in case*
203 # extra permissions check *just in case*
204 if not self._has_perms_for_integration(integration):
204 if not self._has_perms_for_integration(integration):
205 continue
205 continue
206
206
207 integrations.append((IntType, integration))
207 integrations.append((IntType, integration))
208
208
209 sort_arg = self.request.GET.get('sort', 'name:asc')
209 sort_arg = self.request.GET.get('sort', 'name:asc')
210 sort_dir = 'asc'
210 sort_dir = 'asc'
211 if ':' in sort_arg:
211 if ':' in sort_arg:
212 sort_field, sort_dir = sort_arg.split(':')
212 sort_field, sort_dir = sort_arg.split(':')
213 else:
213 else:
214 sort_field = sort_arg, 'asc'
214 sort_field = sort_arg, 'asc'
215
215
216 assert sort_field in ('name', 'integration_type', 'enabled', 'scope')
216 assert sort_field in ('name', 'integration_type', 'enabled', 'scope')
217
217
218 integrations.sort(
218 integrations.sort(
219 key=lambda x: getattr(x[1], sort_field),
219 key=lambda x: getattr(x[1], sort_field),
220 reverse=(sort_dir == 'desc'))
220 reverse=(sort_dir == 'desc'))
221
221
222 page_url = webhelpers.paginate.PageURL(
222 page_url = webhelpers.paginate.PageURL(
223 self.request.path, self.request.GET)
223 self.request.path, self.request.GET)
224 page = safe_int(self.request.GET.get('page', 1), 1)
224 page = safe_int(self.request.GET.get('page', 1), 1)
225
225
226 integrations = h.Page(
226 integrations = h.Page(
227 integrations, page=page, items_per_page=10, url=page_url)
227 integrations, page=page, items_per_page=10, url=page_url)
228
228
229 c.rev_sort_dir = sort_dir != 'desc' and 'desc' or 'asc'
229 c.rev_sort_dir = sort_dir != 'desc' and 'desc' or 'asc'
230
230
231 c.current_IntegrationType = self.IntegrationType
231 c.current_IntegrationType = self.IntegrationType
232 c.integrations_list = integrations
232 c.integrations_list = integrations
233 c.available_integrations = integration_type_registry
233 c.available_integrations = integration_type_registry
234
234
235 return self._get_template_context(c)
235 return self._get_template_context(c)
236
236
237 def _settings_get(self, defaults=None, form=None):
237 def _settings_get(self, defaults=None, form=None):
238 """
238 """
239 View that displays the integration settings as a form.
239 View that displays the integration settings as a form.
240 """
240 """
241 c = self.load_default_context()
241 c = self.load_default_context()
242
242
243 defaults = defaults or self._form_defaults()
243 defaults = defaults or self._form_defaults()
244 schema = self._form_schema()
244 schema = self._form_schema()
245
245
246 if self.integration:
246 if self.integration:
247 buttons = ('submit', 'delete')
247 buttons = ('submit', 'delete')
248 else:
248 else:
249 buttons = ('submit',)
249 buttons = ('submit',)
250
250
251 form = form or deform.Form(schema, appstruct=defaults, buttons=buttons)
251 form = form or deform.Form(schema, appstruct=defaults, buttons=buttons)
252
252
253 c.form = form
253 c.form = form
254 c.current_IntegrationType = self.IntegrationType
254 c.current_IntegrationType = self.IntegrationType
255 c.integration = self.integration
255 c.integration = self.integration
256
256
257 return self._get_template_context(c)
257 return self._get_template_context(c)
258
258
259 def _settings_post(self):
259 def _settings_post(self):
260 """
260 """
261 View that validates and stores the integration settings.
261 View that validates and stores the integration settings.
262 """
262 """
263 _ = self.request.translate
263 _ = self.request.translate
264
264
265 controls = self.request.POST.items()
265 controls = self.request.POST.items()
266 pstruct = peppercorn.parse(controls)
266 pstruct = peppercorn.parse(controls)
267
267
268 if self.integration and pstruct.get('delete'):
268 if self.integration and pstruct.get('delete'):
269 return self._delete_integration(self.integration)
269 return self._delete_integration(self.integration)
270
270
271 schema = self._form_schema()
271 schema = self._form_schema()
272
272
273 skip_settings_validation = False
273 skip_settings_validation = False
274 if self.integration and 'enabled' not in pstruct.get('options', {}):
274 if self.integration and 'enabled' not in pstruct.get('options', {}):
275 skip_settings_validation = True
275 skip_settings_validation = True
276 schema['settings'].validator = None
276 schema['settings'].validator = None
277 for field in schema['settings'].children:
277 for field in schema['settings'].children:
278 field.validator = None
278 field.validator = None
279 field.missing = ''
279 field.missing = ''
280
280
281 if self.integration:
281 if self.integration:
282 buttons = ('submit', 'delete')
282 buttons = ('submit', 'delete')
283 else:
283 else:
284 buttons = ('submit',)
284 buttons = ('submit',)
285
285
286 form = deform.Form(schema, buttons=buttons)
286 form = deform.Form(schema, buttons=buttons)
287
287
288 if not self.admin_view:
288 if not self.admin_view:
289 # scope is read only field in these cases, and has to be added
289 # scope is read only field in these cases, and has to be added
290 options = pstruct.setdefault('options', {})
290 options = pstruct.setdefault('options', {})
291 if 'scope' not in options:
291 if 'scope' not in options:
292 options['scope'] = IntegrationScopeType().serialize(None, {
292 options['scope'] = IntegrationScopeType().serialize(None, {
293 'repo': self.repo,
293 'repo': self.repo,
294 'repo_group': self.repo_group,
294 'repo_group': self.repo_group,
295 })
295 })
296
296
297 try:
297 try:
298 valid_data = form.validate_pstruct(pstruct)
298 valid_data = form.validate_pstruct(pstruct)
299 except deform.ValidationFailure as e:
299 except deform.ValidationFailure as e:
300 h.flash(
300 h.flash(
301 _('Errors exist when saving integration settings. '
301 _('Errors exist when saving integration settings. '
302 'Please check the form inputs.'),
302 'Please check the form inputs.'),
303 category='error')
303 category='error')
304 return self._settings_get(form=e)
304 return self._settings_get(form=e)
305
305
306 if not self.integration:
306 if not self.integration:
307 self.integration = Integration()
307 self.integration = Integration()
308 self.integration.integration_type = self.IntegrationType.key
308 self.integration.integration_type = self.IntegrationType.key
309 Session().add(self.integration)
309 Session().add(self.integration)
310
310
311 scope = valid_data['options']['scope']
311 scope = valid_data['options']['scope']
312
312
313 IntegrationModel().update_integration(self.integration,
313 IntegrationModel().update_integration(self.integration,
314 name=valid_data['options']['name'],
314 name=valid_data['options']['name'],
315 enabled=valid_data['options']['enabled'],
315 enabled=valid_data['options']['enabled'],
316 settings=valid_data['settings'],
316 settings=valid_data['settings'],
317 repo=scope['repo'],
317 repo=scope['repo'],
318 repo_group=scope['repo_group'],
318 repo_group=scope['repo_group'],
319 child_repos_only=scope['child_repos_only'],
319 child_repos_only=scope['child_repos_only'],
320 )
320 )
321
321
322 self.integration.settings = valid_data['settings']
322 self.integration.settings = valid_data['settings']
323 Session().commit()
323 Session().commit()
324 # Display success message and redirect.
324 # Display success message and redirect.
325 h.flash(
325 h.flash(
326 _('Integration {integration_name} updated successfully.').format(
326 _('Integration {integration_name} updated successfully.').format(
327 integration_name=self.IntegrationType.display_name),
327 integration_name=self.IntegrationType.display_name),
328 category='success')
328 category='success')
329
329
330 # if integration scope changes, we must redirect to the right place
330 # if integration scope changes, we must redirect to the right place
331 # keeping in mind if the original view was for /repo/ or /_admin/
331 # keeping in mind if the original view was for /repo/ or /_admin/
332 admin_view = not (self.repo or self.repo_group)
332 admin_view = not (self.repo or self.repo_group)
333
333
334 if self.integration.repo and not admin_view:
334 if self.integration.repo and not admin_view:
335 redirect_to = self.request.route_path(
335 redirect_to = self.request.route_path(
336 'repo_integrations_edit',
336 'repo_integrations_edit',
337 repo_name=self.integration.repo.repo_name,
337 repo_name=self.integration.repo.repo_name,
338 integration=self.integration.integration_type,
338 integration=self.integration.integration_type,
339 integration_id=self.integration.integration_id)
339 integration_id=self.integration.integration_id)
340 elif self.integration.repo_group and not admin_view:
340 elif self.integration.repo_group and not admin_view:
341 redirect_to = self.request.route_path(
341 redirect_to = self.request.route_path(
342 'repo_group_integrations_edit',
342 'repo_group_integrations_edit',
343 repo_group_name=self.integration.repo_group.group_name,
343 repo_group_name=self.integration.repo_group.group_name,
344 integration=self.integration.integration_type,
344 integration=self.integration.integration_type,
345 integration_id=self.integration.integration_id)
345 integration_id=self.integration.integration_id)
346 else:
346 else:
347 redirect_to = self.request.route_path(
347 redirect_to = self.request.route_path(
348 'global_integrations_edit',
348 'global_integrations_edit',
349 integration=self.integration.integration_type,
349 integration=self.integration.integration_type,
350 integration_id=self.integration.integration_id)
350 integration_id=self.integration.integration_id)
351
351
352 return HTTPFound(redirect_to)
352 return HTTPFound(redirect_to)
353
353
354 def _new_integration(self):
354 def _new_integration(self):
355 c = self.load_default_context()
355 c = self.load_default_context()
356 c.available_integrations = integration_type_registry
356 c.available_integrations = integration_type_registry
357 return self._get_template_context(c)
357 return self._get_template_context(c)
358
358
359 def load_default_context(self):
359 def load_default_context(self):
360 raise NotImplementedError()
360 raise NotImplementedError()
361
361
362
362
363 class GlobalIntegrationsView(IntegrationSettingsViewBase):
363 class GlobalIntegrationsView(IntegrationSettingsViewBase):
364 def load_default_context(self):
364 def load_default_context(self):
365 c = self._get_local_tmpl_context()
365 c = self._get_local_tmpl_context()
366 c.repo = self.repo
366 c.repo = self.repo
367 c.repo_group = self.repo_group
367 c.repo_group = self.repo_group
368 c.navlist = navigation_list(self.request)
368 c.navlist = navigation_list(self.request)
369
369
370 return c
370 return c
371
371
372 @LoginRequired()
372 @LoginRequired()
373 @HasPermissionAnyDecorator('hg.admin')
373 @HasPermissionAnyDecorator('hg.admin')
374 def integration_list(self):
374 def integration_list(self):
375 return self._integration_list()
375 return self._integration_list()
376
376
377 @LoginRequired()
377 @LoginRequired()
378 @HasPermissionAnyDecorator('hg.admin')
378 @HasPermissionAnyDecorator('hg.admin')
379 def settings_get(self):
379 def settings_get(self):
380 return self._settings_get()
380 return self._settings_get()
381
381
382 @LoginRequired()
382 @LoginRequired()
383 @HasPermissionAnyDecorator('hg.admin')
383 @HasPermissionAnyDecorator('hg.admin')
384 @CSRFRequired()
384 @CSRFRequired()
385 def settings_post(self):
385 def settings_post(self):
386 return self._settings_post()
386 return self._settings_post()
387
387
388 @LoginRequired()
388 @LoginRequired()
389 @HasPermissionAnyDecorator('hg.admin')
389 @HasPermissionAnyDecorator('hg.admin')
390 def new_integration(self):
390 def new_integration(self):
391 return self._new_integration()
391 return self._new_integration()
392
392
393
393
394 class RepoIntegrationsView(IntegrationSettingsViewBase):
394 class RepoIntegrationsView(IntegrationSettingsViewBase):
395 def load_default_context(self):
395 def load_default_context(self):
396 c = self._get_local_tmpl_context()
396 c = self._get_local_tmpl_context()
397
397
398 c.repo = self.repo
398 c.repo = self.repo
399 c.repo_group = self.repo_group
399 c.repo_group = self.repo_group
400
400
401 self.db_repo = self.repo
401 self.db_repo = self.repo
402 c.rhodecode_db_repo = self.repo
402 c.rhodecode_db_repo = self.repo
403 c.repo_name = self.db_repo.repo_name
403 c.repo_name = self.db_repo.repo_name
404 c.repository_pull_requests = ScmModel().get_pull_requests(self.repo)
404 c.repository_pull_requests = ScmModel().get_pull_requests(self.repo)
405 c.repository_artifacts = ScmModel().get_artifacts(self.repo)
405 c.repository_is_user_following = ScmModel().is_following_repo(
406 c.repository_is_user_following = ScmModel().is_following_repo(
406 c.repo_name, self._rhodecode_user.user_id)
407 c.repo_name, self._rhodecode_user.user_id)
407 c.has_origin_repo_read_perm = False
408 c.has_origin_repo_read_perm = False
408 if self.db_repo.fork:
409 if self.db_repo.fork:
409 c.has_origin_repo_read_perm = h.HasRepoPermissionAny(
410 c.has_origin_repo_read_perm = h.HasRepoPermissionAny(
410 'repository.write', 'repository.read', 'repository.admin')(
411 'repository.write', 'repository.read', 'repository.admin')(
411 self.db_repo.fork.repo_name, 'summary fork link')
412 self.db_repo.fork.repo_name, 'summary fork link')
412 return c
413 return c
413
414
414 @LoginRequired()
415 @LoginRequired()
415 @HasRepoPermissionAnyDecorator('repository.admin')
416 @HasRepoPermissionAnyDecorator('repository.admin')
416 def integration_list(self):
417 def integration_list(self):
417 return self._integration_list()
418 return self._integration_list()
418
419
419 @LoginRequired()
420 @LoginRequired()
420 @HasRepoPermissionAnyDecorator('repository.admin')
421 @HasRepoPermissionAnyDecorator('repository.admin')
421 def settings_get(self):
422 def settings_get(self):
422 return self._settings_get()
423 return self._settings_get()
423
424
424 @LoginRequired()
425 @LoginRequired()
425 @HasRepoPermissionAnyDecorator('repository.admin')
426 @HasRepoPermissionAnyDecorator('repository.admin')
426 @CSRFRequired()
427 @CSRFRequired()
427 def settings_post(self):
428 def settings_post(self):
428 return self._settings_post()
429 return self._settings_post()
429
430
430 @LoginRequired()
431 @LoginRequired()
431 @HasRepoPermissionAnyDecorator('repository.admin')
432 @HasRepoPermissionAnyDecorator('repository.admin')
432 def new_integration(self):
433 def new_integration(self):
433 return self._new_integration()
434 return self._new_integration()
434
435
435
436
436 class RepoGroupIntegrationsView(IntegrationSettingsViewBase):
437 class RepoGroupIntegrationsView(IntegrationSettingsViewBase):
437 def load_default_context(self):
438 def load_default_context(self):
438 c = self._get_local_tmpl_context()
439 c = self._get_local_tmpl_context()
439 c.repo = self.repo
440 c.repo = self.repo
440 c.repo_group = self.repo_group
441 c.repo_group = self.repo_group
441 c.navlist = navigation_list(self.request)
442 c.navlist = navigation_list(self.request)
442
443
443 return c
444 return c
444
445
445 @LoginRequired()
446 @LoginRequired()
446 @HasRepoGroupPermissionAnyDecorator('group.admin')
447 @HasRepoGroupPermissionAnyDecorator('group.admin')
447 def integration_list(self):
448 def integration_list(self):
448 return self._integration_list()
449 return self._integration_list()
449
450
450 @LoginRequired()
451 @LoginRequired()
451 @HasRepoGroupPermissionAnyDecorator('group.admin')
452 @HasRepoGroupPermissionAnyDecorator('group.admin')
452 def settings_get(self):
453 def settings_get(self):
453 return self._settings_get()
454 return self._settings_get()
454
455
455 @LoginRequired()
456 @LoginRequired()
456 @HasRepoGroupPermissionAnyDecorator('group.admin')
457 @HasRepoGroupPermissionAnyDecorator('group.admin')
457 @CSRFRequired()
458 @CSRFRequired()
458 def settings_post(self):
459 def settings_post(self):
459 return self._settings_post()
460 return self._settings_post()
460
461
461 @LoginRequired()
462 @LoginRequired()
462 @HasRepoGroupPermissionAnyDecorator('group.admin')
463 @HasRepoGroupPermissionAnyDecorator('group.admin')
463 def new_integration(self):
464 def new_integration(self):
464 return self._new_integration()
465 return self._new_integration()
@@ -1,1014 +1,1021 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2019 RhodeCode GmbH
3 # Copyright (C) 2010-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 """
21 """
22 Scm model for RhodeCode
22 Scm model for RhodeCode
23 """
23 """
24
24
25 import os.path
25 import os.path
26 import traceback
26 import traceback
27 import logging
27 import logging
28 import cStringIO
28 import cStringIO
29
29
30 from sqlalchemy import func
30 from sqlalchemy import func
31 from zope.cachedescriptors.property import Lazy as LazyProperty
31 from zope.cachedescriptors.property import Lazy as LazyProperty
32
32
33 import rhodecode
33 import rhodecode
34 from rhodecode.lib.vcs import get_backend
34 from rhodecode.lib.vcs import get_backend
35 from rhodecode.lib.vcs.exceptions import RepositoryError, NodeNotChangedError
35 from rhodecode.lib.vcs.exceptions import RepositoryError, NodeNotChangedError
36 from rhodecode.lib.vcs.nodes import FileNode
36 from rhodecode.lib.vcs.nodes import FileNode
37 from rhodecode.lib.vcs.backends.base import EmptyCommit
37 from rhodecode.lib.vcs.backends.base import EmptyCommit
38 from rhodecode.lib import helpers as h, rc_cache
38 from rhodecode.lib import helpers as h, rc_cache
39 from rhodecode.lib.auth import (
39 from rhodecode.lib.auth import (
40 HasRepoPermissionAny, HasRepoGroupPermissionAny,
40 HasRepoPermissionAny, HasRepoGroupPermissionAny,
41 HasUserGroupPermissionAny)
41 HasUserGroupPermissionAny)
42 from rhodecode.lib.exceptions import NonRelativePathError, IMCCommitError
42 from rhodecode.lib.exceptions import NonRelativePathError, IMCCommitError
43 from rhodecode.lib import hooks_utils
43 from rhodecode.lib import hooks_utils
44 from rhodecode.lib.utils import (
44 from rhodecode.lib.utils import (
45 get_filesystem_repos, make_db_config)
45 get_filesystem_repos, make_db_config)
46 from rhodecode.lib.utils2 import (safe_str, safe_unicode)
46 from rhodecode.lib.utils2 import (safe_str, safe_unicode)
47 from rhodecode.lib.system_info import get_system_info
47 from rhodecode.lib.system_info import get_system_info
48 from rhodecode.model import BaseModel
48 from rhodecode.model import BaseModel
49 from rhodecode.model.db import (
49 from rhodecode.model.db import (
50 or_, false,
50 Repository, CacheKey, UserFollowing, UserLog, User, RepoGroup,
51 Repository, CacheKey, UserFollowing, UserLog, User, RepoGroup,
51 PullRequest)
52 PullRequest, FileStore)
52 from rhodecode.model.settings import VcsSettingsModel
53 from rhodecode.model.settings import VcsSettingsModel
53 from rhodecode.model.validation_schema.validators import url_validator, InvalidCloneUrl
54 from rhodecode.model.validation_schema.validators import url_validator, InvalidCloneUrl
54
55
55 log = logging.getLogger(__name__)
56 log = logging.getLogger(__name__)
56
57
57
58
58 class UserTemp(object):
59 class UserTemp(object):
59 def __init__(self, user_id):
60 def __init__(self, user_id):
60 self.user_id = user_id
61 self.user_id = user_id
61
62
62 def __repr__(self):
63 def __repr__(self):
63 return "<%s('id:%s')>" % (self.__class__.__name__, self.user_id)
64 return "<%s('id:%s')>" % (self.__class__.__name__, self.user_id)
64
65
65
66
66 class RepoTemp(object):
67 class RepoTemp(object):
67 def __init__(self, repo_id):
68 def __init__(self, repo_id):
68 self.repo_id = repo_id
69 self.repo_id = repo_id
69
70
70 def __repr__(self):
71 def __repr__(self):
71 return "<%s('id:%s')>" % (self.__class__.__name__, self.repo_id)
72 return "<%s('id:%s')>" % (self.__class__.__name__, self.repo_id)
72
73
73
74
74 class SimpleCachedRepoList(object):
75 class SimpleCachedRepoList(object):
75 """
76 """
76 Lighter version of of iteration of repos without the scm initialisation,
77 Lighter version of of iteration of repos without the scm initialisation,
77 and with cache usage
78 and with cache usage
78 """
79 """
79 def __init__(self, db_repo_list, repos_path, order_by=None, perm_set=None):
80 def __init__(self, db_repo_list, repos_path, order_by=None, perm_set=None):
80 self.db_repo_list = db_repo_list
81 self.db_repo_list = db_repo_list
81 self.repos_path = repos_path
82 self.repos_path = repos_path
82 self.order_by = order_by
83 self.order_by = order_by
83 self.reversed = (order_by or '').startswith('-')
84 self.reversed = (order_by or '').startswith('-')
84 if not perm_set:
85 if not perm_set:
85 perm_set = ['repository.read', 'repository.write',
86 perm_set = ['repository.read', 'repository.write',
86 'repository.admin']
87 'repository.admin']
87 self.perm_set = perm_set
88 self.perm_set = perm_set
88
89
89 def __len__(self):
90 def __len__(self):
90 return len(self.db_repo_list)
91 return len(self.db_repo_list)
91
92
92 def __repr__(self):
93 def __repr__(self):
93 return '<%s (%s)>' % (self.__class__.__name__, self.__len__())
94 return '<%s (%s)>' % (self.__class__.__name__, self.__len__())
94
95
95 def __iter__(self):
96 def __iter__(self):
96 for dbr in self.db_repo_list:
97 for dbr in self.db_repo_list:
97 # check permission at this level
98 # check permission at this level
98 has_perm = HasRepoPermissionAny(*self.perm_set)(
99 has_perm = HasRepoPermissionAny(*self.perm_set)(
99 dbr.repo_name, 'SimpleCachedRepoList check')
100 dbr.repo_name, 'SimpleCachedRepoList check')
100 if not has_perm:
101 if not has_perm:
101 continue
102 continue
102
103
103 tmp_d = {
104 tmp_d = {
104 'name': dbr.repo_name,
105 'name': dbr.repo_name,
105 'dbrepo': dbr.get_dict(),
106 'dbrepo': dbr.get_dict(),
106 'dbrepo_fork': dbr.fork.get_dict() if dbr.fork else {}
107 'dbrepo_fork': dbr.fork.get_dict() if dbr.fork else {}
107 }
108 }
108 yield tmp_d
109 yield tmp_d
109
110
110
111
111 class _PermCheckIterator(object):
112 class _PermCheckIterator(object):
112
113
113 def __init__(
114 def __init__(
114 self, obj_list, obj_attr, perm_set, perm_checker,
115 self, obj_list, obj_attr, perm_set, perm_checker,
115 extra_kwargs=None):
116 extra_kwargs=None):
116 """
117 """
117 Creates iterator from given list of objects, additionally
118 Creates iterator from given list of objects, additionally
118 checking permission for them from perm_set var
119 checking permission for them from perm_set var
119
120
120 :param obj_list: list of db objects
121 :param obj_list: list of db objects
121 :param obj_attr: attribute of object to pass into perm_checker
122 :param obj_attr: attribute of object to pass into perm_checker
122 :param perm_set: list of permissions to check
123 :param perm_set: list of permissions to check
123 :param perm_checker: callable to check permissions against
124 :param perm_checker: callable to check permissions against
124 """
125 """
125 self.obj_list = obj_list
126 self.obj_list = obj_list
126 self.obj_attr = obj_attr
127 self.obj_attr = obj_attr
127 self.perm_set = perm_set
128 self.perm_set = perm_set
128 self.perm_checker = perm_checker
129 self.perm_checker = perm_checker
129 self.extra_kwargs = extra_kwargs or {}
130 self.extra_kwargs = extra_kwargs or {}
130
131
131 def __len__(self):
132 def __len__(self):
132 return len(self.obj_list)
133 return len(self.obj_list)
133
134
134 def __repr__(self):
135 def __repr__(self):
135 return '<%s (%s)>' % (self.__class__.__name__, self.__len__())
136 return '<%s (%s)>' % (self.__class__.__name__, self.__len__())
136
137
137 def __iter__(self):
138 def __iter__(self):
138 checker = self.perm_checker(*self.perm_set)
139 checker = self.perm_checker(*self.perm_set)
139 for db_obj in self.obj_list:
140 for db_obj in self.obj_list:
140 # check permission at this level
141 # check permission at this level
141 name = getattr(db_obj, self.obj_attr, None)
142 name = getattr(db_obj, self.obj_attr, None)
142 if not checker(name, self.__class__.__name__, **self.extra_kwargs):
143 if not checker(name, self.__class__.__name__, **self.extra_kwargs):
143 continue
144 continue
144
145
145 yield db_obj
146 yield db_obj
146
147
147
148
148 class RepoList(_PermCheckIterator):
149 class RepoList(_PermCheckIterator):
149
150
150 def __init__(self, db_repo_list, perm_set=None, extra_kwargs=None):
151 def __init__(self, db_repo_list, perm_set=None, extra_kwargs=None):
151 if not perm_set:
152 if not perm_set:
152 perm_set = [
153 perm_set = [
153 'repository.read', 'repository.write', 'repository.admin']
154 'repository.read', 'repository.write', 'repository.admin']
154
155
155 super(RepoList, self).__init__(
156 super(RepoList, self).__init__(
156 obj_list=db_repo_list,
157 obj_list=db_repo_list,
157 obj_attr='repo_name', perm_set=perm_set,
158 obj_attr='repo_name', perm_set=perm_set,
158 perm_checker=HasRepoPermissionAny,
159 perm_checker=HasRepoPermissionAny,
159 extra_kwargs=extra_kwargs)
160 extra_kwargs=extra_kwargs)
160
161
161
162
162 class RepoGroupList(_PermCheckIterator):
163 class RepoGroupList(_PermCheckIterator):
163
164
164 def __init__(self, db_repo_group_list, perm_set=None, extra_kwargs=None):
165 def __init__(self, db_repo_group_list, perm_set=None, extra_kwargs=None):
165 if not perm_set:
166 if not perm_set:
166 perm_set = ['group.read', 'group.write', 'group.admin']
167 perm_set = ['group.read', 'group.write', 'group.admin']
167
168
168 super(RepoGroupList, self).__init__(
169 super(RepoGroupList, self).__init__(
169 obj_list=db_repo_group_list,
170 obj_list=db_repo_group_list,
170 obj_attr='group_name', perm_set=perm_set,
171 obj_attr='group_name', perm_set=perm_set,
171 perm_checker=HasRepoGroupPermissionAny,
172 perm_checker=HasRepoGroupPermissionAny,
172 extra_kwargs=extra_kwargs)
173 extra_kwargs=extra_kwargs)
173
174
174
175
175 class UserGroupList(_PermCheckIterator):
176 class UserGroupList(_PermCheckIterator):
176
177
177 def __init__(self, db_user_group_list, perm_set=None, extra_kwargs=None):
178 def __init__(self, db_user_group_list, perm_set=None, extra_kwargs=None):
178 if not perm_set:
179 if not perm_set:
179 perm_set = ['usergroup.read', 'usergroup.write', 'usergroup.admin']
180 perm_set = ['usergroup.read', 'usergroup.write', 'usergroup.admin']
180
181
181 super(UserGroupList, self).__init__(
182 super(UserGroupList, self).__init__(
182 obj_list=db_user_group_list,
183 obj_list=db_user_group_list,
183 obj_attr='users_group_name', perm_set=perm_set,
184 obj_attr='users_group_name', perm_set=perm_set,
184 perm_checker=HasUserGroupPermissionAny,
185 perm_checker=HasUserGroupPermissionAny,
185 extra_kwargs=extra_kwargs)
186 extra_kwargs=extra_kwargs)
186
187
187
188
188 class ScmModel(BaseModel):
189 class ScmModel(BaseModel):
189 """
190 """
190 Generic Scm Model
191 Generic Scm Model
191 """
192 """
192
193
193 @LazyProperty
194 @LazyProperty
194 def repos_path(self):
195 def repos_path(self):
195 """
196 """
196 Gets the repositories root path from database
197 Gets the repositories root path from database
197 """
198 """
198
199
199 settings_model = VcsSettingsModel(sa=self.sa)
200 settings_model = VcsSettingsModel(sa=self.sa)
200 return settings_model.get_repos_location()
201 return settings_model.get_repos_location()
201
202
202 def repo_scan(self, repos_path=None):
203 def repo_scan(self, repos_path=None):
203 """
204 """
204 Listing of repositories in given path. This path should not be a
205 Listing of repositories in given path. This path should not be a
205 repository itself. Return a dictionary of repository objects
206 repository itself. Return a dictionary of repository objects
206
207
207 :param repos_path: path to directory containing repositories
208 :param repos_path: path to directory containing repositories
208 """
209 """
209
210
210 if repos_path is None:
211 if repos_path is None:
211 repos_path = self.repos_path
212 repos_path = self.repos_path
212
213
213 log.info('scanning for repositories in %s', repos_path)
214 log.info('scanning for repositories in %s', repos_path)
214
215
215 config = make_db_config()
216 config = make_db_config()
216 config.set('extensions', 'largefiles', '')
217 config.set('extensions', 'largefiles', '')
217 repos = {}
218 repos = {}
218
219
219 for name, path in get_filesystem_repos(repos_path, recursive=True):
220 for name, path in get_filesystem_repos(repos_path, recursive=True):
220 # name need to be decomposed and put back together using the /
221 # name need to be decomposed and put back together using the /
221 # since this is internal storage separator for rhodecode
222 # since this is internal storage separator for rhodecode
222 name = Repository.normalize_repo_name(name)
223 name = Repository.normalize_repo_name(name)
223
224
224 try:
225 try:
225 if name in repos:
226 if name in repos:
226 raise RepositoryError('Duplicate repository name %s '
227 raise RepositoryError('Duplicate repository name %s '
227 'found in %s' % (name, path))
228 'found in %s' % (name, path))
228 elif path[0] in rhodecode.BACKENDS:
229 elif path[0] in rhodecode.BACKENDS:
229 backend = get_backend(path[0])
230 backend = get_backend(path[0])
230 repos[name] = backend(path[1], config=config,
231 repos[name] = backend(path[1], config=config,
231 with_wire={"cache": False})
232 with_wire={"cache": False})
232 except OSError:
233 except OSError:
233 continue
234 continue
234 log.debug('found %s paths with repositories', len(repos))
235 log.debug('found %s paths with repositories', len(repos))
235 return repos
236 return repos
236
237
237 def get_repos(self, all_repos=None, sort_key=None):
238 def get_repos(self, all_repos=None, sort_key=None):
238 """
239 """
239 Get all repositories from db and for each repo create it's
240 Get all repositories from db and for each repo create it's
240 backend instance and fill that backed with information from database
241 backend instance and fill that backed with information from database
241
242
242 :param all_repos: list of repository names as strings
243 :param all_repos: list of repository names as strings
243 give specific repositories list, good for filtering
244 give specific repositories list, good for filtering
244
245
245 :param sort_key: initial sorting of repositories
246 :param sort_key: initial sorting of repositories
246 """
247 """
247 if all_repos is None:
248 if all_repos is None:
248 all_repos = self.sa.query(Repository)\
249 all_repos = self.sa.query(Repository)\
249 .filter(Repository.group_id == None)\
250 .filter(Repository.group_id == None)\
250 .order_by(func.lower(Repository.repo_name)).all()
251 .order_by(func.lower(Repository.repo_name)).all()
251 repo_iter = SimpleCachedRepoList(
252 repo_iter = SimpleCachedRepoList(
252 all_repos, repos_path=self.repos_path, order_by=sort_key)
253 all_repos, repos_path=self.repos_path, order_by=sort_key)
253 return repo_iter
254 return repo_iter
254
255
255 def get_repo_groups(self, all_groups=None):
256 def get_repo_groups(self, all_groups=None):
256 if all_groups is None:
257 if all_groups is None:
257 all_groups = RepoGroup.query()\
258 all_groups = RepoGroup.query()\
258 .filter(RepoGroup.group_parent_id == None).all()
259 .filter(RepoGroup.group_parent_id == None).all()
259 return [x for x in RepoGroupList(all_groups)]
260 return [x for x in RepoGroupList(all_groups)]
260
261
261 def mark_for_invalidation(self, repo_name, delete=False):
262 def mark_for_invalidation(self, repo_name, delete=False):
262 """
263 """
263 Mark caches of this repo invalid in the database. `delete` flag
264 Mark caches of this repo invalid in the database. `delete` flag
264 removes the cache entries
265 removes the cache entries
265
266
266 :param repo_name: the repo_name for which caches should be marked
267 :param repo_name: the repo_name for which caches should be marked
267 invalid, or deleted
268 invalid, or deleted
268 :param delete: delete the entry keys instead of setting bool
269 :param delete: delete the entry keys instead of setting bool
269 flag on them, and also purge caches used by the dogpile
270 flag on them, and also purge caches used by the dogpile
270 """
271 """
271 repo = Repository.get_by_repo_name(repo_name)
272 repo = Repository.get_by_repo_name(repo_name)
272
273
273 if repo:
274 if repo:
274 invalidation_namespace = CacheKey.REPO_INVALIDATION_NAMESPACE.format(
275 invalidation_namespace = CacheKey.REPO_INVALIDATION_NAMESPACE.format(
275 repo_id=repo.repo_id)
276 repo_id=repo.repo_id)
276 CacheKey.set_invalidate(invalidation_namespace, delete=delete)
277 CacheKey.set_invalidate(invalidation_namespace, delete=delete)
277
278
278 repo_id = repo.repo_id
279 repo_id = repo.repo_id
279 config = repo._config
280 config = repo._config
280 config.set('extensions', 'largefiles', '')
281 config.set('extensions', 'largefiles', '')
281 repo.update_commit_cache(config=config, cs_cache=None)
282 repo.update_commit_cache(config=config, cs_cache=None)
282 if delete:
283 if delete:
283 cache_namespace_uid = 'cache_repo.{}'.format(repo_id)
284 cache_namespace_uid = 'cache_repo.{}'.format(repo_id)
284 rc_cache.clear_cache_namespace('cache_repo', cache_namespace_uid)
285 rc_cache.clear_cache_namespace('cache_repo', cache_namespace_uid)
285
286
286 def toggle_following_repo(self, follow_repo_id, user_id):
287 def toggle_following_repo(self, follow_repo_id, user_id):
287
288
288 f = self.sa.query(UserFollowing)\
289 f = self.sa.query(UserFollowing)\
289 .filter(UserFollowing.follows_repo_id == follow_repo_id)\
290 .filter(UserFollowing.follows_repo_id == follow_repo_id)\
290 .filter(UserFollowing.user_id == user_id).scalar()
291 .filter(UserFollowing.user_id == user_id).scalar()
291
292
292 if f is not None:
293 if f is not None:
293 try:
294 try:
294 self.sa.delete(f)
295 self.sa.delete(f)
295 return
296 return
296 except Exception:
297 except Exception:
297 log.error(traceback.format_exc())
298 log.error(traceback.format_exc())
298 raise
299 raise
299
300
300 try:
301 try:
301 f = UserFollowing()
302 f = UserFollowing()
302 f.user_id = user_id
303 f.user_id = user_id
303 f.follows_repo_id = follow_repo_id
304 f.follows_repo_id = follow_repo_id
304 self.sa.add(f)
305 self.sa.add(f)
305 except Exception:
306 except Exception:
306 log.error(traceback.format_exc())
307 log.error(traceback.format_exc())
307 raise
308 raise
308
309
309 def toggle_following_user(self, follow_user_id, user_id):
310 def toggle_following_user(self, follow_user_id, user_id):
310 f = self.sa.query(UserFollowing)\
311 f = self.sa.query(UserFollowing)\
311 .filter(UserFollowing.follows_user_id == follow_user_id)\
312 .filter(UserFollowing.follows_user_id == follow_user_id)\
312 .filter(UserFollowing.user_id == user_id).scalar()
313 .filter(UserFollowing.user_id == user_id).scalar()
313
314
314 if f is not None:
315 if f is not None:
315 try:
316 try:
316 self.sa.delete(f)
317 self.sa.delete(f)
317 return
318 return
318 except Exception:
319 except Exception:
319 log.error(traceback.format_exc())
320 log.error(traceback.format_exc())
320 raise
321 raise
321
322
322 try:
323 try:
323 f = UserFollowing()
324 f = UserFollowing()
324 f.user_id = user_id
325 f.user_id = user_id
325 f.follows_user_id = follow_user_id
326 f.follows_user_id = follow_user_id
326 self.sa.add(f)
327 self.sa.add(f)
327 except Exception:
328 except Exception:
328 log.error(traceback.format_exc())
329 log.error(traceback.format_exc())
329 raise
330 raise
330
331
331 def is_following_repo(self, repo_name, user_id, cache=False):
332 def is_following_repo(self, repo_name, user_id, cache=False):
332 r = self.sa.query(Repository)\
333 r = self.sa.query(Repository)\
333 .filter(Repository.repo_name == repo_name).scalar()
334 .filter(Repository.repo_name == repo_name).scalar()
334
335
335 f = self.sa.query(UserFollowing)\
336 f = self.sa.query(UserFollowing)\
336 .filter(UserFollowing.follows_repository == r)\
337 .filter(UserFollowing.follows_repository == r)\
337 .filter(UserFollowing.user_id == user_id).scalar()
338 .filter(UserFollowing.user_id == user_id).scalar()
338
339
339 return f is not None
340 return f is not None
340
341
341 def is_following_user(self, username, user_id, cache=False):
342 def is_following_user(self, username, user_id, cache=False):
342 u = User.get_by_username(username)
343 u = User.get_by_username(username)
343
344
344 f = self.sa.query(UserFollowing)\
345 f = self.sa.query(UserFollowing)\
345 .filter(UserFollowing.follows_user == u)\
346 .filter(UserFollowing.follows_user == u)\
346 .filter(UserFollowing.user_id == user_id).scalar()
347 .filter(UserFollowing.user_id == user_id).scalar()
347
348
348 return f is not None
349 return f is not None
349
350
350 def get_followers(self, repo):
351 def get_followers(self, repo):
351 repo = self._get_repo(repo)
352 repo = self._get_repo(repo)
352
353
353 return self.sa.query(UserFollowing)\
354 return self.sa.query(UserFollowing)\
354 .filter(UserFollowing.follows_repository == repo).count()
355 .filter(UserFollowing.follows_repository == repo).count()
355
356
356 def get_forks(self, repo):
357 def get_forks(self, repo):
357 repo = self._get_repo(repo)
358 repo = self._get_repo(repo)
358 return self.sa.query(Repository)\
359 return self.sa.query(Repository)\
359 .filter(Repository.fork == repo).count()
360 .filter(Repository.fork == repo).count()
360
361
361 def get_pull_requests(self, repo):
362 def get_pull_requests(self, repo):
362 repo = self._get_repo(repo)
363 repo = self._get_repo(repo)
363 return self.sa.query(PullRequest)\
364 return self.sa.query(PullRequest)\
364 .filter(PullRequest.target_repo == repo)\
365 .filter(PullRequest.target_repo == repo)\
365 .filter(PullRequest.status != PullRequest.STATUS_CLOSED).count()
366 .filter(PullRequest.status != PullRequest.STATUS_CLOSED).count()
366
367
368 def get_artifacts(self, repo):
369 repo = self._get_repo(repo)
370 return self.sa.query(FileStore)\
371 .filter(FileStore.repo == repo)\
372 .filter(or_(FileStore.hidden == None, FileStore.hidden == false())).count()
373
367 def mark_as_fork(self, repo, fork, user):
374 def mark_as_fork(self, repo, fork, user):
368 repo = self._get_repo(repo)
375 repo = self._get_repo(repo)
369 fork = self._get_repo(fork)
376 fork = self._get_repo(fork)
370 if fork and repo.repo_id == fork.repo_id:
377 if fork and repo.repo_id == fork.repo_id:
371 raise Exception("Cannot set repository as fork of itself")
378 raise Exception("Cannot set repository as fork of itself")
372
379
373 if fork and repo.repo_type != fork.repo_type:
380 if fork and repo.repo_type != fork.repo_type:
374 raise RepositoryError(
381 raise RepositoryError(
375 "Cannot set repository as fork of repository with other type")
382 "Cannot set repository as fork of repository with other type")
376
383
377 repo.fork = fork
384 repo.fork = fork
378 self.sa.add(repo)
385 self.sa.add(repo)
379 return repo
386 return repo
380
387
381 def pull_changes(self, repo, username, remote_uri=None, validate_uri=True):
388 def pull_changes(self, repo, username, remote_uri=None, validate_uri=True):
382 dbrepo = self._get_repo(repo)
389 dbrepo = self._get_repo(repo)
383 remote_uri = remote_uri or dbrepo.clone_uri
390 remote_uri = remote_uri or dbrepo.clone_uri
384 if not remote_uri:
391 if not remote_uri:
385 raise Exception("This repository doesn't have a clone uri")
392 raise Exception("This repository doesn't have a clone uri")
386
393
387 repo = dbrepo.scm_instance(cache=False)
394 repo = dbrepo.scm_instance(cache=False)
388 repo.config.clear_section('hooks')
395 repo.config.clear_section('hooks')
389
396
390 try:
397 try:
391 # NOTE(marcink): add extra validation so we skip invalid urls
398 # NOTE(marcink): add extra validation so we skip invalid urls
392 # this is due this tasks can be executed via scheduler without
399 # this is due this tasks can be executed via scheduler without
393 # proper validation of remote_uri
400 # proper validation of remote_uri
394 if validate_uri:
401 if validate_uri:
395 config = make_db_config(clear_session=False)
402 config = make_db_config(clear_session=False)
396 url_validator(remote_uri, dbrepo.repo_type, config)
403 url_validator(remote_uri, dbrepo.repo_type, config)
397 except InvalidCloneUrl:
404 except InvalidCloneUrl:
398 raise
405 raise
399
406
400 repo_name = dbrepo.repo_name
407 repo_name = dbrepo.repo_name
401 try:
408 try:
402 # TODO: we need to make sure those operations call proper hooks !
409 # TODO: we need to make sure those operations call proper hooks !
403 repo.fetch(remote_uri)
410 repo.fetch(remote_uri)
404
411
405 self.mark_for_invalidation(repo_name)
412 self.mark_for_invalidation(repo_name)
406 except Exception:
413 except Exception:
407 log.error(traceback.format_exc())
414 log.error(traceback.format_exc())
408 raise
415 raise
409
416
410 def push_changes(self, repo, username, remote_uri=None, validate_uri=True):
417 def push_changes(self, repo, username, remote_uri=None, validate_uri=True):
411 dbrepo = self._get_repo(repo)
418 dbrepo = self._get_repo(repo)
412 remote_uri = remote_uri or dbrepo.push_uri
419 remote_uri = remote_uri or dbrepo.push_uri
413 if not remote_uri:
420 if not remote_uri:
414 raise Exception("This repository doesn't have a clone uri")
421 raise Exception("This repository doesn't have a clone uri")
415
422
416 repo = dbrepo.scm_instance(cache=False)
423 repo = dbrepo.scm_instance(cache=False)
417 repo.config.clear_section('hooks')
424 repo.config.clear_section('hooks')
418
425
419 try:
426 try:
420 # NOTE(marcink): add extra validation so we skip invalid urls
427 # NOTE(marcink): add extra validation so we skip invalid urls
421 # this is due this tasks can be executed via scheduler without
428 # this is due this tasks can be executed via scheduler without
422 # proper validation of remote_uri
429 # proper validation of remote_uri
423 if validate_uri:
430 if validate_uri:
424 config = make_db_config(clear_session=False)
431 config = make_db_config(clear_session=False)
425 url_validator(remote_uri, dbrepo.repo_type, config)
432 url_validator(remote_uri, dbrepo.repo_type, config)
426 except InvalidCloneUrl:
433 except InvalidCloneUrl:
427 raise
434 raise
428
435
429 try:
436 try:
430 repo.push(remote_uri)
437 repo.push(remote_uri)
431 except Exception:
438 except Exception:
432 log.error(traceback.format_exc())
439 log.error(traceback.format_exc())
433 raise
440 raise
434
441
435 def commit_change(self, repo, repo_name, commit, user, author, message,
442 def commit_change(self, repo, repo_name, commit, user, author, message,
436 content, f_path):
443 content, f_path):
437 """
444 """
438 Commits changes
445 Commits changes
439
446
440 :param repo: SCM instance
447 :param repo: SCM instance
441
448
442 """
449 """
443 user = self._get_user(user)
450 user = self._get_user(user)
444
451
445 # decoding here will force that we have proper encoded values
452 # decoding here will force that we have proper encoded values
446 # in any other case this will throw exceptions and deny commit
453 # in any other case this will throw exceptions and deny commit
447 content = safe_str(content)
454 content = safe_str(content)
448 path = safe_str(f_path)
455 path = safe_str(f_path)
449 # message and author needs to be unicode
456 # message and author needs to be unicode
450 # proper backend should then translate that into required type
457 # proper backend should then translate that into required type
451 message = safe_unicode(message)
458 message = safe_unicode(message)
452 author = safe_unicode(author)
459 author = safe_unicode(author)
453 imc = repo.in_memory_commit
460 imc = repo.in_memory_commit
454 imc.change(FileNode(path, content, mode=commit.get_file_mode(f_path)))
461 imc.change(FileNode(path, content, mode=commit.get_file_mode(f_path)))
455 try:
462 try:
456 # TODO: handle pre-push action !
463 # TODO: handle pre-push action !
457 tip = imc.commit(
464 tip = imc.commit(
458 message=message, author=author, parents=[commit],
465 message=message, author=author, parents=[commit],
459 branch=commit.branch)
466 branch=commit.branch)
460 except Exception as e:
467 except Exception as e:
461 log.error(traceback.format_exc())
468 log.error(traceback.format_exc())
462 raise IMCCommitError(str(e))
469 raise IMCCommitError(str(e))
463 finally:
470 finally:
464 # always clear caches, if commit fails we want fresh object also
471 # always clear caches, if commit fails we want fresh object also
465 self.mark_for_invalidation(repo_name)
472 self.mark_for_invalidation(repo_name)
466
473
467 # We trigger the post-push action
474 # We trigger the post-push action
468 hooks_utils.trigger_post_push_hook(
475 hooks_utils.trigger_post_push_hook(
469 username=user.username, action='push_local', hook_type='post_push',
476 username=user.username, action='push_local', hook_type='post_push',
470 repo_name=repo_name, repo_alias=repo.alias, commit_ids=[tip.raw_id])
477 repo_name=repo_name, repo_alias=repo.alias, commit_ids=[tip.raw_id])
471 return tip
478 return tip
472
479
473 def _sanitize_path(self, f_path):
480 def _sanitize_path(self, f_path):
474 if f_path.startswith('/') or f_path.startswith('./') or '../' in f_path:
481 if f_path.startswith('/') or f_path.startswith('./') or '../' in f_path:
475 raise NonRelativePathError('%s is not an relative path' % f_path)
482 raise NonRelativePathError('%s is not an relative path' % f_path)
476 if f_path:
483 if f_path:
477 f_path = os.path.normpath(f_path)
484 f_path = os.path.normpath(f_path)
478 return f_path
485 return f_path
479
486
480 def get_dirnode_metadata(self, request, commit, dir_node):
487 def get_dirnode_metadata(self, request, commit, dir_node):
481 if not dir_node.is_dir():
488 if not dir_node.is_dir():
482 return []
489 return []
483
490
484 data = []
491 data = []
485 for node in dir_node:
492 for node in dir_node:
486 if not node.is_file():
493 if not node.is_file():
487 # we skip file-nodes
494 # we skip file-nodes
488 continue
495 continue
489
496
490 last_commit = node.last_commit
497 last_commit = node.last_commit
491 last_commit_date = last_commit.date
498 last_commit_date = last_commit.date
492 data.append({
499 data.append({
493 'name': node.name,
500 'name': node.name,
494 'size': h.format_byte_size_binary(node.size),
501 'size': h.format_byte_size_binary(node.size),
495 'modified_at': h.format_date(last_commit_date),
502 'modified_at': h.format_date(last_commit_date),
496 'modified_ts': last_commit_date.isoformat(),
503 'modified_ts': last_commit_date.isoformat(),
497 'revision': last_commit.revision,
504 'revision': last_commit.revision,
498 'short_id': last_commit.short_id,
505 'short_id': last_commit.short_id,
499 'message': h.escape(last_commit.message),
506 'message': h.escape(last_commit.message),
500 'author': h.escape(last_commit.author),
507 'author': h.escape(last_commit.author),
501 'user_profile': h.gravatar_with_user(
508 'user_profile': h.gravatar_with_user(
502 request, last_commit.author),
509 request, last_commit.author),
503 })
510 })
504
511
505 return data
512 return data
506
513
507 def get_nodes(self, repo_name, commit_id, root_path='/', flat=True,
514 def get_nodes(self, repo_name, commit_id, root_path='/', flat=True,
508 extended_info=False, content=False, max_file_bytes=None):
515 extended_info=False, content=False, max_file_bytes=None):
509 """
516 """
510 recursive walk in root dir and return a set of all path in that dir
517 recursive walk in root dir and return a set of all path in that dir
511 based on repository walk function
518 based on repository walk function
512
519
513 :param repo_name: name of repository
520 :param repo_name: name of repository
514 :param commit_id: commit id for which to list nodes
521 :param commit_id: commit id for which to list nodes
515 :param root_path: root path to list
522 :param root_path: root path to list
516 :param flat: return as a list, if False returns a dict with description
523 :param flat: return as a list, if False returns a dict with description
517 :param extended_info: show additional info such as md5, binary, size etc
524 :param extended_info: show additional info such as md5, binary, size etc
518 :param content: add nodes content to the return data
525 :param content: add nodes content to the return data
519 :param max_file_bytes: will not return file contents over this limit
526 :param max_file_bytes: will not return file contents over this limit
520
527
521 """
528 """
522 _files = list()
529 _files = list()
523 _dirs = list()
530 _dirs = list()
524 try:
531 try:
525 _repo = self._get_repo(repo_name)
532 _repo = self._get_repo(repo_name)
526 commit = _repo.scm_instance().get_commit(commit_id=commit_id)
533 commit = _repo.scm_instance().get_commit(commit_id=commit_id)
527 root_path = root_path.lstrip('/')
534 root_path = root_path.lstrip('/')
528 for __, dirs, files in commit.walk(root_path):
535 for __, dirs, files in commit.walk(root_path):
529
536
530 for f in files:
537 for f in files:
531 _content = None
538 _content = None
532 _data = f_name = f.unicode_path
539 _data = f_name = f.unicode_path
533
540
534 if not flat:
541 if not flat:
535 _data = {
542 _data = {
536 "name": h.escape(f_name),
543 "name": h.escape(f_name),
537 "type": "file",
544 "type": "file",
538 }
545 }
539 if extended_info:
546 if extended_info:
540 _data.update({
547 _data.update({
541 "md5": f.md5,
548 "md5": f.md5,
542 "binary": f.is_binary,
549 "binary": f.is_binary,
543 "size": f.size,
550 "size": f.size,
544 "extension": f.extension,
551 "extension": f.extension,
545 "mimetype": f.mimetype,
552 "mimetype": f.mimetype,
546 "lines": f.lines()[0]
553 "lines": f.lines()[0]
547 })
554 })
548
555
549 if content:
556 if content:
550 over_size_limit = (max_file_bytes is not None
557 over_size_limit = (max_file_bytes is not None
551 and f.size > max_file_bytes)
558 and f.size > max_file_bytes)
552 full_content = None
559 full_content = None
553 if not f.is_binary and not over_size_limit:
560 if not f.is_binary and not over_size_limit:
554 full_content = safe_str(f.content)
561 full_content = safe_str(f.content)
555
562
556 _data.update({
563 _data.update({
557 "content": full_content,
564 "content": full_content,
558 })
565 })
559 _files.append(_data)
566 _files.append(_data)
560
567
561 for d in dirs:
568 for d in dirs:
562 _data = d_name = d.unicode_path
569 _data = d_name = d.unicode_path
563 if not flat:
570 if not flat:
564 _data = {
571 _data = {
565 "name": h.escape(d_name),
572 "name": h.escape(d_name),
566 "type": "dir",
573 "type": "dir",
567 }
574 }
568 if extended_info:
575 if extended_info:
569 _data.update({
576 _data.update({
570 "md5": None,
577 "md5": None,
571 "binary": None,
578 "binary": None,
572 "size": None,
579 "size": None,
573 "extension": None,
580 "extension": None,
574 })
581 })
575 if content:
582 if content:
576 _data.update({
583 _data.update({
577 "content": None
584 "content": None
578 })
585 })
579 _dirs.append(_data)
586 _dirs.append(_data)
580 except RepositoryError:
587 except RepositoryError:
581 log.exception("Exception in get_nodes")
588 log.exception("Exception in get_nodes")
582 raise
589 raise
583
590
584 return _dirs, _files
591 return _dirs, _files
585
592
586 def get_quick_filter_nodes(self, repo_name, commit_id, root_path='/'):
593 def get_quick_filter_nodes(self, repo_name, commit_id, root_path='/'):
587 """
594 """
588 Generate files for quick filter in files view
595 Generate files for quick filter in files view
589 """
596 """
590
597
591 _files = list()
598 _files = list()
592 _dirs = list()
599 _dirs = list()
593 try:
600 try:
594 _repo = self._get_repo(repo_name)
601 _repo = self._get_repo(repo_name)
595 commit = _repo.scm_instance().get_commit(commit_id=commit_id)
602 commit = _repo.scm_instance().get_commit(commit_id=commit_id)
596 root_path = root_path.lstrip('/')
603 root_path = root_path.lstrip('/')
597 for __, dirs, files in commit.walk(root_path):
604 for __, dirs, files in commit.walk(root_path):
598
605
599 for f in files:
606 for f in files:
600
607
601 _data = {
608 _data = {
602 "name": h.escape(f.unicode_path),
609 "name": h.escape(f.unicode_path),
603 "type": "file",
610 "type": "file",
604 }
611 }
605
612
606 _files.append(_data)
613 _files.append(_data)
607
614
608 for d in dirs:
615 for d in dirs:
609
616
610 _data = {
617 _data = {
611 "name": h.escape(d.unicode_path),
618 "name": h.escape(d.unicode_path),
612 "type": "dir",
619 "type": "dir",
613 }
620 }
614
621
615 _dirs.append(_data)
622 _dirs.append(_data)
616 except RepositoryError:
623 except RepositoryError:
617 log.exception("Exception in get_quick_filter_nodes")
624 log.exception("Exception in get_quick_filter_nodes")
618 raise
625 raise
619
626
620 return _dirs, _files
627 return _dirs, _files
621
628
622 def get_node(self, repo_name, commit_id, file_path,
629 def get_node(self, repo_name, commit_id, file_path,
623 extended_info=False, content=False, max_file_bytes=None, cache=True):
630 extended_info=False, content=False, max_file_bytes=None, cache=True):
624 """
631 """
625 retrieve single node from commit
632 retrieve single node from commit
626 """
633 """
627 try:
634 try:
628
635
629 _repo = self._get_repo(repo_name)
636 _repo = self._get_repo(repo_name)
630 commit = _repo.scm_instance().get_commit(commit_id=commit_id)
637 commit = _repo.scm_instance().get_commit(commit_id=commit_id)
631
638
632 file_node = commit.get_node(file_path)
639 file_node = commit.get_node(file_path)
633 if file_node.is_dir():
640 if file_node.is_dir():
634 raise RepositoryError('The given path is a directory')
641 raise RepositoryError('The given path is a directory')
635
642
636 _content = None
643 _content = None
637 f_name = file_node.unicode_path
644 f_name = file_node.unicode_path
638
645
639 file_data = {
646 file_data = {
640 "name": h.escape(f_name),
647 "name": h.escape(f_name),
641 "type": "file",
648 "type": "file",
642 }
649 }
643
650
644 if extended_info:
651 if extended_info:
645 file_data.update({
652 file_data.update({
646 "extension": file_node.extension,
653 "extension": file_node.extension,
647 "mimetype": file_node.mimetype,
654 "mimetype": file_node.mimetype,
648 })
655 })
649
656
650 if cache:
657 if cache:
651 md5 = file_node.md5
658 md5 = file_node.md5
652 is_binary = file_node.is_binary
659 is_binary = file_node.is_binary
653 size = file_node.size
660 size = file_node.size
654 else:
661 else:
655 is_binary, md5, size, _content = file_node.metadata_uncached()
662 is_binary, md5, size, _content = file_node.metadata_uncached()
656
663
657 file_data.update({
664 file_data.update({
658 "md5": md5,
665 "md5": md5,
659 "binary": is_binary,
666 "binary": is_binary,
660 "size": size,
667 "size": size,
661 })
668 })
662
669
663 if content and cache:
670 if content and cache:
664 # get content + cache
671 # get content + cache
665 size = file_node.size
672 size = file_node.size
666 over_size_limit = (max_file_bytes is not None and size > max_file_bytes)
673 over_size_limit = (max_file_bytes is not None and size > max_file_bytes)
667 full_content = None
674 full_content = None
668 all_lines = 0
675 all_lines = 0
669 if not file_node.is_binary and not over_size_limit:
676 if not file_node.is_binary and not over_size_limit:
670 full_content = safe_unicode(file_node.content)
677 full_content = safe_unicode(file_node.content)
671 all_lines, empty_lines = file_node.count_lines(full_content)
678 all_lines, empty_lines = file_node.count_lines(full_content)
672
679
673 file_data.update({
680 file_data.update({
674 "content": full_content,
681 "content": full_content,
675 "lines": all_lines
682 "lines": all_lines
676 })
683 })
677 elif content:
684 elif content:
678 # get content *without* cache
685 # get content *without* cache
679 if _content is None:
686 if _content is None:
680 is_binary, md5, size, _content = file_node.metadata_uncached()
687 is_binary, md5, size, _content = file_node.metadata_uncached()
681
688
682 over_size_limit = (max_file_bytes is not None and size > max_file_bytes)
689 over_size_limit = (max_file_bytes is not None and size > max_file_bytes)
683 full_content = None
690 full_content = None
684 all_lines = 0
691 all_lines = 0
685 if not is_binary and not over_size_limit:
692 if not is_binary and not over_size_limit:
686 full_content = safe_unicode(_content)
693 full_content = safe_unicode(_content)
687 all_lines, empty_lines = file_node.count_lines(full_content)
694 all_lines, empty_lines = file_node.count_lines(full_content)
688
695
689 file_data.update({
696 file_data.update({
690 "content": full_content,
697 "content": full_content,
691 "lines": all_lines
698 "lines": all_lines
692 })
699 })
693
700
694 except RepositoryError:
701 except RepositoryError:
695 log.exception("Exception in get_node")
702 log.exception("Exception in get_node")
696 raise
703 raise
697
704
698 return file_data
705 return file_data
699
706
700 def get_fts_data(self, repo_name, commit_id, root_path='/'):
707 def get_fts_data(self, repo_name, commit_id, root_path='/'):
701 """
708 """
702 Fetch node tree for usage in full text search
709 Fetch node tree for usage in full text search
703 """
710 """
704
711
705 tree_info = list()
712 tree_info = list()
706
713
707 try:
714 try:
708 _repo = self._get_repo(repo_name)
715 _repo = self._get_repo(repo_name)
709 commit = _repo.scm_instance().get_commit(commit_id=commit_id)
716 commit = _repo.scm_instance().get_commit(commit_id=commit_id)
710 root_path = root_path.lstrip('/')
717 root_path = root_path.lstrip('/')
711 for __, dirs, files in commit.walk(root_path):
718 for __, dirs, files in commit.walk(root_path):
712
719
713 for f in files:
720 for f in files:
714 is_binary, md5, size, _content = f.metadata_uncached()
721 is_binary, md5, size, _content = f.metadata_uncached()
715 _data = {
722 _data = {
716 "name": f.unicode_path,
723 "name": f.unicode_path,
717 "md5": md5,
724 "md5": md5,
718 "extension": f.extension,
725 "extension": f.extension,
719 "binary": is_binary,
726 "binary": is_binary,
720 "size": size
727 "size": size
721 }
728 }
722
729
723 tree_info.append(_data)
730 tree_info.append(_data)
724
731
725 except RepositoryError:
732 except RepositoryError:
726 log.exception("Exception in get_nodes")
733 log.exception("Exception in get_nodes")
727 raise
734 raise
728
735
729 return tree_info
736 return tree_info
730
737
731 def create_nodes(self, user, repo, message, nodes, parent_commit=None,
738 def create_nodes(self, user, repo, message, nodes, parent_commit=None,
732 author=None, trigger_push_hook=True):
739 author=None, trigger_push_hook=True):
733 """
740 """
734 Commits given multiple nodes into repo
741 Commits given multiple nodes into repo
735
742
736 :param user: RhodeCode User object or user_id, the commiter
743 :param user: RhodeCode User object or user_id, the commiter
737 :param repo: RhodeCode Repository object
744 :param repo: RhodeCode Repository object
738 :param message: commit message
745 :param message: commit message
739 :param nodes: mapping {filename:{'content':content},...}
746 :param nodes: mapping {filename:{'content':content},...}
740 :param parent_commit: parent commit, can be empty than it's
747 :param parent_commit: parent commit, can be empty than it's
741 initial commit
748 initial commit
742 :param author: author of commit, cna be different that commiter
749 :param author: author of commit, cna be different that commiter
743 only for git
750 only for git
744 :param trigger_push_hook: trigger push hooks
751 :param trigger_push_hook: trigger push hooks
745
752
746 :returns: new commited commit
753 :returns: new commited commit
747 """
754 """
748
755
749 user = self._get_user(user)
756 user = self._get_user(user)
750 scm_instance = repo.scm_instance(cache=False)
757 scm_instance = repo.scm_instance(cache=False)
751
758
752 processed_nodes = []
759 processed_nodes = []
753 for f_path in nodes:
760 for f_path in nodes:
754 f_path = self._sanitize_path(f_path)
761 f_path = self._sanitize_path(f_path)
755 content = nodes[f_path]['content']
762 content = nodes[f_path]['content']
756 f_path = safe_str(f_path)
763 f_path = safe_str(f_path)
757 # decoding here will force that we have proper encoded values
764 # decoding here will force that we have proper encoded values
758 # in any other case this will throw exceptions and deny commit
765 # in any other case this will throw exceptions and deny commit
759 if isinstance(content, (basestring,)):
766 if isinstance(content, (basestring,)):
760 content = safe_str(content)
767 content = safe_str(content)
761 elif isinstance(content, (file, cStringIO.OutputType,)):
768 elif isinstance(content, (file, cStringIO.OutputType,)):
762 content = content.read()
769 content = content.read()
763 else:
770 else:
764 raise Exception('Content is of unrecognized type %s' % (
771 raise Exception('Content is of unrecognized type %s' % (
765 type(content)
772 type(content)
766 ))
773 ))
767 processed_nodes.append((f_path, content))
774 processed_nodes.append((f_path, content))
768
775
769 message = safe_unicode(message)
776 message = safe_unicode(message)
770 commiter = user.full_contact
777 commiter = user.full_contact
771 author = safe_unicode(author) if author else commiter
778 author = safe_unicode(author) if author else commiter
772
779
773 imc = scm_instance.in_memory_commit
780 imc = scm_instance.in_memory_commit
774
781
775 if not parent_commit:
782 if not parent_commit:
776 parent_commit = EmptyCommit(alias=scm_instance.alias)
783 parent_commit = EmptyCommit(alias=scm_instance.alias)
777
784
778 if isinstance(parent_commit, EmptyCommit):
785 if isinstance(parent_commit, EmptyCommit):
779 # EmptyCommit means we we're editing empty repository
786 # EmptyCommit means we we're editing empty repository
780 parents = None
787 parents = None
781 else:
788 else:
782 parents = [parent_commit]
789 parents = [parent_commit]
783 # add multiple nodes
790 # add multiple nodes
784 for path, content in processed_nodes:
791 for path, content in processed_nodes:
785 imc.add(FileNode(path, content=content))
792 imc.add(FileNode(path, content=content))
786 # TODO: handle pre push scenario
793 # TODO: handle pre push scenario
787 tip = imc.commit(message=message,
794 tip = imc.commit(message=message,
788 author=author,
795 author=author,
789 parents=parents,
796 parents=parents,
790 branch=parent_commit.branch)
797 branch=parent_commit.branch)
791
798
792 self.mark_for_invalidation(repo.repo_name)
799 self.mark_for_invalidation(repo.repo_name)
793 if trigger_push_hook:
800 if trigger_push_hook:
794 hooks_utils.trigger_post_push_hook(
801 hooks_utils.trigger_post_push_hook(
795 username=user.username, action='push_local',
802 username=user.username, action='push_local',
796 repo_name=repo.repo_name, repo_alias=scm_instance.alias,
803 repo_name=repo.repo_name, repo_alias=scm_instance.alias,
797 hook_type='post_push',
804 hook_type='post_push',
798 commit_ids=[tip.raw_id])
805 commit_ids=[tip.raw_id])
799 return tip
806 return tip
800
807
801 def update_nodes(self, user, repo, message, nodes, parent_commit=None,
808 def update_nodes(self, user, repo, message, nodes, parent_commit=None,
802 author=None, trigger_push_hook=True):
809 author=None, trigger_push_hook=True):
803 user = self._get_user(user)
810 user = self._get_user(user)
804 scm_instance = repo.scm_instance(cache=False)
811 scm_instance = repo.scm_instance(cache=False)
805
812
806 message = safe_unicode(message)
813 message = safe_unicode(message)
807 commiter = user.full_contact
814 commiter = user.full_contact
808 author = safe_unicode(author) if author else commiter
815 author = safe_unicode(author) if author else commiter
809
816
810 imc = scm_instance.in_memory_commit
817 imc = scm_instance.in_memory_commit
811
818
812 if not parent_commit:
819 if not parent_commit:
813 parent_commit = EmptyCommit(alias=scm_instance.alias)
820 parent_commit = EmptyCommit(alias=scm_instance.alias)
814
821
815 if isinstance(parent_commit, EmptyCommit):
822 if isinstance(parent_commit, EmptyCommit):
816 # EmptyCommit means we we're editing empty repository
823 # EmptyCommit means we we're editing empty repository
817 parents = None
824 parents = None
818 else:
825 else:
819 parents = [parent_commit]
826 parents = [parent_commit]
820
827
821 # add multiple nodes
828 # add multiple nodes
822 for _filename, data in nodes.items():
829 for _filename, data in nodes.items():
823 # new filename, can be renamed from the old one, also sanitaze
830 # new filename, can be renamed from the old one, also sanitaze
824 # the path for any hack around relative paths like ../../ etc.
831 # the path for any hack around relative paths like ../../ etc.
825 filename = self._sanitize_path(data['filename'])
832 filename = self._sanitize_path(data['filename'])
826 old_filename = self._sanitize_path(_filename)
833 old_filename = self._sanitize_path(_filename)
827 content = data['content']
834 content = data['content']
828 file_mode = data.get('mode')
835 file_mode = data.get('mode')
829 filenode = FileNode(old_filename, content=content, mode=file_mode)
836 filenode = FileNode(old_filename, content=content, mode=file_mode)
830 op = data['op']
837 op = data['op']
831 if op == 'add':
838 if op == 'add':
832 imc.add(filenode)
839 imc.add(filenode)
833 elif op == 'del':
840 elif op == 'del':
834 imc.remove(filenode)
841 imc.remove(filenode)
835 elif op == 'mod':
842 elif op == 'mod':
836 if filename != old_filename:
843 if filename != old_filename:
837 # TODO: handle renames more efficient, needs vcs lib changes
844 # TODO: handle renames more efficient, needs vcs lib changes
838 imc.remove(filenode)
845 imc.remove(filenode)
839 imc.add(FileNode(filename, content=content, mode=file_mode))
846 imc.add(FileNode(filename, content=content, mode=file_mode))
840 else:
847 else:
841 imc.change(filenode)
848 imc.change(filenode)
842
849
843 try:
850 try:
844 # TODO: handle pre push scenario commit changes
851 # TODO: handle pre push scenario commit changes
845 tip = imc.commit(message=message,
852 tip = imc.commit(message=message,
846 author=author,
853 author=author,
847 parents=parents,
854 parents=parents,
848 branch=parent_commit.branch)
855 branch=parent_commit.branch)
849 except NodeNotChangedError:
856 except NodeNotChangedError:
850 raise
857 raise
851 except Exception as e:
858 except Exception as e:
852 log.exception("Unexpected exception during call to imc.commit")
859 log.exception("Unexpected exception during call to imc.commit")
853 raise IMCCommitError(str(e))
860 raise IMCCommitError(str(e))
854 finally:
861 finally:
855 # always clear caches, if commit fails we want fresh object also
862 # always clear caches, if commit fails we want fresh object also
856 self.mark_for_invalidation(repo.repo_name)
863 self.mark_for_invalidation(repo.repo_name)
857
864
858 if trigger_push_hook:
865 if trigger_push_hook:
859 hooks_utils.trigger_post_push_hook(
866 hooks_utils.trigger_post_push_hook(
860 username=user.username, action='push_local', hook_type='post_push',
867 username=user.username, action='push_local', hook_type='post_push',
861 repo_name=repo.repo_name, repo_alias=scm_instance.alias,
868 repo_name=repo.repo_name, repo_alias=scm_instance.alias,
862 commit_ids=[tip.raw_id])
869 commit_ids=[tip.raw_id])
863
870
864 return tip
871 return tip
865
872
866 def delete_nodes(self, user, repo, message, nodes, parent_commit=None,
873 def delete_nodes(self, user, repo, message, nodes, parent_commit=None,
867 author=None, trigger_push_hook=True):
874 author=None, trigger_push_hook=True):
868 """
875 """
869 Deletes given multiple nodes into `repo`
876 Deletes given multiple nodes into `repo`
870
877
871 :param user: RhodeCode User object or user_id, the committer
878 :param user: RhodeCode User object or user_id, the committer
872 :param repo: RhodeCode Repository object
879 :param repo: RhodeCode Repository object
873 :param message: commit message
880 :param message: commit message
874 :param nodes: mapping {filename:{'content':content},...}
881 :param nodes: mapping {filename:{'content':content},...}
875 :param parent_commit: parent commit, can be empty than it's initial
882 :param parent_commit: parent commit, can be empty than it's initial
876 commit
883 commit
877 :param author: author of commit, cna be different that commiter only
884 :param author: author of commit, cna be different that commiter only
878 for git
885 for git
879 :param trigger_push_hook: trigger push hooks
886 :param trigger_push_hook: trigger push hooks
880
887
881 :returns: new commit after deletion
888 :returns: new commit after deletion
882 """
889 """
883
890
884 user = self._get_user(user)
891 user = self._get_user(user)
885 scm_instance = repo.scm_instance(cache=False)
892 scm_instance = repo.scm_instance(cache=False)
886
893
887 processed_nodes = []
894 processed_nodes = []
888 for f_path in nodes:
895 for f_path in nodes:
889 f_path = self._sanitize_path(f_path)
896 f_path = self._sanitize_path(f_path)
890 # content can be empty but for compatabilty it allows same dicts
897 # content can be empty but for compatabilty it allows same dicts
891 # structure as add_nodes
898 # structure as add_nodes
892 content = nodes[f_path].get('content')
899 content = nodes[f_path].get('content')
893 processed_nodes.append((f_path, content))
900 processed_nodes.append((f_path, content))
894
901
895 message = safe_unicode(message)
902 message = safe_unicode(message)
896 commiter = user.full_contact
903 commiter = user.full_contact
897 author = safe_unicode(author) if author else commiter
904 author = safe_unicode(author) if author else commiter
898
905
899 imc = scm_instance.in_memory_commit
906 imc = scm_instance.in_memory_commit
900
907
901 if not parent_commit:
908 if not parent_commit:
902 parent_commit = EmptyCommit(alias=scm_instance.alias)
909 parent_commit = EmptyCommit(alias=scm_instance.alias)
903
910
904 if isinstance(parent_commit, EmptyCommit):
911 if isinstance(parent_commit, EmptyCommit):
905 # EmptyCommit means we we're editing empty repository
912 # EmptyCommit means we we're editing empty repository
906 parents = None
913 parents = None
907 else:
914 else:
908 parents = [parent_commit]
915 parents = [parent_commit]
909 # add multiple nodes
916 # add multiple nodes
910 for path, content in processed_nodes:
917 for path, content in processed_nodes:
911 imc.remove(FileNode(path, content=content))
918 imc.remove(FileNode(path, content=content))
912
919
913 # TODO: handle pre push scenario
920 # TODO: handle pre push scenario
914 tip = imc.commit(message=message,
921 tip = imc.commit(message=message,
915 author=author,
922 author=author,
916 parents=parents,
923 parents=parents,
917 branch=parent_commit.branch)
924 branch=parent_commit.branch)
918
925
919 self.mark_for_invalidation(repo.repo_name)
926 self.mark_for_invalidation(repo.repo_name)
920 if trigger_push_hook:
927 if trigger_push_hook:
921 hooks_utils.trigger_post_push_hook(
928 hooks_utils.trigger_post_push_hook(
922 username=user.username, action='push_local', hook_type='post_push',
929 username=user.username, action='push_local', hook_type='post_push',
923 repo_name=repo.repo_name, repo_alias=scm_instance.alias,
930 repo_name=repo.repo_name, repo_alias=scm_instance.alias,
924 commit_ids=[tip.raw_id])
931 commit_ids=[tip.raw_id])
925 return tip
932 return tip
926
933
927 def strip(self, repo, commit_id, branch):
934 def strip(self, repo, commit_id, branch):
928 scm_instance = repo.scm_instance(cache=False)
935 scm_instance = repo.scm_instance(cache=False)
929 scm_instance.config.clear_section('hooks')
936 scm_instance.config.clear_section('hooks')
930 scm_instance.strip(commit_id, branch)
937 scm_instance.strip(commit_id, branch)
931 self.mark_for_invalidation(repo.repo_name)
938 self.mark_for_invalidation(repo.repo_name)
932
939
933 def get_unread_journal(self):
940 def get_unread_journal(self):
934 return self.sa.query(UserLog).count()
941 return self.sa.query(UserLog).count()
935
942
936 @classmethod
943 @classmethod
937 def backend_landing_ref(cls, repo_type):
944 def backend_landing_ref(cls, repo_type):
938 """
945 """
939 Return a default landing ref based on a repository type.
946 Return a default landing ref based on a repository type.
940 """
947 """
941
948
942 landing_ref = {
949 landing_ref = {
943 'hg': ('branch:default', 'default'),
950 'hg': ('branch:default', 'default'),
944 'git': ('branch:master', 'master'),
951 'git': ('branch:master', 'master'),
945 'svn': ('rev:tip', 'latest tip'),
952 'svn': ('rev:tip', 'latest tip'),
946 'default': ('rev:tip', 'latest tip'),
953 'default': ('rev:tip', 'latest tip'),
947 }
954 }
948
955
949 return landing_ref.get(repo_type) or landing_ref['default']
956 return landing_ref.get(repo_type) or landing_ref['default']
950
957
951 def get_repo_landing_revs(self, translator, repo=None):
958 def get_repo_landing_revs(self, translator, repo=None):
952 """
959 """
953 Generates select option with tags branches and bookmarks (for hg only)
960 Generates select option with tags branches and bookmarks (for hg only)
954 grouped by type
961 grouped by type
955
962
956 :param repo:
963 :param repo:
957 """
964 """
958 _ = translator
965 _ = translator
959 repo = self._get_repo(repo)
966 repo = self._get_repo(repo)
960
967
961 if repo:
968 if repo:
962 repo_type = repo.repo_type
969 repo_type = repo.repo_type
963 else:
970 else:
964 repo_type = 'default'
971 repo_type = 'default'
965
972
966 default_landing_ref, landing_ref_lbl = self.backend_landing_ref(repo_type)
973 default_landing_ref, landing_ref_lbl = self.backend_landing_ref(repo_type)
967
974
968 default_ref_options = [
975 default_ref_options = [
969 [default_landing_ref, landing_ref_lbl]
976 [default_landing_ref, landing_ref_lbl]
970 ]
977 ]
971 default_choices = [
978 default_choices = [
972 default_landing_ref
979 default_landing_ref
973 ]
980 ]
974
981
975 if not repo:
982 if not repo:
976 return default_choices, default_ref_options
983 return default_choices, default_ref_options
977
984
978 repo = repo.scm_instance()
985 repo = repo.scm_instance()
979
986
980 ref_options = [('rev:tip', 'latest tip')]
987 ref_options = [('rev:tip', 'latest tip')]
981 choices = ['rev:tip']
988 choices = ['rev:tip']
982
989
983 # branches
990 # branches
984 branch_group = [(u'branch:%s' % safe_unicode(b), safe_unicode(b)) for b in repo.branches]
991 branch_group = [(u'branch:%s' % safe_unicode(b), safe_unicode(b)) for b in repo.branches]
985 if not branch_group:
992 if not branch_group:
986 # new repo, or without maybe a branch?
993 # new repo, or without maybe a branch?
987 branch_group = default_ref_options
994 branch_group = default_ref_options
988
995
989 branches_group = (branch_group, _("Branches"))
996 branches_group = (branch_group, _("Branches"))
990 ref_options.append(branches_group)
997 ref_options.append(branches_group)
991 choices.extend([x[0] for x in branches_group[0]])
998 choices.extend([x[0] for x in branches_group[0]])
992
999
993 # bookmarks for HG
1000 # bookmarks for HG
994 if repo.alias == 'hg':
1001 if repo.alias == 'hg':
995 bookmarks_group = (
1002 bookmarks_group = (
996 [(u'book:%s' % safe_unicode(b), safe_unicode(b))
1003 [(u'book:%s' % safe_unicode(b), safe_unicode(b))
997 for b in repo.bookmarks],
1004 for b in repo.bookmarks],
998 _("Bookmarks"))
1005 _("Bookmarks"))
999 ref_options.append(bookmarks_group)
1006 ref_options.append(bookmarks_group)
1000 choices.extend([x[0] for x in bookmarks_group[0]])
1007 choices.extend([x[0] for x in bookmarks_group[0]])
1001
1008
1002 # tags
1009 # tags
1003 tags_group = (
1010 tags_group = (
1004 [(u'tag:%s' % safe_unicode(t), safe_unicode(t))
1011 [(u'tag:%s' % safe_unicode(t), safe_unicode(t))
1005 for t in repo.tags],
1012 for t in repo.tags],
1006 _("Tags"))
1013 _("Tags"))
1007 ref_options.append(tags_group)
1014 ref_options.append(tags_group)
1008 choices.extend([x[0] for x in tags_group[0]])
1015 choices.extend([x[0] for x in tags_group[0]])
1009
1016
1010 return choices, ref_options
1017 return choices, ref_options
1011
1018
1012 def get_server_info(self, environ=None):
1019 def get_server_info(self, environ=None):
1013 server_info = get_system_info(environ)
1020 server_info = get_system_info(environ)
1014 return server_info
1021 return server_info
@@ -1,806 +1,816 b''
1 // navigation.less
1 // navigation.less
2 // For use in RhodeCode applications;
2 // For use in RhodeCode applications;
3 // see style guide documentation for guidelines.
3 // see style guide documentation for guidelines.
4
4
5 // TOP MAIN DARK NAVIGATION
5 // TOP MAIN DARK NAVIGATION
6
6
7 .header .main_nav.horizontal-list {
7 .header .main_nav.horizontal-list {
8 float: right;
8 float: right;
9 color: @grey4;
9 color: @grey4;
10 > li {
10 > li {
11 a {
11 a {
12 color: @grey4;
12 color: @grey4;
13 }
13 }
14 }
14 }
15 }
15 }
16
16
17 // HEADER NAVIGATION
17 // HEADER NAVIGATION
18
18
19 .horizontal-list {
19 .horizontal-list {
20 display: block;
20 display: block;
21 margin: 0;
21 margin: 0;
22 padding: 0;
22 padding: 0;
23 -webkit-padding-start: 0;
23 -webkit-padding-start: 0;
24 text-align: left;
24 text-align: left;
25 font-size: @navigation-fontsize;
25 font-size: @navigation-fontsize;
26 color: @grey6;
26 color: @grey6;
27 z-index:10;
27 z-index:10;
28
28
29 li {
29 li {
30 line-height: 1em;
30 line-height: 1em;
31 list-style-type: none;
31 list-style-type: none;
32 margin: 0 20px 0 0;
32 margin: 0 20px 0 0;
33
33
34 a {
34 a {
35 padding: 0 .5em;
35 padding: 0 .5em;
36
36
37 &.menu_link_notifications {
37 &.menu_link_notifications {
38 .pill(7px,@rcblue);
38 .pill(7px,@rcblue);
39 display: inline;
39 display: inline;
40 margin: 0 7px 0 .7em;
40 margin: 0 7px 0 .7em;
41 font-size: @basefontsize;
41 font-size: @basefontsize;
42 color: white;
42 color: white;
43
43
44 &.empty {
44 &.empty {
45 background-color: @grey4;
45 background-color: @grey4;
46 }
46 }
47
47
48 &:hover {
48 &:hover {
49 background-color: @rcdarkblue;
49 background-color: @rcdarkblue;
50 }
50 }
51 }
51 }
52 }
52 }
53 .pill_container {
53 .pill_container {
54 margin: 1.25em 0px 0px 0px;
54 margin: 1.25em 0px 0px 0px;
55 float: right;
55 float: right;
56 }
56 }
57
57
58 &#quick_login_li {
58 &#quick_login_li {
59 &:hover {
59 &:hover {
60 color: @grey5;
60 color: @grey5;
61 }
61 }
62
62
63 a.menu_link_notifications {
63 a.menu_link_notifications {
64 color: white;
64 color: white;
65 }
65 }
66
66
67 .user {
67 .user {
68 padding-bottom: 10px;
68 padding-bottom: 10px;
69 }
69 }
70 }
70 }
71
71
72 &:before { content: none; }
72 &:before { content: none; }
73
73
74 &:last-child {
74 &:last-child {
75 .menulabel {
75 .menulabel {
76 padding-right: 0;
76 padding-right: 0;
77 border-right: none;
77 border-right: none;
78
78
79 .show_more {
79 .show_more {
80 padding-right: 0;
80 padding-right: 0;
81 }
81 }
82 }
82 }
83
83
84 &> a {
84 &> a {
85 border-bottom: none;
85 border-bottom: none;
86 }
86 }
87 }
87 }
88
88
89 &.open {
89 &.open {
90
90
91 a {
91 a {
92 color: white;
92 color: white;
93 }
93 }
94 }
94 }
95
95
96 &:focus {
96 &:focus {
97 outline: none;
97 outline: none;
98 }
98 }
99
99
100 ul li {
100 ul li {
101 display: block;
101 display: block;
102
102
103 &:last-child> a {
103 &:last-child> a {
104 border-bottom: none;
104 border-bottom: none;
105 }
105 }
106
106
107 ul li:last-child a {
107 ul li:last-child a {
108 /* we don't expect more then 3 levels of submenu and the third
108 /* we don't expect more then 3 levels of submenu and the third
109 level can have different html structure */
109 level can have different html structure */
110 border-bottom: none;
110 border-bottom: none;
111 }
111 }
112 }
112 }
113 }
113 }
114
114
115 > li {
115 > li {
116 float: left;
116 float: left;
117 display: block;
117 display: block;
118 padding: 0;
118 padding: 0;
119
119
120 > a,
120 > a,
121 &.has_select2 a {
121 &.has_select2 a {
122 display: block;
122 display: block;
123 padding: 10px 0;
123 padding: 10px 0;
124 }
124 }
125
125
126 .menulabel {
126 .menulabel {
127 line-height: 1em;
127 line-height: 1em;
128 // for this specifically we do not use a variable
128 // for this specifically we do not use a variable
129 }
129 }
130
130
131 .menulink-counter {
132 border: 1px solid @grey2;
133 border-radius: @border-radius;
134 background: @grey7;
135 display: inline-block;
136 padding: 0px 4px;
137 text-align: center;
138 font-size: 12px;
139 }
140
131 .pr_notifications {
141 .pr_notifications {
132 padding-left: .5em;
142 padding-left: .5em;
133 }
143 }
134
144
135 .pr_notifications + .menulabel {
145 .pr_notifications + .menulabel {
136 display:inline;
146 display:inline;
137 padding-left: 0;
147 padding-left: 0;
138 }
148 }
139
149
140 &:hover,
150 &:hover,
141 &.open,
151 &.open,
142 &.active {
152 &.active {
143 a {
153 a {
144 color: @rcblue;
154 color: @rcblue;
145 }
155 }
146 }
156 }
147 }
157 }
148
158
149 pre {
159 pre {
150 margin: 0;
160 margin: 0;
151 padding: 0;
161 padding: 0;
152 }
162 }
153
163
154 .select2-container,
164 .select2-container,
155 .menulink.childs {
165 .menulink.childs {
156 position: relative;
166 position: relative;
157 }
167 }
158
168
159 .menulink {
169 .menulink {
160 &.disabled {
170 &.disabled {
161 color: @grey3;
171 color: @grey3;
162 cursor: default;
172 cursor: default;
163 opacity: 0.5;
173 opacity: 0.5;
164 }
174 }
165 }
175 }
166
176
167 #quick_login {
177 #quick_login {
168
178
169 li a {
179 li a {
170 padding: .5em 0;
180 padding: .5em 0;
171 border-bottom: none;
181 border-bottom: none;
172 color: @grey2;
182 color: @grey2;
173
183
174 &:hover { color: @grey1; }
184 &:hover { color: @grey1; }
175 }
185 }
176 }
186 }
177
187
178 #quick_login_link {
188 #quick_login_link {
179 display: inline-block;
189 display: inline-block;
180
190
181 .gravatar {
191 .gravatar {
182 border: 1px solid @grey5;
192 border: 1px solid @grey5;
183 }
193 }
184
194
185 .gravatar-login {
195 .gravatar-login {
186 height: 20px;
196 height: 20px;
187 width: 20px;
197 width: 20px;
188 margin: -8px 0;
198 margin: -8px 0;
189 padding: 0;
199 padding: 0;
190 }
200 }
191
201
192 &:hover .user {
202 &:hover .user {
193 color: @grey6;
203 color: @grey6;
194 }
204 }
195 }
205 }
196 }
206 }
197 .header .horizontal-list {
207 .header .horizontal-list {
198
208
199 li {
209 li {
200
210
201 &#quick_login_li {
211 &#quick_login_li {
202 padding-left: .5em;
212 padding-left: .5em;
203 margin-right: 0px;
213 margin-right: 0px;
204
214
205 &:hover #quick_login_link {
215 &:hover #quick_login_link {
206 color: inherit;
216 color: inherit;
207 }
217 }
208
218
209 .menu_link_user {
219 .menu_link_user {
210 padding: 0 2px;
220 padding: 0 2px;
211 }
221 }
212 }
222 }
213 list-style-type: none;
223 list-style-type: none;
214 }
224 }
215
225
216 > li {
226 > li {
217
227
218 a {
228 a {
219 padding: 18px 0 12px 0;
229 padding: 18px 0 12px 0;
220 color: @nav-grey;
230 color: @nav-grey;
221
231
222 &.menu_link_notifications {
232 &.menu_link_notifications {
223 padding: 1px 8px;
233 padding: 1px 8px;
224 }
234 }
225 }
235 }
226
236
227 &:hover,
237 &:hover,
228 &.open,
238 &.open,
229 &.active {
239 &.active {
230 .pill_container a {
240 .pill_container a {
231 // don't select text for the pill container, it has it' own
241 // don't select text for the pill container, it has it' own
232 // hover behaviour
242 // hover behaviour
233 color: @nav-grey;
243 color: @nav-grey;
234 }
244 }
235 }
245 }
236
246
237 &:hover,
247 &:hover,
238 &.open,
248 &.open,
239 &.active {
249 &.active {
240 a {
250 a {
241 color: @grey6;
251 color: @grey6;
242 }
252 }
243 }
253 }
244
254
245 .select2-dropdown-open a {
255 .select2-dropdown-open a {
246 color: @grey6;
256 color: @grey6;
247 }
257 }
248
258
249 .repo-switcher {
259 .repo-switcher {
250 padding-left: 0;
260 padding-left: 0;
251
261
252 .menulabel {
262 .menulabel {
253 padding-left: 0;
263 padding-left: 0;
254 }
264 }
255 }
265 }
256 }
266 }
257
267
258 li ul li {
268 li ul li {
259 background-color:@grey2;
269 background-color:@grey2;
260
270
261 a {
271 a {
262 padding: .5em 0;
272 padding: .5em 0;
263 border-bottom: @border-thickness solid @border-default-color;
273 border-bottom: @border-thickness solid @border-default-color;
264 color: @grey6;
274 color: @grey6;
265 }
275 }
266
276
267 &:last-child a, &.last a{
277 &:last-child a, &.last a{
268 border-bottom: none;
278 border-bottom: none;
269 }
279 }
270
280
271 &:hover {
281 &:hover {
272 background-color: @grey3;
282 background-color: @grey3;
273 }
283 }
274 }
284 }
275
285
276 .submenu {
286 .submenu {
277 margin-top: 5px;
287 margin-top: 5px;
278 }
288 }
279 }
289 }
280
290
281 // SUBMENUS
291 // SUBMENUS
282 .navigation .submenu {
292 .navigation .submenu {
283 display: none;
293 display: none;
284 }
294 }
285
295
286 .navigation li.open {
296 .navigation li.open {
287 .submenu {
297 .submenu {
288 display: block;
298 display: block;
289 }
299 }
290 }
300 }
291
301
292 .navigation li:last-child .submenu {
302 .navigation li:last-child .submenu {
293 right: auto;
303 right: auto;
294 left: 0;
304 left: 0;
295 border: 1px solid @grey5;
305 border: 1px solid @grey5;
296 background: @white;
306 background: @white;
297 box-shadow: @dropdown-shadow;
307 box-shadow: @dropdown-shadow;
298 }
308 }
299
309
300 .submenu {
310 .submenu {
301 position: absolute;
311 position: absolute;
302 top: 100%;
312 top: 100%;
303 left: 0;
313 left: 0;
304 min-width: 180px;
314 min-width: 180px;
305 margin: 2px 0 0;
315 margin: 2px 0 0;
306 padding: 0;
316 padding: 0;
307 text-align: left;
317 text-align: left;
308 font-family: @text-light;
318 font-family: @text-light;
309 border-radius: @border-radius;
319 border-radius: @border-radius;
310 z-index: 20;
320 z-index: 20;
311
321
312 li {
322 li {
313 display: block;
323 display: block;
314 margin: 0;
324 margin: 0;
315 padding: 0 .5em;
325 padding: 0 .5em;
316 line-height: 1em;
326 line-height: 1em;
317 color: @grey3;
327 color: @grey3;
318 background-color: @white;
328 background-color: @white;
319 list-style-type: none;
329 list-style-type: none;
320
330
321 a {
331 a {
322 display: block;
332 display: block;
323 width: 100%;
333 width: 100%;
324 padding: .5em 0;
334 padding: .5em 0;
325 border-right: none;
335 border-right: none;
326 border-bottom: @border-thickness solid white;
336 border-bottom: @border-thickness solid white;
327 color: @grey3;
337 color: @grey3;
328 }
338 }
329
339
330 ul {
340 ul {
331 display: none;
341 display: none;
332 position: absolute;
342 position: absolute;
333 top: 0;
343 top: 0;
334 right: 100%;
344 right: 100%;
335 padding: 0;
345 padding: 0;
336 z-index: 30;
346 z-index: 30;
337 }
347 }
338 &:hover {
348 &:hover {
339 background-color: @grey7;
349 background-color: @grey7;
340 -webkit-transition: background .3s;
350 -webkit-transition: background .3s;
341 -moz-transition: background .3s;
351 -moz-transition: background .3s;
342 -o-transition: background .3s;
352 -o-transition: background .3s;
343 transition: background .3s;
353 transition: background .3s;
344
354
345 ul {
355 ul {
346 display: block;
356 display: block;
347 }
357 }
348 }
358 }
349 }
359 }
350
360
351 }
361 }
352
362
353
363
354
364
355
365
356 // repo dropdown
366 // repo dropdown
357 .quick_repo_menu {
367 .quick_repo_menu {
358 width: 15px;
368 width: 15px;
359 text-align: center;
369 text-align: center;
360 position: relative;
370 position: relative;
361 cursor: pointer;
371 cursor: pointer;
362
372
363 div {
373 div {
364 overflow: visible !important;
374 overflow: visible !important;
365 }
375 }
366
376
367 &.sorting {
377 &.sorting {
368 cursor: auto;
378 cursor: auto;
369 }
379 }
370
380
371 &:hover {
381 &:hover {
372 .menu_items_container {
382 .menu_items_container {
373 position: absolute;
383 position: absolute;
374 display: block;
384 display: block;
375 }
385 }
376 .menu_items {
386 .menu_items {
377 display: block;
387 display: block;
378 }
388 }
379 }
389 }
380
390
381 i {
391 i {
382 margin: 0;
392 margin: 0;
383 color: @grey4;
393 color: @grey4;
384 }
394 }
385
395
386 .menu_items_container {
396 .menu_items_container {
387 position: absolute;
397 position: absolute;
388 top: 0;
398 top: 0;
389 left: 100%;
399 left: 100%;
390 margin: 0;
400 margin: 0;
391 padding: 0;
401 padding: 0;
392 list-style: none;
402 list-style: none;
393 background-color: @grey6;
403 background-color: @grey6;
394 z-index: 999;
404 z-index: 999;
395 text-align: left;
405 text-align: left;
396
406
397 a {
407 a {
398 color: @grey2;
408 color: @grey2;
399 }
409 }
400
410
401 ul.menu_items {
411 ul.menu_items {
402 margin: 0;
412 margin: 0;
403 padding: 0;
413 padding: 0;
404 }
414 }
405
415
406 li {
416 li {
407 margin: 0;
417 margin: 0;
408 padding: 0;
418 padding: 0;
409 line-height: 1em;
419 line-height: 1em;
410 list-style-type: none;
420 list-style-type: none;
411
421
412 a {
422 a {
413 display: block;
423 display: block;
414 height: 16px;
424 height: 16px;
415 padding: 8px; //must add up to td height (28px)
425 padding: 8px; //must add up to td height (28px)
416 width: 120px; // set width
426 width: 120px; // set width
417
427
418 &:hover {
428 &:hover {
419 background-color: @grey5;
429 background-color: @grey5;
420 -webkit-transition: background .3s;
430 -webkit-transition: background .3s;
421 -moz-transition: background .3s;
431 -moz-transition: background .3s;
422 -o-transition: background .3s;
432 -o-transition: background .3s;
423 transition: background .3s;
433 transition: background .3s;
424 }
434 }
425 }
435 }
426 }
436 }
427 }
437 }
428 }
438 }
429
439
430
440
431 // new objects main action
441 // new objects main action
432 .action-menu {
442 .action-menu {
433 left: auto;
443 left: auto;
434 right: 0;
444 right: 0;
435 padding: 12px;
445 padding: 12px;
436 z-index: 999;
446 z-index: 999;
437 overflow: hidden;
447 overflow: hidden;
438 background-color: #fff;
448 background-color: #fff;
439 border: 1px solid @grey5;
449 border: 1px solid @grey5;
440 color: @grey2;
450 color: @grey2;
441 box-shadow: @dropdown-shadow;
451 box-shadow: @dropdown-shadow;
442
452
443 .submenu-title {
453 .submenu-title {
444 font-weight: bold;
454 font-weight: bold;
445 }
455 }
446
456
447 .submenu-title:not(:first-of-type) {
457 .submenu-title:not(:first-of-type) {
448 padding-top: 10px;
458 padding-top: 10px;
449 }
459 }
450
460
451 &.submenu {
461 &.submenu {
452 min-width: 200px;
462 min-width: 200px;
453
463
454 ol {
464 ol {
455 padding:0;
465 padding:0;
456 }
466 }
457
467
458 li {
468 li {
459 display: block;
469 display: block;
460 margin: 0;
470 margin: 0;
461 padding: .2em .5em;
471 padding: .2em .5em;
462 line-height: 1em;
472 line-height: 1em;
463
473
464 background-color: #fff;
474 background-color: #fff;
465 list-style-type: none;
475 list-style-type: none;
466
476
467 a {
477 a {
468 padding: 4px;
478 padding: 4px;
469 color: @grey4 !important;
479 color: @grey4 !important;
470 border-bottom: none;
480 border-bottom: none;
471 }
481 }
472 }
482 }
473 li:not(.submenu-title) a:hover{
483 li:not(.submenu-title) a:hover{
474 color: @grey2 !important;
484 color: @grey2 !important;
475 }
485 }
476 }
486 }
477 }
487 }
478
488
479
489
480 // Header Repository Switcher
490 // Header Repository Switcher
481 // Select2 Dropdown
491 // Select2 Dropdown
482 #select2-drop.select2-drop.repo-switcher-dropdown {
492 #select2-drop.select2-drop.repo-switcher-dropdown {
483 width: auto !important;
493 width: auto !important;
484 margin-top: 5px;
494 margin-top: 5px;
485 padding: 1em 0;
495 padding: 1em 0;
486 text-align: left;
496 text-align: left;
487 .border-radius-bottom(@border-radius);
497 .border-radius-bottom(@border-radius);
488 border-color: transparent;
498 border-color: transparent;
489 color: @grey6;
499 color: @grey6;
490 background-color: @grey2;
500 background-color: @grey2;
491
501
492 input {
502 input {
493 min-width: 90%;
503 min-width: 90%;
494 }
504 }
495
505
496 ul.select2-result-sub {
506 ul.select2-result-sub {
497
507
498 li {
508 li {
499 line-height: 1em;
509 line-height: 1em;
500
510
501 &:hover,
511 &:hover,
502 &.select2-highlighted {
512 &.select2-highlighted {
503 background-color: @grey3;
513 background-color: @grey3;
504 }
514 }
505 }
515 }
506
516
507 &:before { content: none; }
517 &:before { content: none; }
508 }
518 }
509
519
510 ul.select2-results {
520 ul.select2-results {
511 min-width: 200px;
521 min-width: 200px;
512 margin: 0;
522 margin: 0;
513 padding: 0;
523 padding: 0;
514 list-style-type: none;
524 list-style-type: none;
515 overflow-x: visible;
525 overflow-x: visible;
516 overflow-y: scroll;
526 overflow-y: scroll;
517
527
518 li {
528 li {
519 padding: 0 8px;
529 padding: 0 8px;
520 line-height: 1em;
530 line-height: 1em;
521 color: @grey6;
531 color: @grey6;
522
532
523 &>.select2-result-label {
533 &>.select2-result-label {
524 padding: 8px 0;
534 padding: 8px 0;
525 border-bottom: @border-thickness solid @grey3;
535 border-bottom: @border-thickness solid @grey3;
526 white-space: nowrap;
536 white-space: nowrap;
527 color: @grey5;
537 color: @grey5;
528 cursor: pointer;
538 cursor: pointer;
529 }
539 }
530
540
531 &.select2-result-with-children {
541 &.select2-result-with-children {
532 margin: 0;
542 margin: 0;
533 padding: 0;
543 padding: 0;
534 }
544 }
535
545
536 &.select2-result-unselectable > .select2-result-label {
546 &.select2-result-unselectable > .select2-result-label {
537 margin: 0 8px;
547 margin: 0 8px;
538 }
548 }
539
549
540 }
550 }
541 }
551 }
542
552
543 ul.select2-result-sub {
553 ul.select2-result-sub {
544 margin: 0;
554 margin: 0;
545 padding: 0;
555 padding: 0;
546
556
547 li {
557 li {
548 display: block;
558 display: block;
549 margin: 0;
559 margin: 0;
550 border-right: none;
560 border-right: none;
551 line-height: 1em;
561 line-height: 1em;
552 font-family: @text-light;
562 font-family: @text-light;
553 color: @grey2;
563 color: @grey2;
554 list-style-type: none;
564 list-style-type: none;
555
565
556 &:hover {
566 &:hover {
557 background-color: @grey3;
567 background-color: @grey3;
558 }
568 }
559 }
569 }
560 }
570 }
561 }
571 }
562
572
563
573
564 #context-bar {
574 #context-bar {
565 display: block;
575 display: block;
566 margin: 0 auto 20px 0;
576 margin: 0 auto 20px 0;
567 padding: 0 @header-padding;
577 padding: 0 @header-padding;
568 background-color: @grey7;
578 background-color: @grey7;
569 border-bottom: 1px solid @grey5;
579 border-bottom: 1px solid @grey5;
570
580
571 .clear {
581 .clear {
572 clear: both;
582 clear: both;
573 }
583 }
574 }
584 }
575
585
576 ul#context-pages {
586 ul#context-pages {
577 li {
587 li {
578 list-style-type: none;
588 list-style-type: none;
579
589
580 a {
590 a {
581 color: @grey2;
591 color: @grey2;
582
592
583 &:hover {
593 &:hover {
584 color: @grey1;
594 color: @grey1;
585 }
595 }
586 }
596 }
587
597
588 &.active {
598 &.active {
589 // special case, non-variable color
599 // special case, non-variable color
590 border-bottom: 2px solid @rcblue;
600 border-bottom: 2px solid @rcblue;
591
601
592 a {
602 a {
593 color: @rcblue;
603 color: @rcblue;
594 }
604 }
595 }
605 }
596 }
606 }
597 }
607 }
598
608
599 // PAGINATION
609 // PAGINATION
600
610
601 .pagination {
611 .pagination {
602 border: @border-thickness solid @grey5;
612 border: @border-thickness solid @grey5;
603 color: @grey2;
613 color: @grey2;
604 box-shadow: @button-shadow;
614 box-shadow: @button-shadow;
605
615
606 .current {
616 .current {
607 color: @grey4;
617 color: @grey4;
608 }
618 }
609 }
619 }
610
620
611 .dataTables_processing {
621 .dataTables_processing {
612 text-align: center;
622 text-align: center;
613 font-size: 1.1em;
623 font-size: 1.1em;
614 position: relative;
624 position: relative;
615 top: 95px;
625 top: 95px;
616 }
626 }
617
627
618 .dataTables_paginate, .pagination-wh {
628 .dataTables_paginate, .pagination-wh {
619 text-align: left;
629 text-align: left;
620 display: inline-block;
630 display: inline-block;
621 border-left: 1px solid @grey5;
631 border-left: 1px solid @grey5;
622 float: none;
632 float: none;
623 overflow: hidden;
633 overflow: hidden;
624 box-shadow: @button-shadow;
634 box-shadow: @button-shadow;
625
635
626 .paginate_button, .pager_curpage,
636 .paginate_button, .pager_curpage,
627 .pager_link, .pg-previous, .pg-next, .pager_dotdot {
637 .pager_link, .pg-previous, .pg-next, .pager_dotdot {
628 display: inline-block;
638 display: inline-block;
629 padding: @menupadding/4 @menupadding;
639 padding: @menupadding/4 @menupadding;
630 border: 1px solid @grey5;
640 border: 1px solid @grey5;
631 border-left: 0;
641 border-left: 0;
632 color: @grey2;
642 color: @grey2;
633 cursor: pointer;
643 cursor: pointer;
634 float: left;
644 float: left;
635
645
636 &:hover {
646 &:hover {
637 color: @rcdarkblue;
647 color: @rcdarkblue;
638 }
648 }
639 }
649 }
640
650
641 .paginate_button.disabled,
651 .paginate_button.disabled,
642 .disabled {
652 .disabled {
643 color: @grey3;
653 color: @grey3;
644 cursor: default;
654 cursor: default;
645 opacity: 0.5;
655 opacity: 0.5;
646 }
656 }
647
657
648 .paginate_button.current, .pager_curpage {
658 .paginate_button.current, .pager_curpage {
649 background: @rcblue;
659 background: @rcblue;
650 border-color: @rcblue;
660 border-color: @rcblue;
651 color: @white;
661 color: @white;
652 }
662 }
653
663
654 .ellipsis {
664 .ellipsis {
655 display: inline-block;
665 display: inline-block;
656 text-align: left;
666 text-align: left;
657 padding: @menupadding/4 @menupadding;
667 padding: @menupadding/4 @menupadding;
658 border: 1px solid @grey5;
668 border: 1px solid @grey5;
659 border-left: 0;
669 border-left: 0;
660 float: left;
670 float: left;
661 }
671 }
662 }
672 }
663
673
664 // SIDEBAR
674 // SIDEBAR
665
675
666 .sidebar {
676 .sidebar {
667 .block-left;
677 .block-left;
668 clear: left;
678 clear: left;
669 max-width: @sidebar-width;
679 max-width: @sidebar-width;
670 margin-right: @sidebarpadding;
680 margin-right: @sidebarpadding;
671 padding-right: @sidebarpadding;
681 padding-right: @sidebarpadding;
672 font-family: @text-regular;
682 font-family: @text-regular;
673 color: @grey1;
683 color: @grey1;
674
684
675 .nav-pills {
685 .nav-pills {
676 margin: 0;
686 margin: 0;
677 }
687 }
678
688
679 .nav {
689 .nav {
680 list-style: none;
690 list-style: none;
681 padding: 0;
691 padding: 0;
682
692
683 li {
693 li {
684 padding-bottom: @menupadding;
694 padding-bottom: @menupadding;
685 line-height: 1em;
695 line-height: 1em;
686 color: @grey4;
696 color: @grey4;
687 list-style-type: none;
697 list-style-type: none;
688
698
689 &.active a {
699 &.active a {
690 color: @grey2;
700 color: @grey2;
691 }
701 }
692
702
693 a {
703 a {
694 color: @grey4;
704 color: @grey4;
695 }
705 }
696 }
706 }
697
707
698 }
708 }
699 }
709 }
700
710
701 .main_filter_help_box {
711 .main_filter_help_box {
702 padding: 7px 7px;
712 padding: 7px 7px;
703 display: inline-block;
713 display: inline-block;
704 vertical-align: top;
714 vertical-align: top;
705 background: inherit;
715 background: inherit;
706 position: absolute;
716 position: absolute;
707 right: 0;
717 right: 0;
708 top: 9px;
718 top: 9px;
709 }
719 }
710
720
711 .main_filter_input_box {
721 .main_filter_input_box {
712 display: inline-block;
722 display: inline-block;
713
723
714 .searchItems {
724 .searchItems {
715 display:flex;
725 display:flex;
716 background: @black;
726 background: @black;
717 padding: 0px;
727 padding: 0px;
718 border-radius: 3px;
728 border-radius: 3px;
719 border: 1px solid @black;
729 border: 1px solid @black;
720
730
721 a {
731 a {
722 border: none !important;
732 border: none !important;
723 }
733 }
724 }
734 }
725
735
726 .searchTag {
736 .searchTag {
727 line-height: 28px;
737 line-height: 28px;
728 padding: 0 5px;
738 padding: 0 5px;
729
739
730 .tag {
740 .tag {
731 color: @grey5;
741 color: @grey5;
732 border-color: @grey2;
742 border-color: @grey2;
733 background: @grey1;
743 background: @grey1;
734 }
744 }
735 }
745 }
736
746
737 .searchTagFilter {
747 .searchTagFilter {
738 background-color: @black !important;
748 background-color: @black !important;
739 margin-right: 0;
749 margin-right: 0;
740 }
750 }
741
751
742 .searchTagHelp {
752 .searchTagHelp {
743 background-color: @grey1 !important;
753 background-color: @grey1 !important;
744 margin: 0;
754 margin: 0;
745 }
755 }
746 .searchTagHelp:hover {
756 .searchTagHelp:hover {
747 background-color: @grey1 !important;
757 background-color: @grey1 !important;
748 }
758 }
749 .searchTagInput {
759 .searchTagInput {
750 background-color: @grey1 !important;
760 background-color: @grey1 !important;
751 margin-right: 0;
761 margin-right: 0;
752 }
762 }
753 }
763 }
754
764
755 .main_filter_box {
765 .main_filter_box {
756 margin: 9px 0 0 0;
766 margin: 9px 0 0 0;
757 }
767 }
758
768
759 #main_filter_help {
769 #main_filter_help {
760 background: @grey1;
770 background: @grey1;
761 border: 1px solid black;
771 border: 1px solid black;
762 position: absolute;
772 position: absolute;
763 white-space: pre;
773 white-space: pre;
764 z-index: 9999;
774 z-index: 9999;
765 color: @nav-grey;
775 color: @nav-grey;
766 padding: 0 10px;
776 padding: 0 10px;
767 }
777 }
768
778
769 input {
779 input {
770
780
771 &.main_filter_input {
781 &.main_filter_input {
772 padding: 5px 10px;
782 padding: 5px 10px;
773 min-width: 340px;
783 min-width: 340px;
774 color: @grey7;
784 color: @grey7;
775 background: @black;
785 background: @black;
776 min-height: 18px;
786 min-height: 18px;
777 border: 0;
787 border: 0;
778
788
779 &:active {
789 &:active {
780 color: @grey2 !important;
790 color: @grey2 !important;
781 background: white !important;
791 background: white !important;
782 }
792 }
783 &:focus {
793 &:focus {
784 color: @grey2 !important;
794 color: @grey2 !important;
785 background: white !important;
795 background: white !important;
786 }
796 }
787 }
797 }
788 }
798 }
789
799
790
800
791
801
792 .main_filter_input::placeholder {
802 .main_filter_input::placeholder {
793 color: @nav-grey;
803 color: @nav-grey;
794 opacity: 1;
804 opacity: 1;
795 }
805 }
796
806
797 .notice-box {
807 .notice-box {
798 display:block !important;
808 display:block !important;
799 padding: 9px 0 !important;
809 padding: 9px 0 !important;
800 }
810 }
801
811
802 .menulabel-notice {
812 .menulabel-notice {
803 border: 1px solid @color5;
813 border: 1px solid @color5;
804 padding:7px 10px;
814 padding:7px 10px;
805 color: @color5;
815 color: @color5;
806 }
816 }
@@ -1,1061 +1,1063 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-60x60.png')}" alt="RhodeCode"/></a>
12 <a href="${h.route_path('home')}"><img src="${h.asset('images/rhodecode-logo-white-60x60.png')}" alt="RhodeCode"/></a>
13 </div>
13 </div>
14 % if c.rhodecode_name:
14 % if c.rhodecode_name:
15 <div class="branding">
15 <div class="branding">
16 <a href="${h.route_path('home')}">${h.branding(c.rhodecode_name)}</a>
16 <a href="${h.route_path('home')}">${h.branding(c.rhodecode_name)}</a>
17 </div>
17 </div>
18 % endif
18 % endif
19 </div>
19 </div>
20 <!-- MENU BAR NAV -->
20 <!-- MENU BAR NAV -->
21 ${self.menu_bar_nav()}
21 ${self.menu_bar_nav()}
22 <!-- END MENU BAR NAV -->
22 <!-- END MENU BAR NAV -->
23 </div>
23 </div>
24 </div>
24 </div>
25 ${self.menu_bar_subnav()}
25 ${self.menu_bar_subnav()}
26 <!-- END HEADER -->
26 <!-- END HEADER -->
27
27
28 <!-- CONTENT -->
28 <!-- CONTENT -->
29 <div id="content" class="wrapper">
29 <div id="content" class="wrapper">
30
30
31 <rhodecode-toast id="notifications"></rhodecode-toast>
31 <rhodecode-toast id="notifications"></rhodecode-toast>
32
32
33 <div class="main">
33 <div class="main">
34 ${next.main()}
34 ${next.main()}
35 </div>
35 </div>
36 </div>
36 </div>
37 <!-- END CONTENT -->
37 <!-- END CONTENT -->
38
38
39 </div>
39 </div>
40 <!-- FOOTER -->
40 <!-- FOOTER -->
41 <div id="footer">
41 <div id="footer">
42 <div id="footer-inner" class="title wrapper">
42 <div id="footer-inner" class="title wrapper">
43 <div>
43 <div>
44 <p class="footer-link-right">
44 <p class="footer-link-right">
45 % if c.visual.show_version:
45 % if c.visual.show_version:
46 RhodeCode Enterprise ${c.rhodecode_version} ${c.rhodecode_edition}
46 RhodeCode Enterprise ${c.rhodecode_version} ${c.rhodecode_edition}
47 % endif
47 % endif
48 &copy; 2010-${h.datetime.today().year}, <a href="${h.route_url('rhodecode_official')}" target="_blank">RhodeCode GmbH</a>. All rights reserved.
48 &copy; 2010-${h.datetime.today().year}, <a href="${h.route_url('rhodecode_official')}" target="_blank">RhodeCode GmbH</a>. All rights reserved.
49 % if c.visual.rhodecode_support_url:
49 % if c.visual.rhodecode_support_url:
50 <a href="${c.visual.rhodecode_support_url}" target="_blank">${_('Support')}</a>
50 <a href="${c.visual.rhodecode_support_url}" target="_blank">${_('Support')}</a>
51 % endif
51 % endif
52 </p>
52 </p>
53 <% sid = 'block' if request.GET.get('showrcid') else 'none' %>
53 <% sid = 'block' if request.GET.get('showrcid') else 'none' %>
54 <p class="server-instance" style="display:${sid}">
54 <p class="server-instance" style="display:${sid}">
55 ## display hidden instance ID if specially defined
55 ## display hidden instance ID if specially defined
56 % if c.rhodecode_instanceid:
56 % if c.rhodecode_instanceid:
57 ${_('RhodeCode instance id: {}').format(c.rhodecode_instanceid)}
57 ${_('RhodeCode instance id: {}').format(c.rhodecode_instanceid)}
58 % endif
58 % endif
59 </p>
59 </p>
60 </div>
60 </div>
61 </div>
61 </div>
62 </div>
62 </div>
63
63
64 <!-- END FOOTER -->
64 <!-- END FOOTER -->
65
65
66 ### MAKO DEFS ###
66 ### MAKO DEFS ###
67
67
68 <%def name="menu_bar_subnav()">
68 <%def name="menu_bar_subnav()">
69 </%def>
69 </%def>
70
70
71 <%def name="breadcrumbs(class_='breadcrumbs')">
71 <%def name="breadcrumbs(class_='breadcrumbs')">
72 <div class="${class_}">
72 <div class="${class_}">
73 ${self.breadcrumbs_links()}
73 ${self.breadcrumbs_links()}
74 </div>
74 </div>
75 </%def>
75 </%def>
76
76
77 <%def name="admin_menu(active=None)">
77 <%def name="admin_menu(active=None)">
78 <%
78 <%
79 def is_active(selected):
79 def is_active(selected):
80 if selected == active:
80 if selected == active:
81 return "active"
81 return "active"
82 %>
82 %>
83
83
84 <div id="context-bar">
84 <div id="context-bar">
85 <div class="wrapper">
85 <div class="wrapper">
86 <div class="title">
86 <div class="title">
87 <div class="title-content">
87 <div class="title-content">
88 <div class="title-main">
88 <div class="title-main">
89 % if c.is_super_admin:
89 % if c.is_super_admin:
90 ${_('Super Admin Panel')}
90 ${_('Super Admin Panel')}
91 % else:
91 % else:
92 ${_('Delegated Admin Panel')}
92 ${_('Delegated Admin Panel')}
93 % endif
93 % endif
94 </div>
94 </div>
95 </div>
95 </div>
96 </div>
96 </div>
97
97
98 <ul id="context-pages" class="navigation horizontal-list">
98 <ul id="context-pages" class="navigation horizontal-list">
99
99
100 ## super admin case
100 ## super admin case
101 % if c.is_super_admin:
101 % if c.is_super_admin:
102 <li class="${is_active('audit_logs')}"><a href="${h.route_path('admin_audit_logs')}">${_('Admin audit logs')}</a></li>
102 <li class="${is_active('audit_logs')}"><a href="${h.route_path('admin_audit_logs')}">${_('Admin audit logs')}</a></li>
103 <li class="${is_active('repositories')}"><a href="${h.route_path('repos')}">${_('Repositories')}</a></li>
103 <li class="${is_active('repositories')}"><a href="${h.route_path('repos')}">${_('Repositories')}</a></li>
104 <li class="${is_active('repository_groups')}"><a href="${h.route_path('repo_groups')}">${_('Repository groups')}</a></li>
104 <li class="${is_active('repository_groups')}"><a href="${h.route_path('repo_groups')}">${_('Repository groups')}</a></li>
105 <li class="${is_active('users')}"><a href="${h.route_path('users')}">${_('Users')}</a></li>
105 <li class="${is_active('users')}"><a href="${h.route_path('users')}">${_('Users')}</a></li>
106 <li class="${is_active('user_groups')}"><a href="${h.route_path('user_groups')}">${_('User groups')}</a></li>
106 <li class="${is_active('user_groups')}"><a href="${h.route_path('user_groups')}">${_('User groups')}</a></li>
107 <li class="${is_active('permissions')}"><a href="${h.route_path('admin_permissions_application')}">${_('Permissions')}</a></li>
107 <li class="${is_active('permissions')}"><a href="${h.route_path('admin_permissions_application')}">${_('Permissions')}</a></li>
108 <li class="${is_active('authentication')}"><a href="${h.route_path('auth_home', traverse='')}">${_('Authentication')}</a></li>
108 <li class="${is_active('authentication')}"><a href="${h.route_path('auth_home', traverse='')}">${_('Authentication')}</a></li>
109 <li class="${is_active('integrations')}"><a href="${h.route_path('global_integrations_home')}">${_('Integrations')}</a></li>
109 <li class="${is_active('integrations')}"><a href="${h.route_path('global_integrations_home')}">${_('Integrations')}</a></li>
110 <li class="${is_active('defaults')}"><a href="${h.route_path('admin_defaults_repositories')}">${_('Defaults')}</a></li>
110 <li class="${is_active('defaults')}"><a href="${h.route_path('admin_defaults_repositories')}">${_('Defaults')}</a></li>
111 <li class="${is_active('settings')}"><a href="${h.route_path('admin_settings')}">${_('Settings')}</a></li>
111 <li class="${is_active('settings')}"><a href="${h.route_path('admin_settings')}">${_('Settings')}</a></li>
112
112
113 ## delegated admin
113 ## delegated admin
114 % elif c.is_delegated_admin:
114 % elif c.is_delegated_admin:
115 <%
115 <%
116 repositories=c.auth_user.repositories_admin or c.can_create_repo
116 repositories=c.auth_user.repositories_admin or c.can_create_repo
117 repository_groups=c.auth_user.repository_groups_admin or c.can_create_repo_group
117 repository_groups=c.auth_user.repository_groups_admin or c.can_create_repo_group
118 user_groups=c.auth_user.user_groups_admin or c.can_create_user_group
118 user_groups=c.auth_user.user_groups_admin or c.can_create_user_group
119 %>
119 %>
120
120
121 %if repositories:
121 %if repositories:
122 <li class="${is_active('repositories')} local-admin-repos"><a href="${h.route_path('repos')}">${_('Repositories')}</a></li>
122 <li class="${is_active('repositories')} local-admin-repos"><a href="${h.route_path('repos')}">${_('Repositories')}</a></li>
123 %endif
123 %endif
124 %if repository_groups:
124 %if repository_groups:
125 <li class="${is_active('repository_groups')} local-admin-repo-groups"><a href="${h.route_path('repo_groups')}">${_('Repository groups')}</a></li>
125 <li class="${is_active('repository_groups')} local-admin-repo-groups"><a href="${h.route_path('repo_groups')}">${_('Repository groups')}</a></li>
126 %endif
126 %endif
127 %if user_groups:
127 %if user_groups:
128 <li class="${is_active('user_groups')} local-admin-user-groups"><a href="${h.route_path('user_groups')}">${_('User groups')}</a></li>
128 <li class="${is_active('user_groups')} local-admin-user-groups"><a href="${h.route_path('user_groups')}">${_('User groups')}</a></li>
129 %endif
129 %endif
130 % endif
130 % endif
131 </ul>
131 </ul>
132
132
133 </div>
133 </div>
134 <div class="clear"></div>
134 <div class="clear"></div>
135 </div>
135 </div>
136 </%def>
136 </%def>
137
137
138 <%def name="dt_info_panel(elements)">
138 <%def name="dt_info_panel(elements)">
139 <dl class="dl-horizontal">
139 <dl class="dl-horizontal">
140 %for dt, dd, title, show_items in elements:
140 %for dt, dd, title, show_items in elements:
141 <dt>${dt}:</dt>
141 <dt>${dt}:</dt>
142 <dd title="${h.tooltip(title)}">
142 <dd title="${h.tooltip(title)}">
143 %if callable(dd):
143 %if callable(dd):
144 ## allow lazy evaluation of elements
144 ## allow lazy evaluation of elements
145 ${dd()}
145 ${dd()}
146 %else:
146 %else:
147 ${dd}
147 ${dd}
148 %endif
148 %endif
149 %if show_items:
149 %if show_items:
150 <span class="btn-collapse" data-toggle="item-${h.md5_safe(dt)[:6]}-details">${_('Show More')} </span>
150 <span class="btn-collapse" data-toggle="item-${h.md5_safe(dt)[:6]}-details">${_('Show More')} </span>
151 %endif
151 %endif
152 </dd>
152 </dd>
153
153
154 %if show_items:
154 %if show_items:
155 <div class="collapsable-content" data-toggle="item-${h.md5_safe(dt)[:6]}-details" style="display: none">
155 <div class="collapsable-content" data-toggle="item-${h.md5_safe(dt)[:6]}-details" style="display: none">
156 %for item in show_items:
156 %for item in show_items:
157 <dt></dt>
157 <dt></dt>
158 <dd>${item}</dd>
158 <dd>${item}</dd>
159 %endfor
159 %endfor
160 </div>
160 </div>
161 %endif
161 %endif
162
162
163 %endfor
163 %endfor
164 </dl>
164 </dl>
165 </%def>
165 </%def>
166
166
167 <%def name="gravatar(email, size=16)">
167 <%def name="gravatar(email, size=16)">
168 <%
168 <%
169 if (size > 16):
169 if (size > 16):
170 gravatar_class = 'gravatar gravatar-large'
170 gravatar_class = 'gravatar gravatar-large'
171 else:
171 else:
172 gravatar_class = 'gravatar'
172 gravatar_class = 'gravatar'
173 %>
173 %>
174 <%doc>
174 <%doc>
175 TODO: johbo: For now we serve double size images to make it smooth
175 TODO: johbo: For now we serve double size images to make it smooth
176 for retina. This is how it worked until now. Should be replaced
176 for retina. This is how it worked until now. Should be replaced
177 with a better solution at some point.
177 with a better solution at some point.
178 </%doc>
178 </%doc>
179 <img class="${gravatar_class}" src="${h.gravatar_url(email, size * 2)}" height="${size}" width="${size}">
179 <img class="${gravatar_class}" src="${h.gravatar_url(email, size * 2)}" height="${size}" width="${size}">
180 </%def>
180 </%def>
181
181
182
182
183 <%def name="gravatar_with_user(contact, size=16, show_disabled=False)">
183 <%def name="gravatar_with_user(contact, size=16, show_disabled=False)">
184 <% email = h.email_or_none(contact) %>
184 <% email = h.email_or_none(contact) %>
185 <div class="rc-user tooltip" title="${h.tooltip(h.author_string(email))}">
185 <div class="rc-user tooltip" title="${h.tooltip(h.author_string(email))}">
186 ${self.gravatar(email, size)}
186 ${self.gravatar(email, size)}
187 <span class="${'user user-disabled' if show_disabled else 'user'}"> ${h.link_to_user(contact)}</span>
187 <span class="${'user user-disabled' if show_disabled else 'user'}"> ${h.link_to_user(contact)}</span>
188 </div>
188 </div>
189 </%def>
189 </%def>
190
190
191
191
192 <%def name="repo_page_title(repo_instance)">
192 <%def name="repo_page_title(repo_instance)">
193 <div class="title-content repo-title">
193 <div class="title-content repo-title">
194
194
195 <div class="title-main">
195 <div class="title-main">
196 ## SVN/HG/GIT icons
196 ## SVN/HG/GIT icons
197 %if h.is_hg(repo_instance):
197 %if h.is_hg(repo_instance):
198 <i class="icon-hg"></i>
198 <i class="icon-hg"></i>
199 %endif
199 %endif
200 %if h.is_git(repo_instance):
200 %if h.is_git(repo_instance):
201 <i class="icon-git"></i>
201 <i class="icon-git"></i>
202 %endif
202 %endif
203 %if h.is_svn(repo_instance):
203 %if h.is_svn(repo_instance):
204 <i class="icon-svn"></i>
204 <i class="icon-svn"></i>
205 %endif
205 %endif
206
206
207 ## public/private
207 ## public/private
208 %if repo_instance.private:
208 %if repo_instance.private:
209 <i class="icon-repo-private"></i>
209 <i class="icon-repo-private"></i>
210 %else:
210 %else:
211 <i class="icon-repo-public"></i>
211 <i class="icon-repo-public"></i>
212 %endif
212 %endif
213
213
214 ## repo name with group name
214 ## repo name with group name
215 ${h.breadcrumb_repo_link(repo_instance)}
215 ${h.breadcrumb_repo_link(repo_instance)}
216
216
217 ## Context Actions
217 ## Context Actions
218 <div class="pull-right">
218 <div class="pull-right">
219 %if c.rhodecode_user.username != h.DEFAULT_USER:
219 %if c.rhodecode_user.username != h.DEFAULT_USER:
220 <a href="${h.route_path('atom_feed_home', repo_name=c.rhodecode_db_repo.repo_uid, _query=dict(auth_token=c.rhodecode_user.feed_token))}" title="${_('RSS Feed')}" class="btn btn-sm"><i class="icon-rss-sign"></i>RSS</a>
220 <a href="${h.route_path('atom_feed_home', repo_name=c.rhodecode_db_repo.repo_uid, _query=dict(auth_token=c.rhodecode_user.feed_token))}" title="${_('RSS Feed')}" class="btn btn-sm"><i class="icon-rss-sign"></i>RSS</a>
221
221
222 <a href="#WatchRepo" onclick="toggleFollowingRepo(this, templateContext.repo_id); return false" title="${_('Watch this Repository and actions on it in your personalized journal')}" class="btn btn-sm ${('watching' if c.repository_is_user_following else '')}">
222 <a href="#WatchRepo" onclick="toggleFollowingRepo(this, templateContext.repo_id); return false" title="${_('Watch this Repository and actions on it in your personalized journal')}" class="btn btn-sm ${('watching' if c.repository_is_user_following else '')}">
223 % if c.repository_is_user_following:
223 % if c.repository_is_user_following:
224 <i class="icon-eye-off"></i>${_('Unwatch')}
224 <i class="icon-eye-off"></i>${_('Unwatch')}
225 % else:
225 % else:
226 <i class="icon-eye"></i>${_('Watch')}
226 <i class="icon-eye"></i>${_('Watch')}
227 % endif
227 % endif
228
228
229 </a>
229 </a>
230 %else:
230 %else:
231 <a href="${h.route_path('atom_feed_home', repo_name=c.rhodecode_db_repo.repo_uid)}" title="${_('RSS Feed')}" class="btn btn-sm"><i class="icon-rss-sign"></i>RSS</a>
231 <a href="${h.route_path('atom_feed_home', repo_name=c.rhodecode_db_repo.repo_uid)}" title="${_('RSS Feed')}" class="btn btn-sm"><i class="icon-rss-sign"></i>RSS</a>
232 %endif
232 %endif
233 </div>
233 </div>
234
234
235 </div>
235 </div>
236
236
237 ## FORKED
237 ## FORKED
238 %if repo_instance.fork:
238 %if repo_instance.fork:
239 <p class="discreet">
239 <p class="discreet">
240 <i class="icon-code-fork"></i> ${_('Fork of')}
240 <i class="icon-code-fork"></i> ${_('Fork of')}
241 ${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))}
241 ${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))}
242 </p>
242 </p>
243 %endif
243 %endif
244
244
245 ## IMPORTED FROM REMOTE
245 ## IMPORTED FROM REMOTE
246 %if repo_instance.clone_uri:
246 %if repo_instance.clone_uri:
247 <p class="discreet">
247 <p class="discreet">
248 <i class="icon-code-fork"></i> ${_('Clone from')}
248 <i class="icon-code-fork"></i> ${_('Clone from')}
249 <a href="${h.safe_str(h.hide_credentials(repo_instance.clone_uri))}">${h.hide_credentials(repo_instance.clone_uri)}</a>
249 <a href="${h.safe_str(h.hide_credentials(repo_instance.clone_uri))}">${h.hide_credentials(repo_instance.clone_uri)}</a>
250 </p>
250 </p>
251 %endif
251 %endif
252
252
253 ## LOCKING STATUS
253 ## LOCKING STATUS
254 %if repo_instance.locked[0]:
254 %if repo_instance.locked[0]:
255 <p class="locking_locked discreet">
255 <p class="locking_locked discreet">
256 <i class="icon-repo-lock"></i>
256 <i class="icon-repo-lock"></i>
257 ${_('Repository locked by %(user)s') % {'user': h.person_by_id(repo_instance.locked[0])}}
257 ${_('Repository locked by %(user)s') % {'user': h.person_by_id(repo_instance.locked[0])}}
258 </p>
258 </p>
259 %elif repo_instance.enable_locking:
259 %elif repo_instance.enable_locking:
260 <p class="locking_unlocked discreet">
260 <p class="locking_unlocked discreet">
261 <i class="icon-repo-unlock"></i>
261 <i class="icon-repo-unlock"></i>
262 ${_('Repository not locked. Pull repository to lock it.')}
262 ${_('Repository not locked. Pull repository to lock it.')}
263 </p>
263 </p>
264 %endif
264 %endif
265
265
266 </div>
266 </div>
267 </%def>
267 </%def>
268
268
269 <%def name="repo_menu(active=None)">
269 <%def name="repo_menu(active=None)">
270 <%
270 <%
271 def is_active(selected):
271 def is_active(selected):
272 if selected == active:
272 if selected == active:
273 return "active"
273 return "active"
274 ## determine if we have "any" option available
274 ## determine if we have "any" option available
275 can_lock = h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name) and c.rhodecode_db_repo.enable_locking
275 can_lock = h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name) and c.rhodecode_db_repo.enable_locking
276 has_actions = can_lock
276 has_actions = can_lock
277
277
278 %>
278 %>
279 % if c.rhodecode_db_repo.archived:
279 % if c.rhodecode_db_repo.archived:
280 <div class="alert alert-warning text-center">
280 <div class="alert alert-warning text-center">
281 <strong>${_('This repository has been archived. It is now read-only.')}</strong>
281 <strong>${_('This repository has been archived. It is now read-only.')}</strong>
282 </div>
282 </div>
283 % endif
283 % endif
284
284
285 <!--- REPO CONTEXT BAR -->
285 <!--- REPO CONTEXT BAR -->
286 <div id="context-bar">
286 <div id="context-bar">
287 <div class="wrapper">
287 <div class="wrapper">
288
288
289 <div class="title">
289 <div class="title">
290 ${self.repo_page_title(c.rhodecode_db_repo)}
290 ${self.repo_page_title(c.rhodecode_db_repo)}
291 </div>
291 </div>
292
292
293 <ul id="context-pages" class="navigation horizontal-list">
293 <ul id="context-pages" class="navigation horizontal-list">
294 <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>
294 <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>
295 <li class="${is_active('commits')}"><a class="menulink" href="${h.route_path('repo_commits', repo_name=c.repo_name)}"><div class="menulabel">${_('Commits')}</div></a></li>
295 <li class="${is_active('commits')}"><a class="menulink" href="${h.route_path('repo_commits', repo_name=c.repo_name)}"><div class="menulabel">${_('Commits')}</div></a></li>
296 <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>
296 <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>
297 <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>
297 <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>
298
298
299 ## TODO: anderson: ideally it would have a function on the scm_instance "enable_pullrequest() and enable_fork()"
299 ## TODO: anderson: ideally it would have a function on the scm_instance "enable_pullrequest() and enable_fork()"
300 %if c.rhodecode_db_repo.repo_type in ['git','hg']:
300 %if c.rhodecode_db_repo.repo_type in ['git','hg']:
301 <li class="${is_active('showpullrequest')}">
301 <li class="${is_active('showpullrequest')}">
302 <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)}">
302 <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)}">
303 <div class="menulabel">
303 <div class="menulabel">
304 %if c.repository_pull_requests == 1:
304 ${_('Pull Requests')} <span class="menulink-counter">${c.repository_pull_requests}</span>
305 ${_('Pull Request')} ${c.repository_pull_requests}
306 %else:
307 ${_('Pull Requests')} ${c.repository_pull_requests}
308 %endif
309 </div>
305 </div>
310 </a>
306 </a>
311 </li>
307 </li>
312 %endif
308 %endif
313
309
314 <li class="${is_active('artifacts')}"><a class="menulink" href="${h.route_path('repo_artifacts_list',repo_name=c.repo_name)}"><div class="menulabel">${_('Artifacts')}</div></a></li>
310 <li class="${is_active('artifacts')}">
311 <a class="menulink" href="${h.route_path('repo_artifacts_list',repo_name=c.repo_name)}">
312 <div class="menulabel">
313 ${_('Artifacts')} <span class="menulink-counter">${c.repository_artifacts}</span>
314 </div>
315 </a>
316 </li>
315
317
316 %if h.HasRepoPermissionAll('repository.admin')(c.repo_name):
318 %if h.HasRepoPermissionAll('repository.admin')(c.repo_name):
317 <li class="${is_active('settings')}"><a class="menulink" href="${h.route_path('edit_repo',repo_name=c.repo_name)}"><div class="menulabel">${_('Repository Settings')}</div></a></li>
319 <li class="${is_active('settings')}"><a class="menulink" href="${h.route_path('edit_repo',repo_name=c.repo_name)}"><div class="menulabel">${_('Repository Settings')}</div></a></li>
318 %endif
320 %endif
319
321
320 <li class="${is_active('options')}">
322 <li class="${is_active('options')}">
321 % if has_actions:
323 % if has_actions:
322 <a class="menulink dropdown">
324 <a class="menulink dropdown">
323 <div class="menulabel">${_('Options')}<div class="show_more"></div></div>
325 <div class="menulabel">${_('Options')}<div class="show_more"></div></div>
324 </a>
326 </a>
325 <ul class="submenu">
327 <ul class="submenu">
326 %if can_lock:
328 %if can_lock:
327 %if c.rhodecode_db_repo.locked[0]:
329 %if c.rhodecode_db_repo.locked[0]:
328 <li><a class="locking_del" href="${h.route_path('repo_edit_toggle_locking',repo_name=c.repo_name)}">${_('Unlock Repository')}</a></li>
330 <li><a class="locking_del" href="${h.route_path('repo_edit_toggle_locking',repo_name=c.repo_name)}">${_('Unlock Repository')}</a></li>
329 %else:
331 %else:
330 <li><a class="locking_add" href="${h.route_path('repo_edit_toggle_locking',repo_name=c.repo_name)}">${_('Lock Repository')}</a></li>
332 <li><a class="locking_add" href="${h.route_path('repo_edit_toggle_locking',repo_name=c.repo_name)}">${_('Lock Repository')}</a></li>
331 %endif
333 %endif
332 %endif
334 %endif
333 </ul>
335 </ul>
334 % else:
336 % else:
335 <a class="menulink disabled">
337 <a class="menulink disabled">
336 <div class="menulabel">${_('Options')}<div class="show_more"></div></div>
338 <div class="menulabel">${_('Options')}<div class="show_more"></div></div>
337 </a>
339 </a>
338 % endif
340 % endif
339 </li>
341 </li>
340
342
341 </ul>
343 </ul>
342 </div>
344 </div>
343 <div class="clear"></div>
345 <div class="clear"></div>
344 </div>
346 </div>
345
347
346 <!--- REPO END CONTEXT BAR -->
348 <!--- REPO END CONTEXT BAR -->
347
349
348 </%def>
350 </%def>
349
351
350 <%def name="repo_group_page_title(repo_group_instance)">
352 <%def name="repo_group_page_title(repo_group_instance)">
351 <div class="title-content">
353 <div class="title-content">
352 <div class="title-main">
354 <div class="title-main">
353 ## Repository Group icon
355 ## Repository Group icon
354 <i class="icon-repo-group"></i>
356 <i class="icon-repo-group"></i>
355
357
356 ## repo name with group name
358 ## repo name with group name
357 ${h.breadcrumb_repo_group_link(repo_group_instance)}
359 ${h.breadcrumb_repo_group_link(repo_group_instance)}
358 </div>
360 </div>
359
361
360 <%namespace name="dt" file="/data_table/_dt_elements.mako"/>
362 <%namespace name="dt" file="/data_table/_dt_elements.mako"/>
361 <div class="repo-group-desc discreet">
363 <div class="repo-group-desc discreet">
362 ${dt.repo_group_desc(repo_group_instance.description_safe, repo_group_instance.personal, c.visual.stylify_metatags)}
364 ${dt.repo_group_desc(repo_group_instance.description_safe, repo_group_instance.personal, c.visual.stylify_metatags)}
363 </div>
365 </div>
364
366
365 </div>
367 </div>
366 </%def>
368 </%def>
367
369
368
370
369 <%def name="repo_group_menu(active=None)">
371 <%def name="repo_group_menu(active=None)">
370 <%
372 <%
371 def is_active(selected):
373 def is_active(selected):
372 if selected == active:
374 if selected == active:
373 return "active"
375 return "active"
374
376
375 gr_name = c.repo_group.group_name if c.repo_group else None
377 gr_name = c.repo_group.group_name if c.repo_group else None
376 # create repositories with write permission on group is set to true
378 # create repositories with write permission on group is set to true
377 group_admin = h.HasRepoGroupPermissionAny('group.admin')(gr_name, 'group admin index page')
379 group_admin = h.HasRepoGroupPermissionAny('group.admin')(gr_name, 'group admin index page')
378
380
379 %>
381 %>
380
382
381
383
382 <!--- REPO GROUP CONTEXT BAR -->
384 <!--- REPO GROUP CONTEXT BAR -->
383 <div id="context-bar">
385 <div id="context-bar">
384 <div class="wrapper">
386 <div class="wrapper">
385 <div class="title">
387 <div class="title">
386 ${self.repo_group_page_title(c.repo_group)}
388 ${self.repo_group_page_title(c.repo_group)}
387 </div>
389 </div>
388
390
389 <ul id="context-pages" class="navigation horizontal-list">
391 <ul id="context-pages" class="navigation horizontal-list">
390 <li class="${is_active('home')}">
392 <li class="${is_active('home')}">
391 <a class="menulink" href="${h.route_path('repo_group_home', repo_group_name=c.repo_group.group_name)}"><div class="menulabel">${_('Group Home')}</div></a>
393 <a class="menulink" href="${h.route_path('repo_group_home', repo_group_name=c.repo_group.group_name)}"><div class="menulabel">${_('Group Home')}</div></a>
392 </li>
394 </li>
393 % if c.is_super_admin or group_admin:
395 % if c.is_super_admin or group_admin:
394 <li class="${is_active('settings')}">
396 <li class="${is_active('settings')}">
395 <a class="menulink" href="${h.route_path('edit_repo_group',repo_group_name=c.repo_group.group_name)}" title="${_('You have admin right to this group, and can edit it')}"><div class="menulabel">${_('Group Settings')}</div></a>
397 <a class="menulink" href="${h.route_path('edit_repo_group',repo_group_name=c.repo_group.group_name)}" title="${_('You have admin right to this group, and can edit it')}"><div class="menulabel">${_('Group Settings')}</div></a>
396 </li>
398 </li>
397 % endif
399 % endif
398
400
399 </ul>
401 </ul>
400 </div>
402 </div>
401 <div class="clear"></div>
403 <div class="clear"></div>
402 </div>
404 </div>
403
405
404 <!--- REPO GROUP CONTEXT BAR -->
406 <!--- REPO GROUP CONTEXT BAR -->
405
407
406 </%def>
408 </%def>
407
409
408
410
409 <%def name="usermenu(active=False)">
411 <%def name="usermenu(active=False)">
410 <%
412 <%
411 not_anonymous = c.rhodecode_user.username != h.DEFAULT_USER
413 not_anonymous = c.rhodecode_user.username != h.DEFAULT_USER
412
414
413 gr_name = c.repo_group.group_name if (hasattr(c, 'repo_group') and c.repo_group) else None
415 gr_name = c.repo_group.group_name if (hasattr(c, 'repo_group') and c.repo_group) else None
414 # create repositories with write permission on group is set to true
416 # create repositories with write permission on group is set to true
415
417
416 can_fork = c.is_super_admin or h.HasPermissionAny('hg.fork.repository')()
418 can_fork = c.is_super_admin or h.HasPermissionAny('hg.fork.repository')()
417 create_on_write = h.HasPermissionAny('hg.create.write_on_repogroup.true')()
419 create_on_write = h.HasPermissionAny('hg.create.write_on_repogroup.true')()
418 group_write = h.HasRepoGroupPermissionAny('group.write')(gr_name, 'can write into group index page')
420 group_write = h.HasRepoGroupPermissionAny('group.write')(gr_name, 'can write into group index page')
419 group_admin = h.HasRepoGroupPermissionAny('group.admin')(gr_name, 'group admin index page')
421 group_admin = h.HasRepoGroupPermissionAny('group.admin')(gr_name, 'group admin index page')
420
422
421 can_create_repos = c.is_super_admin or c.can_create_repo
423 can_create_repos = c.is_super_admin or c.can_create_repo
422 can_create_repo_groups = c.is_super_admin or c.can_create_repo_group
424 can_create_repo_groups = c.is_super_admin or c.can_create_repo_group
423
425
424 can_create_repos_in_group = c.is_super_admin or group_admin or (group_write and create_on_write)
426 can_create_repos_in_group = c.is_super_admin or group_admin or (group_write and create_on_write)
425 can_create_repo_groups_in_group = c.is_super_admin or group_admin
427 can_create_repo_groups_in_group = c.is_super_admin or group_admin
426 %>
428 %>
427
429
428 % if not_anonymous:
430 % if not_anonymous:
429 <%
431 <%
430 default_target_group = dict()
432 default_target_group = dict()
431 if c.rhodecode_user.personal_repo_group:
433 if c.rhodecode_user.personal_repo_group:
432 default_target_group = dict(parent_group=c.rhodecode_user.personal_repo_group.group_id)
434 default_target_group = dict(parent_group=c.rhodecode_user.personal_repo_group.group_id)
433 %>
435 %>
434
436
435 ## create action
437 ## create action
436 <li>
438 <li>
437 <a href="#create-actions" onclick="return false;" class="menulink childs">
439 <a href="#create-actions" onclick="return false;" class="menulink childs">
438 <i class="icon-plus-circled"></i>
440 <i class="icon-plus-circled"></i>
439 </a>
441 </a>
440
442
441 <div class="action-menu submenu">
443 <div class="action-menu submenu">
442
444
443 <ol>
445 <ol>
444 ## scope of within a repository
446 ## scope of within a repository
445 % if hasattr(c, 'rhodecode_db_repo') and c.rhodecode_db_repo:
447 % if hasattr(c, 'rhodecode_db_repo') and c.rhodecode_db_repo:
446 <li class="submenu-title">${_('This Repository')}</li>
448 <li class="submenu-title">${_('This Repository')}</li>
447 <li>
449 <li>
448 <a href="${h.route_path('pullrequest_new',repo_name=c.repo_name)}">${_('Create Pull Request')}</a>
450 <a href="${h.route_path('pullrequest_new',repo_name=c.repo_name)}">${_('Create Pull Request')}</a>
449 </li>
451 </li>
450 % if can_fork:
452 % if can_fork:
451 <li>
453 <li>
452 <a href="${h.route_path('repo_fork_new',repo_name=c.repo_name,_query=default_target_group)}">${_('Fork this repository')}</a>
454 <a href="${h.route_path('repo_fork_new',repo_name=c.repo_name,_query=default_target_group)}">${_('Fork this repository')}</a>
453 </li>
455 </li>
454 % endif
456 % endif
455 % endif
457 % endif
456
458
457 ## scope of within repository groups
459 ## scope of within repository groups
458 % if hasattr(c, 'repo_group') and c.repo_group and (can_create_repos_in_group or can_create_repo_groups_in_group):
460 % if hasattr(c, 'repo_group') and c.repo_group and (can_create_repos_in_group or can_create_repo_groups_in_group):
459 <li class="submenu-title">${_('This Repository Group')}</li>
461 <li class="submenu-title">${_('This Repository Group')}</li>
460
462
461 % if can_create_repos_in_group:
463 % if can_create_repos_in_group:
462 <li>
464 <li>
463 <a href="${h.route_path('repo_new',_query=default_target_group)}">${_('New Repository')}</a>
465 <a href="${h.route_path('repo_new',_query=default_target_group)}">${_('New Repository')}</a>
464 </li>
466 </li>
465 % endif
467 % endif
466
468
467 % if can_create_repo_groups_in_group:
469 % if can_create_repo_groups_in_group:
468 <li>
470 <li>
469 <a href="${h.route_path('repo_group_new',_query=default_target_group)}">${_(u'New Repository Group')}</a>
471 <a href="${h.route_path('repo_group_new',_query=default_target_group)}">${_(u'New Repository Group')}</a>
470 </li>
472 </li>
471 % endif
473 % endif
472 % endif
474 % endif
473
475
474 ## personal group
476 ## personal group
475 % if c.rhodecode_user.personal_repo_group:
477 % if c.rhodecode_user.personal_repo_group:
476 <li class="submenu-title">Personal Group</li>
478 <li class="submenu-title">Personal Group</li>
477
479
478 <li>
480 <li>
479 <a href="${h.route_path('repo_new',_query=dict(parent_group=c.rhodecode_user.personal_repo_group.group_id))}" >${_('New Repository')} </a>
481 <a href="${h.route_path('repo_new',_query=dict(parent_group=c.rhodecode_user.personal_repo_group.group_id))}" >${_('New Repository')} </a>
480 </li>
482 </li>
481
483
482 <li>
484 <li>
483 <a href="${h.route_path('repo_group_new',_query=dict(parent_group=c.rhodecode_user.personal_repo_group.group_id))}">${_('New Repository Group')} </a>
485 <a href="${h.route_path('repo_group_new',_query=dict(parent_group=c.rhodecode_user.personal_repo_group.group_id))}">${_('New Repository Group')} </a>
484 </li>
486 </li>
485 % endif
487 % endif
486
488
487 ## Global actions
489 ## Global actions
488 <li class="submenu-title">RhodeCode</li>
490 <li class="submenu-title">RhodeCode</li>
489 % if can_create_repos:
491 % if can_create_repos:
490 <li>
492 <li>
491 <a href="${h.route_path('repo_new')}" >${_('New Repository')}</a>
493 <a href="${h.route_path('repo_new')}" >${_('New Repository')}</a>
492 </li>
494 </li>
493 % endif
495 % endif
494
496
495 % if can_create_repo_groups:
497 % if can_create_repo_groups:
496 <li>
498 <li>
497 <a href="${h.route_path('repo_group_new')}" >${_(u'New Repository Group')}</a>
499 <a href="${h.route_path('repo_group_new')}" >${_(u'New Repository Group')}</a>
498 </li>
500 </li>
499 % endif
501 % endif
500
502
501 <li>
503 <li>
502 <a href="${h.route_path('gists_new')}">${_(u'New Gist')}</a>
504 <a href="${h.route_path('gists_new')}">${_(u'New Gist')}</a>
503 </li>
505 </li>
504
506
505 </ol>
507 </ol>
506
508
507 </div>
509 </div>
508 </li>
510 </li>
509
511
510 ## notifications
512 ## notifications
511 <li>
513 <li>
512 <a class="${('empty' if c.unread_notifications == 0 else '')}" href="${h.route_path('notifications_show_all')}">
514 <a class="${('empty' if c.unread_notifications == 0 else '')}" href="${h.route_path('notifications_show_all')}">
513 ${c.unread_notifications}
515 ${c.unread_notifications}
514 </a>
516 </a>
515 </li>
517 </li>
516 % endif
518 % endif
517
519
518 ## USER MENU
520 ## USER MENU
519 <li id="quick_login_li" class="${'active' if active else ''}">
521 <li id="quick_login_li" class="${'active' if active else ''}">
520 % if c.rhodecode_user.username == h.DEFAULT_USER:
522 % if c.rhodecode_user.username == h.DEFAULT_USER:
521 <a id="quick_login_link" class="menulink childs" href="${h.route_path('login', _query={'came_from': h.current_route_path(request)})}">
523 <a id="quick_login_link" class="menulink childs" href="${h.route_path('login', _query={'came_from': h.current_route_path(request)})}">
522 ${gravatar(c.rhodecode_user.email, 20)}
524 ${gravatar(c.rhodecode_user.email, 20)}
523 <span class="user">
525 <span class="user">
524 <span>${_('Sign in')}</span>
526 <span>${_('Sign in')}</span>
525 </span>
527 </span>
526 </a>
528 </a>
527 % else:
529 % else:
528 ## logged in user
530 ## logged in user
529 <a id="quick_login_link" class="menulink childs">
531 <a id="quick_login_link" class="menulink childs">
530 ${gravatar(c.rhodecode_user.email, 20)}
532 ${gravatar(c.rhodecode_user.email, 20)}
531 <span class="user">
533 <span class="user">
532 <span class="menu_link_user">${c.rhodecode_user.username}</span>
534 <span class="menu_link_user">${c.rhodecode_user.username}</span>
533 <div class="show_more"></div>
535 <div class="show_more"></div>
534 </span>
536 </span>
535 </a>
537 </a>
536 ## subnav with menu for logged in user
538 ## subnav with menu for logged in user
537 <div class="user-menu submenu">
539 <div class="user-menu submenu">
538 <div id="quick_login">
540 <div id="quick_login">
539 %if c.rhodecode_user.username != h.DEFAULT_USER:
541 %if c.rhodecode_user.username != h.DEFAULT_USER:
540 <div class="">
542 <div class="">
541 <div class="big_gravatar">${gravatar(c.rhodecode_user.email, 48)}</div>
543 <div class="big_gravatar">${gravatar(c.rhodecode_user.email, 48)}</div>
542 <div class="full_name">${c.rhodecode_user.full_name_or_username}</div>
544 <div class="full_name">${c.rhodecode_user.full_name_or_username}</div>
543 <div class="email">${c.rhodecode_user.email}</div>
545 <div class="email">${c.rhodecode_user.email}</div>
544 </div>
546 </div>
545 <div class="">
547 <div class="">
546 <ol class="links">
548 <ol class="links">
547 <li>${h.link_to(_(u'My account'),h.route_path('my_account_profile'))}</li>
549 <li>${h.link_to(_(u'My account'),h.route_path('my_account_profile'))}</li>
548 % if c.rhodecode_user.personal_repo_group:
550 % if c.rhodecode_user.personal_repo_group:
549 <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>
551 <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>
550 % endif
552 % endif
551 <li>${h.link_to(_(u'Pull Requests'), h.route_path('my_account_pullrequests'))}</li>
553 <li>${h.link_to(_(u'Pull Requests'), h.route_path('my_account_pullrequests'))}</li>
552
554
553 % if c.debug_style:
555 % if c.debug_style:
554 <li>
556 <li>
555 <a class="menulink" title="${_('Style')}" href="${h.route_path('debug_style_home')}">
557 <a class="menulink" title="${_('Style')}" href="${h.route_path('debug_style_home')}">
556 <div class="menulabel">${_('[Style]')}</div>
558 <div class="menulabel">${_('[Style]')}</div>
557 </a>
559 </a>
558 </li>
560 </li>
559 % endif
561 % endif
560
562
561 ## bookmark-items
563 ## bookmark-items
562 <li class="bookmark-items">
564 <li class="bookmark-items">
563 ${_('Bookmarks')}
565 ${_('Bookmarks')}
564 <div class="pull-right">
566 <div class="pull-right">
565 <a href="${h.route_path('my_account_bookmarks')}">
567 <a href="${h.route_path('my_account_bookmarks')}">
566
568
567 <i class="icon-cog"></i>
569 <i class="icon-cog"></i>
568 </a>
570 </a>
569 </div>
571 </div>
570 </li>
572 </li>
571 % if not c.bookmark_items:
573 % if not c.bookmark_items:
572 <li>
574 <li>
573 <a href="${h.route_path('my_account_bookmarks')}">${_('No Bookmarks yet.')}</a>
575 <a href="${h.route_path('my_account_bookmarks')}">${_('No Bookmarks yet.')}</a>
574 </li>
576 </li>
575 % endif
577 % endif
576 % for item in c.bookmark_items:
578 % for item in c.bookmark_items:
577 <li>
579 <li>
578 % if item.repository:
580 % if item.repository:
579 <div>
581 <div>
580 <a class="bookmark-item" href="${h.route_path('my_account_goto_bookmark', bookmark_id=item.position)}">
582 <a class="bookmark-item" href="${h.route_path('my_account_goto_bookmark', bookmark_id=item.position)}">
581 <code>${item.position}</code>
583 <code>${item.position}</code>
582 % if item.repository.repo_type == 'hg':
584 % if item.repository.repo_type == 'hg':
583 <i class="icon-hg" title="${_('Repository')}" style="font-size: 16px"></i>
585 <i class="icon-hg" title="${_('Repository')}" style="font-size: 16px"></i>
584 % elif item.repository.repo_type == 'git':
586 % elif item.repository.repo_type == 'git':
585 <i class="icon-git" title="${_('Repository')}" style="font-size: 16px"></i>
587 <i class="icon-git" title="${_('Repository')}" style="font-size: 16px"></i>
586 % elif item.repository.repo_type == 'svn':
588 % elif item.repository.repo_type == 'svn':
587 <i class="icon-svn" title="${_('Repository')}" style="font-size: 16px"></i>
589 <i class="icon-svn" title="${_('Repository')}" style="font-size: 16px"></i>
588 % endif
590 % endif
589 ${(item.title or h.shorter(item.repository.repo_name, 30))}
591 ${(item.title or h.shorter(item.repository.repo_name, 30))}
590 </a>
592 </a>
591 </div>
593 </div>
592 % elif item.repository_group:
594 % elif item.repository_group:
593 <div>
595 <div>
594 <a class="bookmark-item" href="${h.route_path('my_account_goto_bookmark', bookmark_id=item.position)}">
596 <a class="bookmark-item" href="${h.route_path('my_account_goto_bookmark', bookmark_id=item.position)}">
595 <code>${item.position}</code>
597 <code>${item.position}</code>
596 <i class="icon-repo-group" title="${_('Repository group')}" style="font-size: 14px"></i>
598 <i class="icon-repo-group" title="${_('Repository group')}" style="font-size: 14px"></i>
597 ${(item.title or h.shorter(item.repository_group.group_name, 30))}
599 ${(item.title or h.shorter(item.repository_group.group_name, 30))}
598 </a>
600 </a>
599 </div>
601 </div>
600 % else:
602 % else:
601 <a class="bookmark-item" href="${h.route_path('my_account_goto_bookmark', bookmark_id=item.position)}">
603 <a class="bookmark-item" href="${h.route_path('my_account_goto_bookmark', bookmark_id=item.position)}">
602 <code>${item.position}</code>
604 <code>${item.position}</code>
603 ${item.title}
605 ${item.title}
604 </a>
606 </a>
605 % endif
607 % endif
606 </li>
608 </li>
607 % endfor
609 % endfor
608
610
609 <li class="logout">
611 <li class="logout">
610 ${h.secure_form(h.route_path('logout'), request=request)}
612 ${h.secure_form(h.route_path('logout'), request=request)}
611 ${h.submit('log_out', _(u'Sign Out'),class_="btn btn-primary")}
613 ${h.submit('log_out', _(u'Sign Out'),class_="btn btn-primary")}
612 ${h.end_form()}
614 ${h.end_form()}
613 </li>
615 </li>
614 </ol>
616 </ol>
615 </div>
617 </div>
616 %endif
618 %endif
617 </div>
619 </div>
618 </div>
620 </div>
619
621
620 % endif
622 % endif
621 </li>
623 </li>
622 </%def>
624 </%def>
623
625
624 <%def name="menu_items(active=None)">
626 <%def name="menu_items(active=None)">
625 <%
627 <%
626 def is_active(selected):
628 def is_active(selected):
627 if selected == active:
629 if selected == active:
628 return "active"
630 return "active"
629 return ""
631 return ""
630 %>
632 %>
631
633
632 <ul id="quick" class="main_nav navigation horizontal-list">
634 <ul id="quick" class="main_nav navigation horizontal-list">
633 ## notice box for important system messages
635 ## notice box for important system messages
634 <li style="display: none">
636 <li style="display: none">
635 <a class="notice-box" href="#openNotice" onclick="return false">
637 <a class="notice-box" href="#openNotice" onclick="return false">
636 <div class="menulabel-notice" >
638 <div class="menulabel-notice" >
637 0
639 0
638 </div>
640 </div>
639 </a>
641 </a>
640 </li>
642 </li>
641
643
642 ## Main filter
644 ## Main filter
643 <li>
645 <li>
644 <div class="menulabel main_filter_box">
646 <div class="menulabel main_filter_box">
645 <div class="main_filter_input_box">
647 <div class="main_filter_input_box">
646 <ul class="searchItems">
648 <ul class="searchItems">
647
649
648 % if c.template_context['search_context']['repo_id']:
650 % if c.template_context['search_context']['repo_id']:
649 <li class="searchTag searchTagFilter searchTagHidable" >
651 <li class="searchTag searchTagFilter searchTagHidable" >
650 ##<a href="${h.route_path('search_repo',repo_name=c.template_context['search_context']['repo_name'])}">
652 ##<a href="${h.route_path('search_repo',repo_name=c.template_context['search_context']['repo_name'])}">
651 <span class="tag">
653 <span class="tag">
652 This repo
654 This repo
653 <a href="#removeGoToFilter" onclick="removeGoToFilter(); return false"><i class="icon-cancel-circled"></i></a>
655 <a href="#removeGoToFilter" onclick="removeGoToFilter(); return false"><i class="icon-cancel-circled"></i></a>
654 </span>
656 </span>
655 ##</a>
657 ##</a>
656 </li>
658 </li>
657 % elif c.template_context['search_context']['repo_group_id']:
659 % elif c.template_context['search_context']['repo_group_id']:
658 <li class="searchTag searchTagFilter searchTagHidable">
660 <li class="searchTag searchTagFilter searchTagHidable">
659 ##<a href="${h.route_path('search_repo_group',repo_group_name=c.template_context['search_context']['repo_group_name'])}">
661 ##<a href="${h.route_path('search_repo_group',repo_group_name=c.template_context['search_context']['repo_group_name'])}">
660 <span class="tag">
662 <span class="tag">
661 This group
663 This group
662 <a href="#removeGoToFilter" onclick="removeGoToFilter(); return false"><i class="icon-cancel-circled"></i></a>
664 <a href="#removeGoToFilter" onclick="removeGoToFilter(); return false"><i class="icon-cancel-circled"></i></a>
663 </span>
665 </span>
664 ##</a>
666 ##</a>
665 </li>
667 </li>
666 % endif
668 % endif
667
669
668 <li class="searchTagInput">
670 <li class="searchTagInput">
669 <input class="main_filter_input" id="main_filter" size="25" type="text" name="main_filter" placeholder="${_('search / go to...')}" value="" />
671 <input class="main_filter_input" id="main_filter" size="25" type="text" name="main_filter" placeholder="${_('search / go to...')}" value="" />
670 </li>
672 </li>
671 <li class="searchTag searchTagHelp">
673 <li class="searchTag searchTagHelp">
672 <a href="#showFilterHelp" onclick="showMainFilterBox(); return false">?</a>
674 <a href="#showFilterHelp" onclick="showMainFilterBox(); return false">?</a>
673 </li>
675 </li>
674 </ul>
676 </ul>
675 </div>
677 </div>
676 </div>
678 </div>
677
679
678 <div id="main_filter_help" style="display: none">
680 <div id="main_filter_help" style="display: none">
679 - Use '/' key to quickly access this field.
681 - Use '/' key to quickly access this field.
680
682
681 - Enter a name of repository, or repository group for quick search.
683 - Enter a name of repository, or repository group for quick search.
682
684
683 - Prefix query to allow special search:
685 - Prefix query to allow special search:
684
686
685 user:admin, to search for usernames, always global
687 user:admin, to search for usernames, always global
686
688
687 user_group:devops, to search for user groups, always global
689 user_group:devops, to search for user groups, always global
688
690
689 commit:efced4, to search for commits, scoped to repositories or groups
691 commit:efced4, to search for commits, scoped to repositories or groups
690
692
691 file:models.py, to search for file paths, scoped to repositories or groups
693 file:models.py, to search for file paths, scoped to repositories or groups
692
694
693 % if c.template_context['search_context']['repo_id']:
695 % if c.template_context['search_context']['repo_id']:
694 For advanced full text search visit: <a href="${h.route_path('search_repo',repo_name=c.template_context['search_context']['repo_name'])}">repository search</a>
696 For advanced full text search visit: <a href="${h.route_path('search_repo',repo_name=c.template_context['search_context']['repo_name'])}">repository search</a>
695 % elif c.template_context['search_context']['repo_group_id']:
697 % elif c.template_context['search_context']['repo_group_id']:
696 For advanced full text search visit: <a href="${h.route_path('search_repo_group',repo_group_name=c.template_context['search_context']['repo_group_name'])}">repository group search</a>
698 For advanced full text search visit: <a href="${h.route_path('search_repo_group',repo_group_name=c.template_context['search_context']['repo_group_name'])}">repository group search</a>
697 % else:
699 % else:
698 For advanced full text search visit: <a href="${h.route_path('search')}">global search</a>
700 For advanced full text search visit: <a href="${h.route_path('search')}">global search</a>
699 % endif
701 % endif
700 </div>
702 </div>
701 </li>
703 </li>
702
704
703 ## ROOT MENU
705 ## ROOT MENU
704 <li class="${is_active('home')}">
706 <li class="${is_active('home')}">
705 <a class="menulink" title="${_('Home')}" href="${h.route_path('home')}">
707 <a class="menulink" title="${_('Home')}" href="${h.route_path('home')}">
706 <div class="menulabel">${_('Home')}</div>
708 <div class="menulabel">${_('Home')}</div>
707 </a>
709 </a>
708 </li>
710 </li>
709
711
710 %if c.rhodecode_user.username != h.DEFAULT_USER:
712 %if c.rhodecode_user.username != h.DEFAULT_USER:
711 <li class="${is_active('journal')}">
713 <li class="${is_active('journal')}">
712 <a class="menulink" title="${_('Show activity journal')}" href="${h.route_path('journal')}">
714 <a class="menulink" title="${_('Show activity journal')}" href="${h.route_path('journal')}">
713 <div class="menulabel">${_('Journal')}</div>
715 <div class="menulabel">${_('Journal')}</div>
714 </a>
716 </a>
715 </li>
717 </li>
716 %else:
718 %else:
717 <li class="${is_active('journal')}">
719 <li class="${is_active('journal')}">
718 <a class="menulink" title="${_('Show Public activity journal')}" href="${h.route_path('journal_public')}">
720 <a class="menulink" title="${_('Show Public activity journal')}" href="${h.route_path('journal_public')}">
719 <div class="menulabel">${_('Public journal')}</div>
721 <div class="menulabel">${_('Public journal')}</div>
720 </a>
722 </a>
721 </li>
723 </li>
722 %endif
724 %endif
723
725
724 <li class="${is_active('gists')}">
726 <li class="${is_active('gists')}">
725 <a class="menulink childs" title="${_('Show Gists')}" href="${h.route_path('gists_show')}">
727 <a class="menulink childs" title="${_('Show Gists')}" href="${h.route_path('gists_show')}">
726 <div class="menulabel">${_('Gists')}</div>
728 <div class="menulabel">${_('Gists')}</div>
727 </a>
729 </a>
728 </li>
730 </li>
729
731
730 % if c.is_super_admin or c.is_delegated_admin:
732 % if c.is_super_admin or c.is_delegated_admin:
731 <li class="${is_active('admin')}">
733 <li class="${is_active('admin')}">
732 <a class="menulink childs" title="${_('Admin settings')}" href="${h.route_path('admin_home')}">
734 <a class="menulink childs" title="${_('Admin settings')}" href="${h.route_path('admin_home')}">
733 <div class="menulabel">${_('Admin')} </div>
735 <div class="menulabel">${_('Admin')} </div>
734 </a>
736 </a>
735 </li>
737 </li>
736 % endif
738 % endif
737
739
738 ## render extra user menu
740 ## render extra user menu
739 ${usermenu(active=(active=='my_account'))}
741 ${usermenu(active=(active=='my_account'))}
740
742
741 </ul>
743 </ul>
742
744
743 <script type="text/javascript">
745 <script type="text/javascript">
744 var visualShowPublicIcon = "${c.visual.show_public_icon}" == "True";
746 var visualShowPublicIcon = "${c.visual.show_public_icon}" == "True";
745
747
746 var formatRepoResult = function(result, container, query, escapeMarkup) {
748 var formatRepoResult = function(result, container, query, escapeMarkup) {
747 return function(data, escapeMarkup) {
749 return function(data, escapeMarkup) {
748 if (!data.repo_id){
750 if (!data.repo_id){
749 return data.text; // optgroup text Repositories
751 return data.text; // optgroup text Repositories
750 }
752 }
751
753
752 var tmpl = '';
754 var tmpl = '';
753 var repoType = data['repo_type'];
755 var repoType = data['repo_type'];
754 var repoName = data['text'];
756 var repoName = data['text'];
755
757
756 if(data && data.type == 'repo'){
758 if(data && data.type == 'repo'){
757 if(repoType === 'hg'){
759 if(repoType === 'hg'){
758 tmpl += '<i class="icon-hg"></i> ';
760 tmpl += '<i class="icon-hg"></i> ';
759 }
761 }
760 else if(repoType === 'git'){
762 else if(repoType === 'git'){
761 tmpl += '<i class="icon-git"></i> ';
763 tmpl += '<i class="icon-git"></i> ';
762 }
764 }
763 else if(repoType === 'svn'){
765 else if(repoType === 'svn'){
764 tmpl += '<i class="icon-svn"></i> ';
766 tmpl += '<i class="icon-svn"></i> ';
765 }
767 }
766 if(data['private']){
768 if(data['private']){
767 tmpl += '<i class="icon-lock" ></i> ';
769 tmpl += '<i class="icon-lock" ></i> ';
768 }
770 }
769 else if(visualShowPublicIcon){
771 else if(visualShowPublicIcon){
770 tmpl += '<i class="icon-unlock-alt"></i> ';
772 tmpl += '<i class="icon-unlock-alt"></i> ';
771 }
773 }
772 }
774 }
773 tmpl += escapeMarkup(repoName);
775 tmpl += escapeMarkup(repoName);
774 return tmpl;
776 return tmpl;
775
777
776 }(result, escapeMarkup);
778 }(result, escapeMarkup);
777 };
779 };
778
780
779 var formatRepoGroupResult = function(result, container, query, escapeMarkup) {
781 var formatRepoGroupResult = function(result, container, query, escapeMarkup) {
780 return function(data, escapeMarkup) {
782 return function(data, escapeMarkup) {
781 if (!data.repo_group_id){
783 if (!data.repo_group_id){
782 return data.text; // optgroup text Repositories
784 return data.text; // optgroup text Repositories
783 }
785 }
784
786
785 var tmpl = '';
787 var tmpl = '';
786 var repoGroupName = data['text'];
788 var repoGroupName = data['text'];
787
789
788 if(data){
790 if(data){
789
791
790 tmpl += '<i class="icon-repo-group"></i> ';
792 tmpl += '<i class="icon-repo-group"></i> ';
791
793
792 }
794 }
793 tmpl += escapeMarkup(repoGroupName);
795 tmpl += escapeMarkup(repoGroupName);
794 return tmpl;
796 return tmpl;
795
797
796 }(result, escapeMarkup);
798 }(result, escapeMarkup);
797 };
799 };
798
800
799 var escapeRegExChars = function (value) {
801 var escapeRegExChars = function (value) {
800 return value.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
802 return value.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
801 };
803 };
802
804
803 var getRepoIcon = function(repo_type) {
805 var getRepoIcon = function(repo_type) {
804 if (repo_type === 'hg') {
806 if (repo_type === 'hg') {
805 return '<i class="icon-hg"></i> ';
807 return '<i class="icon-hg"></i> ';
806 }
808 }
807 else if (repo_type === 'git') {
809 else if (repo_type === 'git') {
808 return '<i class="icon-git"></i> ';
810 return '<i class="icon-git"></i> ';
809 }
811 }
810 else if (repo_type === 'svn') {
812 else if (repo_type === 'svn') {
811 return '<i class="icon-svn"></i> ';
813 return '<i class="icon-svn"></i> ';
812 }
814 }
813 return ''
815 return ''
814 };
816 };
815
817
816 var autocompleteMainFilterFormatResult = function (data, value, org_formatter) {
818 var autocompleteMainFilterFormatResult = function (data, value, org_formatter) {
817
819
818 if (value.split(':').length === 2) {
820 if (value.split(':').length === 2) {
819 value = value.split(':')[1]
821 value = value.split(':')[1]
820 }
822 }
821
823
822 var searchType = data['type'];
824 var searchType = data['type'];
823 var searchSubType = data['subtype'];
825 var searchSubType = data['subtype'];
824 var valueDisplay = data['value_display'];
826 var valueDisplay = data['value_display'];
825
827
826 var pattern = '(' + escapeRegExChars(value) + ')';
828 var pattern = '(' + escapeRegExChars(value) + ')';
827
829
828 valueDisplay = Select2.util.escapeMarkup(valueDisplay);
830 valueDisplay = Select2.util.escapeMarkup(valueDisplay);
829
831
830 // highlight match
832 // highlight match
831 if (searchType != 'text') {
833 if (searchType != 'text') {
832 valueDisplay = valueDisplay.replace(new RegExp(pattern, 'gi'), '<strong>$1<\/strong>');
834 valueDisplay = valueDisplay.replace(new RegExp(pattern, 'gi'), '<strong>$1<\/strong>');
833 }
835 }
834
836
835 var icon = '';
837 var icon = '';
836
838
837 if (searchType === 'hint') {
839 if (searchType === 'hint') {
838 icon += '<i class="icon-repo-group"></i> ';
840 icon += '<i class="icon-repo-group"></i> ';
839 }
841 }
840 // full text search/hints
842 // full text search/hints
841 else if (searchType === 'search') {
843 else if (searchType === 'search') {
842 icon += '<i class="icon-more"></i> ';
844 icon += '<i class="icon-more"></i> ';
843 if (searchSubType !== undefined && searchSubType == 'repo') {
845 if (searchSubType !== undefined && searchSubType == 'repo') {
844 valueDisplay += '<div class="pull-right tag">repository</div>';
846 valueDisplay += '<div class="pull-right tag">repository</div>';
845 }
847 }
846 else if (searchSubType !== undefined && searchSubType == 'repo_group') {
848 else if (searchSubType !== undefined && searchSubType == 'repo_group') {
847 valueDisplay += '<div class="pull-right tag">repo group</div>';
849 valueDisplay += '<div class="pull-right tag">repo group</div>';
848 }
850 }
849 }
851 }
850 // repository
852 // repository
851 else if (searchType === 'repo') {
853 else if (searchType === 'repo') {
852
854
853 var repoIcon = getRepoIcon(data['repo_type']);
855 var repoIcon = getRepoIcon(data['repo_type']);
854 icon += repoIcon;
856 icon += repoIcon;
855
857
856 if (data['private']) {
858 if (data['private']) {
857 icon += '<i class="icon-lock" ></i> ';
859 icon += '<i class="icon-lock" ></i> ';
858 }
860 }
859 else if (visualShowPublicIcon) {
861 else if (visualShowPublicIcon) {
860 icon += '<i class="icon-unlock-alt"></i> ';
862 icon += '<i class="icon-unlock-alt"></i> ';
861 }
863 }
862 }
864 }
863 // repository groups
865 // repository groups
864 else if (searchType === 'repo_group') {
866 else if (searchType === 'repo_group') {
865 icon += '<i class="icon-repo-group"></i> ';
867 icon += '<i class="icon-repo-group"></i> ';
866 }
868 }
867 // user group
869 // user group
868 else if (searchType === 'user_group') {
870 else if (searchType === 'user_group') {
869 icon += '<i class="icon-group"></i> ';
871 icon += '<i class="icon-group"></i> ';
870 }
872 }
871 // user
873 // user
872 else if (searchType === 'user') {
874 else if (searchType === 'user') {
873 icon += '<img class="gravatar" src="{0}"/>'.format(data['icon_link']);
875 icon += '<img class="gravatar" src="{0}"/>'.format(data['icon_link']);
874 }
876 }
875 // commit
877 // commit
876 else if (searchType === 'commit') {
878 else if (searchType === 'commit') {
877 var repo_data = data['repo_data'];
879 var repo_data = data['repo_data'];
878 var repoIcon = getRepoIcon(repo_data['repository_type']);
880 var repoIcon = getRepoIcon(repo_data['repository_type']);
879 if (repoIcon) {
881 if (repoIcon) {
880 icon += repoIcon;
882 icon += repoIcon;
881 } else {
883 } else {
882 icon += '<i class="icon-tag"></i>';
884 icon += '<i class="icon-tag"></i>';
883 }
885 }
884 }
886 }
885 // file
887 // file
886 else if (searchType === 'file') {
888 else if (searchType === 'file') {
887 var repo_data = data['repo_data'];
889 var repo_data = data['repo_data'];
888 var repoIcon = getRepoIcon(repo_data['repository_type']);
890 var repoIcon = getRepoIcon(repo_data['repository_type']);
889 if (repoIcon) {
891 if (repoIcon) {
890 icon += repoIcon;
892 icon += repoIcon;
891 } else {
893 } else {
892 icon += '<i class="icon-tag"></i>';
894 icon += '<i class="icon-tag"></i>';
893 }
895 }
894 }
896 }
895 // generic text
897 // generic text
896 else if (searchType === 'text') {
898 else if (searchType === 'text') {
897 icon = '';
899 icon = '';
898 }
900 }
899
901
900 var tmpl = '<div class="ac-container-wrap">{0}{1}</div>';
902 var tmpl = '<div class="ac-container-wrap">{0}{1}</div>';
901 return tmpl.format(icon, valueDisplay);
903 return tmpl.format(icon, valueDisplay);
902 };
904 };
903
905
904 var handleSelect = function(element, suggestion) {
906 var handleSelect = function(element, suggestion) {
905 if (suggestion.type === "hint") {
907 if (suggestion.type === "hint") {
906 // we skip action
908 // we skip action
907 $('#main_filter').focus();
909 $('#main_filter').focus();
908 }
910 }
909 else if (suggestion.type === "text") {
911 else if (suggestion.type === "text") {
910 // we skip action
912 // we skip action
911 $('#main_filter').focus();
913 $('#main_filter').focus();
912
914
913 } else {
915 } else {
914 window.location = suggestion['url'];
916 window.location = suggestion['url'];
915 }
917 }
916 };
918 };
917
919
918 var autocompleteMainFilterResult = function (suggestion, originalQuery, queryLowerCase) {
920 var autocompleteMainFilterResult = function (suggestion, originalQuery, queryLowerCase) {
919 if (queryLowerCase.split(':').length === 2) {
921 if (queryLowerCase.split(':').length === 2) {
920 queryLowerCase = queryLowerCase.split(':')[1]
922 queryLowerCase = queryLowerCase.split(':')[1]
921 }
923 }
922 if (suggestion.type === "text") {
924 if (suggestion.type === "text") {
923 // special case we don't want to "skip" display for
925 // special case we don't want to "skip" display for
924 return true
926 return true
925 }
927 }
926 return suggestion.value_display.toLowerCase().indexOf(queryLowerCase) !== -1;
928 return suggestion.value_display.toLowerCase().indexOf(queryLowerCase) !== -1;
927 };
929 };
928
930
929 var cleanContext = {
931 var cleanContext = {
930 repo_view_type: null,
932 repo_view_type: null,
931
933
932 repo_id: null,
934 repo_id: null,
933 repo_name: "",
935 repo_name: "",
934
936
935 repo_group_id: null,
937 repo_group_id: null,
936 repo_group_name: null
938 repo_group_name: null
937 };
939 };
938 var removeGoToFilter = function () {
940 var removeGoToFilter = function () {
939 $('.searchTagHidable').hide();
941 $('.searchTagHidable').hide();
940 $('#main_filter').autocomplete(
942 $('#main_filter').autocomplete(
941 'setOptions', {params:{search_context: cleanContext}});
943 'setOptions', {params:{search_context: cleanContext}});
942 };
944 };
943
945
944 $('#main_filter').autocomplete({
946 $('#main_filter').autocomplete({
945 serviceUrl: pyroutes.url('goto_switcher_data'),
947 serviceUrl: pyroutes.url('goto_switcher_data'),
946 params: {
948 params: {
947 "search_context": templateContext.search_context
949 "search_context": templateContext.search_context
948 },
950 },
949 minChars:2,
951 minChars:2,
950 maxHeight:400,
952 maxHeight:400,
951 deferRequestBy: 300, //miliseconds
953 deferRequestBy: 300, //miliseconds
952 tabDisabled: true,
954 tabDisabled: true,
953 autoSelectFirst: false,
955 autoSelectFirst: false,
954 containerClass: 'autocomplete-qfilter-suggestions',
956 containerClass: 'autocomplete-qfilter-suggestions',
955 formatResult: autocompleteMainFilterFormatResult,
957 formatResult: autocompleteMainFilterFormatResult,
956 lookupFilter: autocompleteMainFilterResult,
958 lookupFilter: autocompleteMainFilterResult,
957 onSelect: function (element, suggestion) {
959 onSelect: function (element, suggestion) {
958 handleSelect(element, suggestion);
960 handleSelect(element, suggestion);
959 return false;
961 return false;
960 },
962 },
961 onSearchError: function (element, query, jqXHR, textStatus, errorThrown) {
963 onSearchError: function (element, query, jqXHR, textStatus, errorThrown) {
962 if (jqXHR !== 'abort') {
964 if (jqXHR !== 'abort') {
963 alert("Error during search.\nError code: {0}".format(textStatus));
965 alert("Error during search.\nError code: {0}".format(textStatus));
964 window.location = '';
966 window.location = '';
965 }
967 }
966 }
968 }
967 });
969 });
968
970
969 showMainFilterBox = function () {
971 showMainFilterBox = function () {
970 $('#main_filter_help').toggle();
972 $('#main_filter_help').toggle();
971 };
973 };
972
974
973 $('#main_filter').on('keydown.autocomplete', function (e) {
975 $('#main_filter').on('keydown.autocomplete', function (e) {
974
976
975 var BACKSPACE = 8;
977 var BACKSPACE = 8;
976 var el = $(e.currentTarget);
978 var el = $(e.currentTarget);
977 if(e.which === BACKSPACE){
979 if(e.which === BACKSPACE){
978 var inputVal = el.val();
980 var inputVal = el.val();
979 if (inputVal === ""){
981 if (inputVal === ""){
980 removeGoToFilter()
982 removeGoToFilter()
981 }
983 }
982 }
984 }
983 });
985 });
984
986
985 </script>
987 </script>
986 <script src="${h.asset('js/rhodecode/base/keyboard-bindings.js', ver=c.rhodecode_version_hash)}"></script>
988 <script src="${h.asset('js/rhodecode/base/keyboard-bindings.js', ver=c.rhodecode_version_hash)}"></script>
987 </%def>
989 </%def>
988
990
989 <div class="modal" id="help_kb" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
991 <div class="modal" id="help_kb" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
990 <div class="modal-dialog">
992 <div class="modal-dialog">
991 <div class="modal-content">
993 <div class="modal-content">
992 <div class="modal-header">
994 <div class="modal-header">
993 <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
995 <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
994 <h4 class="modal-title" id="myModalLabel">${_('Keyboard shortcuts')}</h4>
996 <h4 class="modal-title" id="myModalLabel">${_('Keyboard shortcuts')}</h4>
995 </div>
997 </div>
996 <div class="modal-body">
998 <div class="modal-body">
997 <div class="block-left">
999 <div class="block-left">
998 <table class="keyboard-mappings">
1000 <table class="keyboard-mappings">
999 <tbody>
1001 <tbody>
1000 <tr>
1002 <tr>
1001 <th></th>
1003 <th></th>
1002 <th>${_('Site-wide shortcuts')}</th>
1004 <th>${_('Site-wide shortcuts')}</th>
1003 </tr>
1005 </tr>
1004 <%
1006 <%
1005 elems = [
1007 elems = [
1006 ('/', 'Use quick search box'),
1008 ('/', 'Use quick search box'),
1007 ('g h', 'Goto home page'),
1009 ('g h', 'Goto home page'),
1008 ('g g', 'Goto my private gists page'),
1010 ('g g', 'Goto my private gists page'),
1009 ('g G', 'Goto my public gists page'),
1011 ('g G', 'Goto my public gists page'),
1010 ('g 0-9', 'Goto bookmarked items from 0-9'),
1012 ('g 0-9', 'Goto bookmarked items from 0-9'),
1011 ('n r', 'New repository page'),
1013 ('n r', 'New repository page'),
1012 ('n g', 'New gist page'),
1014 ('n g', 'New gist page'),
1013 ]
1015 ]
1014 %>
1016 %>
1015 %for key, desc in elems:
1017 %for key, desc in elems:
1016 <tr>
1018 <tr>
1017 <td class="keys">
1019 <td class="keys">
1018 <span class="key tag">${key}</span>
1020 <span class="key tag">${key}</span>
1019 </td>
1021 </td>
1020 <td>${desc}</td>
1022 <td>${desc}</td>
1021 </tr>
1023 </tr>
1022 %endfor
1024 %endfor
1023 </tbody>
1025 </tbody>
1024 </table>
1026 </table>
1025 </div>
1027 </div>
1026 <div class="block-left">
1028 <div class="block-left">
1027 <table class="keyboard-mappings">
1029 <table class="keyboard-mappings">
1028 <tbody>
1030 <tbody>
1029 <tr>
1031 <tr>
1030 <th></th>
1032 <th></th>
1031 <th>${_('Repositories')}</th>
1033 <th>${_('Repositories')}</th>
1032 </tr>
1034 </tr>
1033 <%
1035 <%
1034 elems = [
1036 elems = [
1035 ('g s', 'Goto summary page'),
1037 ('g s', 'Goto summary page'),
1036 ('g c', 'Goto changelog page'),
1038 ('g c', 'Goto changelog page'),
1037 ('g f', 'Goto files page'),
1039 ('g f', 'Goto files page'),
1038 ('g F', 'Goto files page with file search activated'),
1040 ('g F', 'Goto files page with file search activated'),
1039 ('g p', 'Goto pull requests page'),
1041 ('g p', 'Goto pull requests page'),
1040 ('g o', 'Goto repository settings'),
1042 ('g o', 'Goto repository settings'),
1041 ('g O', 'Goto repository permissions settings'),
1043 ('g O', 'Goto repository permissions settings'),
1042 ]
1044 ]
1043 %>
1045 %>
1044 %for key, desc in elems:
1046 %for key, desc in elems:
1045 <tr>
1047 <tr>
1046 <td class="keys">
1048 <td class="keys">
1047 <span class="key tag">${key}</span>
1049 <span class="key tag">${key}</span>
1048 </td>
1050 </td>
1049 <td>${desc}</td>
1051 <td>${desc}</td>
1050 </tr>
1052 </tr>
1051 %endfor
1053 %endfor
1052 </tbody>
1054 </tbody>
1053 </table>
1055 </table>
1054 </div>
1056 </div>
1055 </div>
1057 </div>
1056 <div class="modal-footer">
1058 <div class="modal-footer">
1057 </div>
1059 </div>
1058 </div><!-- /.modal-content -->
1060 </div><!-- /.modal-content -->
1059 </div><!-- /.modal-dialog -->
1061 </div><!-- /.modal-dialog -->
1060 </div><!-- /.modal -->
1062 </div><!-- /.modal -->
1061
1063
General Comments 0
You need to be logged in to leave comments. Login now