##// END OF EJS Templates
core: revamp of automation/scheduler/artifacts EE functionality
super-admin -
r5137:f3cd5ebe default
parent child Browse files
Show More
@@ -0,0 +1,38 b''
1 # Copyright (C) 2016-2023 RhodeCode GmbH
2 #
3 # This program is free software: you can redistribute it and/or modify
4 # it under the terms of the GNU Affero General Public License, version 3
5 # (only), as published by the Free Software Foundation.
6 #
7 # This program is distributed in the hope that it will be useful,
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # GNU General Public License for more details.
11 #
12 # You should have received a copy of the GNU Affero General Public License
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 #
15 # This program is dual-licensed. If you wish to learn more about the
16 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
18
19 import logging
20
21 from rhodecode.apps._base import BaseAppView
22 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator
23
24 log = logging.getLogger(__name__)
25
26
27 class AdminAutomationView(BaseAppView):
28
29 def load_default_context(self):
30 c = self._get_local_tmpl_context()
31 return c
32
33 @LoginRequired()
34 @HasPermissionAllDecorator('hg.admin')
35 def automation(self):
36 c = self.load_default_context()
37 c.active = 'automation'
38 return self._get_template_context(c)
@@ -0,0 +1,38 b''
1 # Copyright (C) 2016-2023 RhodeCode GmbH
2 #
3 # This program is free software: you can redistribute it and/or modify
4 # it under the terms of the GNU Affero General Public License, version 3
5 # (only), as published by the Free Software Foundation.
6 #
7 # This program is distributed in the hope that it will be useful,
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # GNU General Public License for more details.
11 #
12 # You should have received a copy of the GNU Affero General Public License
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 #
15 # This program is dual-licensed. If you wish to learn more about the
16 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
18
19 import logging
20
21 from rhodecode.apps._base import BaseAppView
22 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator
23
24 log = logging.getLogger(__name__)
25
26
27 class AdminSchedulerView(BaseAppView):
28
29 def load_default_context(self):
30 c = self._get_local_tmpl_context()
31 return c
32
33 @LoginRequired()
34 @HasPermissionAllDecorator('hg.admin')
35 def scheduler(self):
36 c = self.load_default_context()
37 c.active = 'scheduler'
38 return self._get_template_context(c)
@@ -0,0 +1,39 b''
1 <%inherit file="/base/base.mako"/>
2
3 <%def name="title()">
4 ${_('Artifacts Admin')}
5 %if c.rhodecode_name:
6 &middot; ${h.branding(c.rhodecode_name)}
7 %endif
8 </%def>
9
10 <%def name="breadcrumbs_links()"></%def>
11
12 <%def name="menu_bar_nav()">
13 ${self.menu_items(active='admin')}
14 </%def>
15
16 <%def name="menu_bar_subnav()">
17 ${self.admin_menu(active='scheduler')}
18 </%def>
19
20 <%def name="main()">
21
22 <div class="box">
23
24 <div class="panel panel-default">
25 <div class="panel-heading">
26 <h3 class="panel-title">${_('Scheduler Administration.')}</h3>
27 </div>
28 <div class="panel-body">
29 <h4>${_('This feature is available in RhodeCode EE edition only. Contact {sales_email} to obtain a trial license.').format(sales_email='<a href="mailto:sales@rhodecode.com">sales@rhodecode.com</a>')|n}</h4>
30 <p>
31 Scheduler enables management of automation tasks, and defining new custom cron-like actions to be executed within RhodeCode system.
32 </p>
33 </div>
34 </div>
35
36 </div>
37
38
39 </%def>
@@ -0,0 +1,37 b''
1 <%inherit file="/base/base.mako"/>
2
3 <%def name="title()">
4 ${_('Artifacts Admin')}
5 %if c.rhodecode_name:
6 &middot; ${h.branding(c.rhodecode_name)}
7 %endif
8 </%def>
9
10 <%def name="breadcrumbs_links()"></%def>
11
12 <%def name="menu_bar_nav()">
13 ${self.menu_items(active='admin')}
14 </%def>
15
16 <%def name="menu_bar_subnav()">
17 ${self.admin_menu(active='automation')}
18 </%def>
19
20 <%def name="main()">
21
22 <div class="box">
23
24 <div class="panel panel-default">
25 <div class="panel-heading">
26 <h3 class="panel-title">${_('Automation Administration.')}</h3>
27 </div>
28 <div class="panel-body">
29 <h4>${_('This feature is available in RhodeCode EE edition only. Contact {sales_email} to obtain a trial license.').format(sales_email='<a href="mailto:sales@rhodecode.com">sales@rhodecode.com</a>')|n}</h4>
30 <img style="width: 100%; height: 100%" src="${h.asset('images/ee_features/admin_automation.png')}"/>
31 </div>
32 </div>
33
34 </div>
35
36
37 </%def>
@@ -1,858 +1,858 b''
1 # Copyright (C) 2016-2023 RhodeCode GmbH
1 # Copyright (C) 2016-2023 RhodeCode GmbH
2 #
2 #
3 # This program is free software: you can redistribute it and/or modify
3 # This program is free software: you can redistribute it and/or modify
4 # it under the terms of the GNU Affero General Public License, version 3
4 # it under the terms of the GNU Affero General Public License, version 3
5 # (only), as published by the Free Software Foundation.
5 # (only), as published by the Free Software Foundation.
6 #
6 #
7 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # GNU General Public License for more details.
10 # GNU General Public License for more details.
11 #
11 #
12 # You should have received a copy of the GNU Affero General Public License
12 # You should have received a copy of the GNU Affero General Public License
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 #
14 #
15 # This program is dual-licensed. If you wish to learn more about the
15 # This program is dual-licensed. If you wish to learn more about the
16 # RhodeCode Enterprise Edition, including its added features, Support services,
16 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
18
18
19 import time
19 import time
20 import logging
20 import logging
21 import operator
21 import operator
22
22
23 from pyramid.httpexceptions import HTTPFound, HTTPForbidden, HTTPBadRequest
23 from pyramid.httpexceptions import HTTPFound, HTTPForbidden, HTTPBadRequest
24
24
25 from rhodecode.lib import helpers as h, diffs, rc_cache
25 from rhodecode.lib import helpers as h, diffs, rc_cache
26 from rhodecode.lib.str_utils import safe_str
26 from rhodecode.lib.str_utils import safe_str
27 from rhodecode.lib.utils import repo_name_slug
27 from rhodecode.lib.utils import repo_name_slug
28 from rhodecode.lib.utils2 import (
28 from rhodecode.lib.utils2 import (
29 StrictAttributeDict, str2bool, safe_int, datetime_to_time)
29 StrictAttributeDict, str2bool, safe_int, datetime_to_time)
30 from rhodecode.lib.markup_renderer import MarkupRenderer, relative_links
30 from rhodecode.lib.markup_renderer import MarkupRenderer, relative_links
31 from rhodecode.lib.vcs.backends.base import EmptyCommit
31 from rhodecode.lib.vcs.backends.base import EmptyCommit
32 from rhodecode.lib.vcs.exceptions import RepositoryRequirementError
32 from rhodecode.lib.vcs.exceptions import RepositoryRequirementError
33 from rhodecode.model import repo
33 from rhodecode.model import repo
34 from rhodecode.model import repo_group
34 from rhodecode.model import repo_group
35 from rhodecode.model import user_group
35 from rhodecode.model import user_group
36 from rhodecode.model import user
36 from rhodecode.model import user
37 from rhodecode.model.db import User
37 from rhodecode.model.db import User
38 from rhodecode.model.scm import ScmModel
38 from rhodecode.model.scm import ScmModel
39 from rhodecode.model.settings import VcsSettingsModel, IssueTrackerSettingsModel
39 from rhodecode.model.settings import VcsSettingsModel, IssueTrackerSettingsModel
40 from rhodecode.model.repo import ReadmeFinder
40 from rhodecode.model.repo import ReadmeFinder
41
41
42 log = logging.getLogger(__name__)
42 log = logging.getLogger(__name__)
43
43
44
44
45 ADMIN_PREFIX = '/_admin'
45 ADMIN_PREFIX: str = '/_admin'
46 STATIC_FILE_PREFIX = '/_static'
46 STATIC_FILE_PREFIX: str = '/_static'
47
47
48 URL_NAME_REQUIREMENTS = {
48 URL_NAME_REQUIREMENTS = {
49 # group name can have a slash in them, but they must not end with a slash
49 # group name can have a slash in them, but they must not end with a slash
50 'group_name': r'.*?[^/]',
50 'group_name': r'.*?[^/]',
51 'repo_group_name': r'.*?[^/]',
51 'repo_group_name': r'.*?[^/]',
52 # repo names can have a slash in them, but they must not end with a slash
52 # repo names can have a slash in them, but they must not end with a slash
53 'repo_name': r'.*?[^/]',
53 'repo_name': r'.*?[^/]',
54 # file path eats up everything at the end
54 # file path eats up everything at the end
55 'f_path': r'.*',
55 'f_path': r'.*',
56 # reference types
56 # reference types
57 'source_ref_type': r'(branch|book|tag|rev|\%\(source_ref_type\)s)',
57 'source_ref_type': r'(branch|book|tag|rev|\%\(source_ref_type\)s)',
58 'target_ref_type': r'(branch|book|tag|rev|\%\(target_ref_type\)s)',
58 'target_ref_type': r'(branch|book|tag|rev|\%\(target_ref_type\)s)',
59 }
59 }
60
60
61
61
62 def add_route_with_slash(config,name, pattern, **kw):
62 def add_route_with_slash(config,name, pattern, **kw):
63 config.add_route(name, pattern, **kw)
63 config.add_route(name, pattern, **kw)
64 if not pattern.endswith('/'):
64 if not pattern.endswith('/'):
65 config.add_route(name + '_slash', pattern + '/', **kw)
65 config.add_route(name + '_slash', pattern + '/', **kw)
66
66
67
67
68 def add_route_requirements(route_path, requirements=None):
68 def add_route_requirements(route_path, requirements=None):
69 """
69 """
70 Adds regex requirements to pyramid routes using a mapping dict
70 Adds regex requirements to pyramid routes using a mapping dict
71 e.g::
71 e.g::
72 add_route_requirements('{repo_name}/settings')
72 add_route_requirements('{repo_name}/settings')
73 """
73 """
74 requirements = requirements or URL_NAME_REQUIREMENTS
74 requirements = requirements or URL_NAME_REQUIREMENTS
75 for key, regex in list(requirements.items()):
75 for key, regex in list(requirements.items()):
76 route_path = route_path.replace('{%s}' % key, '{%s:%s}' % (key, regex))
76 route_path = route_path.replace('{%s}' % key, '{%s:%s}' % (key, regex))
77 return route_path
77 return route_path
78
78
79
79
80 def get_format_ref_id(repo):
80 def get_format_ref_id(repo):
81 """Returns a `repo` specific reference formatter function"""
81 """Returns a `repo` specific reference formatter function"""
82 if h.is_svn(repo):
82 if h.is_svn(repo):
83 return _format_ref_id_svn
83 return _format_ref_id_svn
84 else:
84 else:
85 return _format_ref_id
85 return _format_ref_id
86
86
87
87
88 def _format_ref_id(name, raw_id):
88 def _format_ref_id(name, raw_id):
89 """Default formatting of a given reference `name`"""
89 """Default formatting of a given reference `name`"""
90 return name
90 return name
91
91
92
92
93 def _format_ref_id_svn(name, raw_id):
93 def _format_ref_id_svn(name, raw_id):
94 """Special way of formatting a reference for Subversion including path"""
94 """Special way of formatting a reference for Subversion including path"""
95 return f'{name}@{raw_id}'
95 return f'{name}@{raw_id}'
96
96
97
97
98 class TemplateArgs(StrictAttributeDict):
98 class TemplateArgs(StrictAttributeDict):
99 pass
99 pass
100
100
101
101
102 class BaseAppView(object):
102 class BaseAppView(object):
103
103
104 def __init__(self, context, request):
104 def __init__(self, context, request):
105 self.request = request
105 self.request = request
106 self.context = context
106 self.context = context
107 self.session = request.session
107 self.session = request.session
108 if not hasattr(request, 'user'):
108 if not hasattr(request, 'user'):
109 # NOTE(marcink): edge case, we ended up in matched route
109 # NOTE(marcink): edge case, we ended up in matched route
110 # but probably of web-app context, e.g API CALL/VCS CALL
110 # but probably of web-app context, e.g API CALL/VCS CALL
111 if hasattr(request, 'vcs_call') or hasattr(request, 'rpc_method'):
111 if hasattr(request, 'vcs_call') or hasattr(request, 'rpc_method'):
112 log.warning('Unable to process request `%s` in this scope', request)
112 log.warning('Unable to process request `%s` in this scope', request)
113 raise HTTPBadRequest()
113 raise HTTPBadRequest()
114
114
115 self._rhodecode_user = request.user # auth user
115 self._rhodecode_user = request.user # auth user
116 self._rhodecode_db_user = self._rhodecode_user.get_instance()
116 self._rhodecode_db_user = self._rhodecode_user.get_instance()
117 self._maybe_needs_password_change(
117 self._maybe_needs_password_change(
118 request.matched_route.name, self._rhodecode_db_user)
118 request.matched_route.name, self._rhodecode_db_user)
119
119
120 def _maybe_needs_password_change(self, view_name, user_obj):
120 def _maybe_needs_password_change(self, view_name, user_obj):
121
121
122 dont_check_views = [
122 dont_check_views = [
123 'channelstream_connect',
123 'channelstream_connect',
124 'ops_ping'
124 'ops_ping'
125 ]
125 ]
126 if view_name in dont_check_views:
126 if view_name in dont_check_views:
127 return
127 return
128
128
129 log.debug('Checking if user %s needs password change on view %s',
129 log.debug('Checking if user %s needs password change on view %s',
130 user_obj, view_name)
130 user_obj, view_name)
131
131
132 skip_user_views = [
132 skip_user_views = [
133 'logout', 'login',
133 'logout', 'login',
134 'my_account_password', 'my_account_password_update'
134 'my_account_password', 'my_account_password_update'
135 ]
135 ]
136
136
137 if not user_obj:
137 if not user_obj:
138 return
138 return
139
139
140 if user_obj.username == User.DEFAULT_USER:
140 if user_obj.username == User.DEFAULT_USER:
141 return
141 return
142
142
143 now = time.time()
143 now = time.time()
144 should_change = user_obj.user_data.get('force_password_change')
144 should_change = user_obj.user_data.get('force_password_change')
145 change_after = safe_int(should_change) or 0
145 change_after = safe_int(should_change) or 0
146 if should_change and now > change_after:
146 if should_change and now > change_after:
147 log.debug('User %s requires password change', user_obj)
147 log.debug('User %s requires password change', user_obj)
148 h.flash('You are required to change your password', 'warning',
148 h.flash('You are required to change your password', 'warning',
149 ignore_duplicate=True)
149 ignore_duplicate=True)
150
150
151 if view_name not in skip_user_views:
151 if view_name not in skip_user_views:
152 raise HTTPFound(
152 raise HTTPFound(
153 self.request.route_path('my_account_password'))
153 self.request.route_path('my_account_password'))
154
154
155 def _log_creation_exception(self, e, repo_name):
155 def _log_creation_exception(self, e, repo_name):
156 _ = self.request.translate
156 _ = self.request.translate
157 reason = None
157 reason = None
158 if len(e.args) == 2:
158 if len(e.args) == 2:
159 reason = e.args[1]
159 reason = e.args[1]
160
160
161 if reason == 'INVALID_CERTIFICATE':
161 if reason == 'INVALID_CERTIFICATE':
162 log.exception(
162 log.exception(
163 'Exception creating a repository: invalid certificate')
163 'Exception creating a repository: invalid certificate')
164 msg = (_('Error creating repository %s: invalid certificate')
164 msg = (_('Error creating repository %s: invalid certificate')
165 % repo_name)
165 % repo_name)
166 else:
166 else:
167 log.exception("Exception creating a repository")
167 log.exception("Exception creating a repository")
168 msg = (_('Error creating repository %s')
168 msg = (_('Error creating repository %s')
169 % repo_name)
169 % repo_name)
170 return msg
170 return msg
171
171
172 def _get_local_tmpl_context(self, include_app_defaults=True):
172 def _get_local_tmpl_context(self, include_app_defaults=True):
173 c = TemplateArgs()
173 c = TemplateArgs()
174 c.auth_user = self.request.user
174 c.auth_user = self.request.user
175 # TODO(marcink): migrate the usage of c.rhodecode_user to c.auth_user
175 # TODO(marcink): migrate the usage of c.rhodecode_user to c.auth_user
176 c.rhodecode_user = self.request.user
176 c.rhodecode_user = self.request.user
177
177
178 if include_app_defaults:
178 if include_app_defaults:
179 from rhodecode.lib.base import attach_context_attributes
179 from rhodecode.lib.base import attach_context_attributes
180 attach_context_attributes(c, self.request, self.request.user.user_id)
180 attach_context_attributes(c, self.request, self.request.user.user_id)
181
181
182 c.is_super_admin = c.auth_user.is_admin
182 c.is_super_admin = c.auth_user.is_admin
183
183
184 c.can_create_repo = c.is_super_admin
184 c.can_create_repo = c.is_super_admin
185 c.can_create_repo_group = c.is_super_admin
185 c.can_create_repo_group = c.is_super_admin
186 c.can_create_user_group = c.is_super_admin
186 c.can_create_user_group = c.is_super_admin
187
187
188 c.is_delegated_admin = False
188 c.is_delegated_admin = False
189
189
190 if not c.auth_user.is_default and not c.is_super_admin:
190 if not c.auth_user.is_default and not c.is_super_admin:
191 c.can_create_repo = h.HasPermissionAny('hg.create.repository')(
191 c.can_create_repo = h.HasPermissionAny('hg.create.repository')(
192 user=self.request.user)
192 user=self.request.user)
193 repositories = c.auth_user.repositories_admin or c.can_create_repo
193 repositories = c.auth_user.repositories_admin or c.can_create_repo
194
194
195 c.can_create_repo_group = h.HasPermissionAny('hg.repogroup.create.true')(
195 c.can_create_repo_group = h.HasPermissionAny('hg.repogroup.create.true')(
196 user=self.request.user)
196 user=self.request.user)
197 repository_groups = c.auth_user.repository_groups_admin or c.can_create_repo_group
197 repository_groups = c.auth_user.repository_groups_admin or c.can_create_repo_group
198
198
199 c.can_create_user_group = h.HasPermissionAny('hg.usergroup.create.true')(
199 c.can_create_user_group = h.HasPermissionAny('hg.usergroup.create.true')(
200 user=self.request.user)
200 user=self.request.user)
201 user_groups = c.auth_user.user_groups_admin or c.can_create_user_group
201 user_groups = c.auth_user.user_groups_admin or c.can_create_user_group
202 # delegated admin can create, or manage some objects
202 # delegated admin can create, or manage some objects
203 c.is_delegated_admin = repositories or repository_groups or user_groups
203 c.is_delegated_admin = repositories or repository_groups or user_groups
204 return c
204 return c
205
205
206 def _get_template_context(self, tmpl_args, **kwargs):
206 def _get_template_context(self, tmpl_args, **kwargs):
207
207
208 local_tmpl_args = {
208 local_tmpl_args = {
209 'defaults': {},
209 'defaults': {},
210 'errors': {},
210 'errors': {},
211 'c': tmpl_args
211 'c': tmpl_args
212 }
212 }
213 local_tmpl_args.update(kwargs)
213 local_tmpl_args.update(kwargs)
214 return local_tmpl_args
214 return local_tmpl_args
215
215
216 def load_default_context(self):
216 def load_default_context(self):
217 """
217 """
218 example:
218 example:
219
219
220 def load_default_context(self):
220 def load_default_context(self):
221 c = self._get_local_tmpl_context()
221 c = self._get_local_tmpl_context()
222 c.custom_var = 'foobar'
222 c.custom_var = 'foobar'
223
223
224 return c
224 return c
225 """
225 """
226 raise NotImplementedError('Needs implementation in view class')
226 raise NotImplementedError('Needs implementation in view class')
227
227
228
228
229 class RepoAppView(BaseAppView):
229 class RepoAppView(BaseAppView):
230
230
231 def __init__(self, context, request):
231 def __init__(self, context, request):
232 super().__init__(context, request)
232 super().__init__(context, request)
233 self.db_repo = request.db_repo
233 self.db_repo = request.db_repo
234 self.db_repo_name = self.db_repo.repo_name
234 self.db_repo_name = self.db_repo.repo_name
235 self.db_repo_pull_requests = ScmModel().get_pull_requests(self.db_repo)
235 self.db_repo_pull_requests = ScmModel().get_pull_requests(self.db_repo)
236 self.db_repo_artifacts = ScmModel().get_artifacts(self.db_repo)
236 self.db_repo_artifacts = ScmModel().get_artifacts(self.db_repo)
237 self.db_repo_patterns = IssueTrackerSettingsModel(repo=self.db_repo)
237 self.db_repo_patterns = IssueTrackerSettingsModel(repo=self.db_repo)
238
238
239 def _handle_missing_requirements(self, error):
239 def _handle_missing_requirements(self, error):
240 log.error(
240 log.error(
241 'Requirements are missing for repository %s: %s',
241 'Requirements are missing for repository %s: %s',
242 self.db_repo_name, safe_str(error))
242 self.db_repo_name, safe_str(error))
243
243
244 def _prepare_and_set_clone_url(self, c):
244 def _prepare_and_set_clone_url(self, c):
245 username = ''
245 username = ''
246 if self._rhodecode_user.username != User.DEFAULT_USER:
246 if self._rhodecode_user.username != User.DEFAULT_USER:
247 username = self._rhodecode_user.username
247 username = self._rhodecode_user.username
248
248
249 _def_clone_uri = c.clone_uri_tmpl
249 _def_clone_uri = c.clone_uri_tmpl
250 _def_clone_uri_id = c.clone_uri_id_tmpl
250 _def_clone_uri_id = c.clone_uri_id_tmpl
251 _def_clone_uri_ssh = c.clone_uri_ssh_tmpl
251 _def_clone_uri_ssh = c.clone_uri_ssh_tmpl
252
252
253 c.clone_repo_url = self.db_repo.clone_url(
253 c.clone_repo_url = self.db_repo.clone_url(
254 user=username, uri_tmpl=_def_clone_uri)
254 user=username, uri_tmpl=_def_clone_uri)
255 c.clone_repo_url_id = self.db_repo.clone_url(
255 c.clone_repo_url_id = self.db_repo.clone_url(
256 user=username, uri_tmpl=_def_clone_uri_id)
256 user=username, uri_tmpl=_def_clone_uri_id)
257 c.clone_repo_url_ssh = self.db_repo.clone_url(
257 c.clone_repo_url_ssh = self.db_repo.clone_url(
258 uri_tmpl=_def_clone_uri_ssh, ssh=True)
258 uri_tmpl=_def_clone_uri_ssh, ssh=True)
259
259
260 def _get_local_tmpl_context(self, include_app_defaults=True):
260 def _get_local_tmpl_context(self, include_app_defaults=True):
261 _ = self.request.translate
261 _ = self.request.translate
262 c = super()._get_local_tmpl_context(
262 c = super()._get_local_tmpl_context(
263 include_app_defaults=include_app_defaults)
263 include_app_defaults=include_app_defaults)
264
264
265 # register common vars for this type of view
265 # register common vars for this type of view
266 c.rhodecode_db_repo = self.db_repo
266 c.rhodecode_db_repo = self.db_repo
267 c.repo_name = self.db_repo_name
267 c.repo_name = self.db_repo_name
268 c.repository_pull_requests = self.db_repo_pull_requests
268 c.repository_pull_requests = self.db_repo_pull_requests
269 c.repository_artifacts = self.db_repo_artifacts
269 c.repository_artifacts = self.db_repo_artifacts
270 c.repository_is_user_following = ScmModel().is_following_repo(
270 c.repository_is_user_following = ScmModel().is_following_repo(
271 self.db_repo_name, self._rhodecode_user.user_id)
271 self.db_repo_name, self._rhodecode_user.user_id)
272 self.path_filter = PathFilter(None)
272 self.path_filter = PathFilter(None)
273
273
274 c.repository_requirements_missing = {}
274 c.repository_requirements_missing = {}
275 try:
275 try:
276 self.rhodecode_vcs_repo = self.db_repo.scm_instance()
276 self.rhodecode_vcs_repo = self.db_repo.scm_instance()
277 # NOTE(marcink):
277 # NOTE(marcink):
278 # comparison to None since if it's an object __bool__ is expensive to
278 # comparison to None since if it's an object __bool__ is expensive to
279 # calculate
279 # calculate
280 if self.rhodecode_vcs_repo is not None:
280 if self.rhodecode_vcs_repo is not None:
281 path_perms = self.rhodecode_vcs_repo.get_path_permissions(
281 path_perms = self.rhodecode_vcs_repo.get_path_permissions(
282 c.auth_user.username)
282 c.auth_user.username)
283 self.path_filter = PathFilter(path_perms)
283 self.path_filter = PathFilter(path_perms)
284 except RepositoryRequirementError as e:
284 except RepositoryRequirementError as e:
285 c.repository_requirements_missing = {'error': str(e)}
285 c.repository_requirements_missing = {'error': str(e)}
286 self._handle_missing_requirements(e)
286 self._handle_missing_requirements(e)
287 self.rhodecode_vcs_repo = None
287 self.rhodecode_vcs_repo = None
288
288
289 c.path_filter = self.path_filter # used by atom_feed_entry.mako
289 c.path_filter = self.path_filter # used by atom_feed_entry.mako
290
290
291 if self.rhodecode_vcs_repo is None:
291 if self.rhodecode_vcs_repo is None:
292 # unable to fetch this repo as vcs instance, report back to user
292 # unable to fetch this repo as vcs instance, report back to user
293 log.debug('Repository was not found on filesystem, check if it exists or is not damaged')
293 log.debug('Repository was not found on filesystem, check if it exists or is not damaged')
294 h.flash(_(
294 h.flash(_(
295 "The repository `%(repo_name)s` cannot be loaded in filesystem. "
295 "The repository `%(repo_name)s` cannot be loaded in filesystem. "
296 "Please check if it exist, or is not damaged.") %
296 "Please check if it exist, or is not damaged.") %
297 {'repo_name': c.repo_name},
297 {'repo_name': c.repo_name},
298 category='error', ignore_duplicate=True)
298 category='error', ignore_duplicate=True)
299 if c.repository_requirements_missing:
299 if c.repository_requirements_missing:
300 route = self.request.matched_route.name
300 route = self.request.matched_route.name
301 if route.startswith(('edit_repo', 'repo_summary')):
301 if route.startswith(('edit_repo', 'repo_summary')):
302 # allow summary and edit repo on missing requirements
302 # allow summary and edit repo on missing requirements
303 return c
303 return c
304
304
305 raise HTTPFound(
305 raise HTTPFound(
306 h.route_path('repo_summary', repo_name=self.db_repo_name))
306 h.route_path('repo_summary', repo_name=self.db_repo_name))
307
307
308 else: # redirect if we don't show missing requirements
308 else: # redirect if we don't show missing requirements
309 raise HTTPFound(h.route_path('home'))
309 raise HTTPFound(h.route_path('home'))
310
310
311 c.has_origin_repo_read_perm = False
311 c.has_origin_repo_read_perm = False
312 if self.db_repo.fork:
312 if self.db_repo.fork:
313 c.has_origin_repo_read_perm = h.HasRepoPermissionAny(
313 c.has_origin_repo_read_perm = h.HasRepoPermissionAny(
314 'repository.write', 'repository.read', 'repository.admin')(
314 'repository.write', 'repository.read', 'repository.admin')(
315 self.db_repo.fork.repo_name, 'summary fork link')
315 self.db_repo.fork.repo_name, 'summary fork link')
316
316
317 return c
317 return c
318
318
319 def _get_f_path_unchecked(self, matchdict, default=None):
319 def _get_f_path_unchecked(self, matchdict, default=None):
320 """
320 """
321 Should only be used by redirects, everything else should call _get_f_path
321 Should only be used by redirects, everything else should call _get_f_path
322 """
322 """
323 f_path = matchdict.get('f_path')
323 f_path = matchdict.get('f_path')
324 if f_path:
324 if f_path:
325 # fix for multiple initial slashes that causes errors for GIT
325 # fix for multiple initial slashes that causes errors for GIT
326 return f_path.lstrip('/')
326 return f_path.lstrip('/')
327
327
328 return default
328 return default
329
329
330 def _get_f_path(self, matchdict, default=None):
330 def _get_f_path(self, matchdict, default=None):
331 f_path_match = self._get_f_path_unchecked(matchdict, default)
331 f_path_match = self._get_f_path_unchecked(matchdict, default)
332 return self.path_filter.assert_path_permissions(f_path_match)
332 return self.path_filter.assert_path_permissions(f_path_match)
333
333
334 def _get_general_setting(self, target_repo, settings_key, default=False):
334 def _get_general_setting(self, target_repo, settings_key, default=False):
335 settings_model = VcsSettingsModel(repo=target_repo)
335 settings_model = VcsSettingsModel(repo=target_repo)
336 settings = settings_model.get_general_settings()
336 settings = settings_model.get_general_settings()
337 return settings.get(settings_key, default)
337 return settings.get(settings_key, default)
338
338
339 def _get_repo_setting(self, target_repo, settings_key, default=False):
339 def _get_repo_setting(self, target_repo, settings_key, default=False):
340 settings_model = VcsSettingsModel(repo=target_repo)
340 settings_model = VcsSettingsModel(repo=target_repo)
341 settings = settings_model.get_repo_settings_inherited()
341 settings = settings_model.get_repo_settings_inherited()
342 return settings.get(settings_key, default)
342 return settings.get(settings_key, default)
343
343
344 def _get_readme_data(self, db_repo, renderer_type, commit_id=None, path='/'):
344 def _get_readme_data(self, db_repo, renderer_type, commit_id=None, path='/'):
345 log.debug('Looking for README file at path %s', path)
345 log.debug('Looking for README file at path %s', path)
346 if commit_id:
346 if commit_id:
347 landing_commit_id = commit_id
347 landing_commit_id = commit_id
348 else:
348 else:
349 landing_commit = db_repo.get_landing_commit()
349 landing_commit = db_repo.get_landing_commit()
350 if isinstance(landing_commit, EmptyCommit):
350 if isinstance(landing_commit, EmptyCommit):
351 return None, None
351 return None, None
352 landing_commit_id = landing_commit.raw_id
352 landing_commit_id = landing_commit.raw_id
353
353
354 cache_namespace_uid = f'repo.{db_repo.repo_id}'
354 cache_namespace_uid = f'repo.{db_repo.repo_id}'
355 region = rc_cache.get_or_create_region('cache_repo', cache_namespace_uid, use_async_runner=True)
355 region = rc_cache.get_or_create_region('cache_repo', cache_namespace_uid, use_async_runner=True)
356 start = time.time()
356 start = time.time()
357
357
358 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid)
358 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid)
359 def generate_repo_readme(repo_id, _commit_id, _repo_name, _readme_search_path, _renderer_type):
359 def generate_repo_readme(repo_id, _commit_id, _repo_name, _readme_search_path, _renderer_type):
360 readme_data = None
360 readme_data = None
361 readme_filename = None
361 readme_filename = None
362
362
363 commit = db_repo.get_commit(_commit_id)
363 commit = db_repo.get_commit(_commit_id)
364 log.debug("Searching for a README file at commit %s.", _commit_id)
364 log.debug("Searching for a README file at commit %s.", _commit_id)
365 readme_node = ReadmeFinder(_renderer_type).search(commit, path=_readme_search_path)
365 readme_node = ReadmeFinder(_renderer_type).search(commit, path=_readme_search_path)
366
366
367 if readme_node:
367 if readme_node:
368 log.debug('Found README node: %s', readme_node)
368 log.debug('Found README node: %s', readme_node)
369 relative_urls = {
369 relative_urls = {
370 'raw': h.route_path(
370 'raw': h.route_path(
371 'repo_file_raw', repo_name=_repo_name,
371 'repo_file_raw', repo_name=_repo_name,
372 commit_id=commit.raw_id, f_path=readme_node.path),
372 commit_id=commit.raw_id, f_path=readme_node.path),
373 'standard': h.route_path(
373 'standard': h.route_path(
374 'repo_files', repo_name=_repo_name,
374 'repo_files', repo_name=_repo_name,
375 commit_id=commit.raw_id, f_path=readme_node.path),
375 commit_id=commit.raw_id, f_path=readme_node.path),
376 }
376 }
377
377
378 readme_data = self._render_readme_or_none(commit, readme_node, relative_urls)
378 readme_data = self._render_readme_or_none(commit, readme_node, relative_urls)
379 readme_filename = readme_node.str_path
379 readme_filename = readme_node.str_path
380
380
381 return readme_data, readme_filename
381 return readme_data, readme_filename
382
382
383 readme_data, readme_filename = generate_repo_readme(
383 readme_data, readme_filename = generate_repo_readme(
384 db_repo.repo_id, landing_commit_id, db_repo.repo_name, path, renderer_type,)
384 db_repo.repo_id, landing_commit_id, db_repo.repo_name, path, renderer_type,)
385
385
386 compute_time = time.time() - start
386 compute_time = time.time() - start
387 log.debug('Repo README for path %s generated and computed in %.4fs',
387 log.debug('Repo README for path %s generated and computed in %.4fs',
388 path, compute_time)
388 path, compute_time)
389 return readme_data, readme_filename
389 return readme_data, readme_filename
390
390
391 def _render_readme_or_none(self, commit, readme_node, relative_urls):
391 def _render_readme_or_none(self, commit, readme_node, relative_urls):
392 log.debug('Found README file `%s` rendering...', readme_node.path)
392 log.debug('Found README file `%s` rendering...', readme_node.path)
393 renderer = MarkupRenderer()
393 renderer = MarkupRenderer()
394 try:
394 try:
395 html_source = renderer.render(
395 html_source = renderer.render(
396 readme_node.str_content, filename=readme_node.path)
396 readme_node.str_content, filename=readme_node.path)
397 if relative_urls:
397 if relative_urls:
398 return relative_links(html_source, relative_urls)
398 return relative_links(html_source, relative_urls)
399 return html_source
399 return html_source
400 except Exception:
400 except Exception:
401 log.exception("Exception while trying to render the README")
401 log.exception("Exception while trying to render the README")
402
402
403 def get_recache_flag(self):
403 def get_recache_flag(self):
404 for flag_name in ['force_recache', 'force-recache', 'no-cache']:
404 for flag_name in ['force_recache', 'force-recache', 'no-cache']:
405 flag_val = self.request.GET.get(flag_name)
405 flag_val = self.request.GET.get(flag_name)
406 if str2bool(flag_val):
406 if str2bool(flag_val):
407 return True
407 return True
408 return False
408 return False
409
409
410 def get_commit_preload_attrs(cls):
410 def get_commit_preload_attrs(cls):
411 pre_load = ['author', 'branch', 'date', 'message', 'parents',
411 pre_load = ['author', 'branch', 'date', 'message', 'parents',
412 'obsolete', 'phase', 'hidden']
412 'obsolete', 'phase', 'hidden']
413 return pre_load
413 return pre_load
414
414
415
415
416 class PathFilter(object):
416 class PathFilter(object):
417
417
418 # Expects and instance of BasePathPermissionChecker or None
418 # Expects and instance of BasePathPermissionChecker or None
419 def __init__(self, permission_checker):
419 def __init__(self, permission_checker):
420 self.permission_checker = permission_checker
420 self.permission_checker = permission_checker
421
421
422 def assert_path_permissions(self, path):
422 def assert_path_permissions(self, path):
423 if self.path_access_allowed(path):
423 if self.path_access_allowed(path):
424 return path
424 return path
425 raise HTTPForbidden()
425 raise HTTPForbidden()
426
426
427 def path_access_allowed(self, path):
427 def path_access_allowed(self, path):
428 log.debug('Checking ACL permissions for PathFilter for `%s`', path)
428 log.debug('Checking ACL permissions for PathFilter for `%s`', path)
429 if self.permission_checker:
429 if self.permission_checker:
430 has_access = path and self.permission_checker.has_access(path)
430 has_access = path and self.permission_checker.has_access(path)
431 log.debug('ACL Permissions checker enabled, ACL Check has_access: %s', has_access)
431 log.debug('ACL Permissions checker enabled, ACL Check has_access: %s', has_access)
432 return has_access
432 return has_access
433
433
434 log.debug('ACL permissions checker not enabled, skipping...')
434 log.debug('ACL permissions checker not enabled, skipping...')
435 return True
435 return True
436
436
437 def filter_patchset(self, patchset):
437 def filter_patchset(self, patchset):
438 if not self.permission_checker or not patchset:
438 if not self.permission_checker or not patchset:
439 return patchset, False
439 return patchset, False
440 had_filtered = False
440 had_filtered = False
441 filtered_patchset = []
441 filtered_patchset = []
442 for patch in patchset:
442 for patch in patchset:
443 filename = patch.get('filename', None)
443 filename = patch.get('filename', None)
444 if not filename or self.permission_checker.has_access(filename):
444 if not filename or self.permission_checker.has_access(filename):
445 filtered_patchset.append(patch)
445 filtered_patchset.append(patch)
446 else:
446 else:
447 had_filtered = True
447 had_filtered = True
448 if had_filtered:
448 if had_filtered:
449 if isinstance(patchset, diffs.LimitedDiffContainer):
449 if isinstance(patchset, diffs.LimitedDiffContainer):
450 filtered_patchset = diffs.LimitedDiffContainer(patchset.diff_limit, patchset.cur_diff_size, filtered_patchset)
450 filtered_patchset = diffs.LimitedDiffContainer(patchset.diff_limit, patchset.cur_diff_size, filtered_patchset)
451 return filtered_patchset, True
451 return filtered_patchset, True
452 else:
452 else:
453 return patchset, False
453 return patchset, False
454
454
455 def render_patchset_filtered(self, diffset, patchset, source_ref=None, target_ref=None):
455 def render_patchset_filtered(self, diffset, patchset, source_ref=None, target_ref=None):
456
456
457 filtered_patchset, has_hidden_changes = self.filter_patchset(patchset)
457 filtered_patchset, has_hidden_changes = self.filter_patchset(patchset)
458 result = diffset.render_patchset(
458 result = diffset.render_patchset(
459 filtered_patchset, source_ref=source_ref, target_ref=target_ref)
459 filtered_patchset, source_ref=source_ref, target_ref=target_ref)
460 result.has_hidden_changes = has_hidden_changes
460 result.has_hidden_changes = has_hidden_changes
461 return result
461 return result
462
462
463 def get_raw_patch(self, diff_processor):
463 def get_raw_patch(self, diff_processor):
464 if self.permission_checker is None:
464 if self.permission_checker is None:
465 return diff_processor.as_raw()
465 return diff_processor.as_raw()
466 elif self.permission_checker.has_full_access:
466 elif self.permission_checker.has_full_access:
467 return diff_processor.as_raw()
467 return diff_processor.as_raw()
468 else:
468 else:
469 return '# Repository has user-specific filters, raw patch generation is disabled.'
469 return '# Repository has user-specific filters, raw patch generation is disabled.'
470
470
471 @property
471 @property
472 def is_enabled(self):
472 def is_enabled(self):
473 return self.permission_checker is not None
473 return self.permission_checker is not None
474
474
475
475
476 class RepoGroupAppView(BaseAppView):
476 class RepoGroupAppView(BaseAppView):
477 def __init__(self, context, request):
477 def __init__(self, context, request):
478 super().__init__(context, request)
478 super().__init__(context, request)
479 self.db_repo_group = request.db_repo_group
479 self.db_repo_group = request.db_repo_group
480 self.db_repo_group_name = self.db_repo_group.group_name
480 self.db_repo_group_name = self.db_repo_group.group_name
481
481
482 def _get_local_tmpl_context(self, include_app_defaults=True):
482 def _get_local_tmpl_context(self, include_app_defaults=True):
483 _ = self.request.translate
483 _ = self.request.translate
484 c = super()._get_local_tmpl_context(
484 c = super()._get_local_tmpl_context(
485 include_app_defaults=include_app_defaults)
485 include_app_defaults=include_app_defaults)
486 c.repo_group = self.db_repo_group
486 c.repo_group = self.db_repo_group
487 return c
487 return c
488
488
489 def _revoke_perms_on_yourself(self, form_result):
489 def _revoke_perms_on_yourself(self, form_result):
490 _updates = [u for u in form_result['perm_updates'] if self._rhodecode_user.user_id == int(u[0])]
490 _updates = [u for u in form_result['perm_updates'] if self._rhodecode_user.user_id == int(u[0])]
491 _additions = [u for u in form_result['perm_additions'] if self._rhodecode_user.user_id == int(u[0])]
491 _additions = [u for u in form_result['perm_additions'] if self._rhodecode_user.user_id == int(u[0])]
492 _deletions = [u for u in form_result['perm_deletions'] if self._rhodecode_user.user_id == int(u[0])]
492 _deletions = [u for u in form_result['perm_deletions'] if self._rhodecode_user.user_id == int(u[0])]
493 admin_perm = 'group.admin'
493 admin_perm = 'group.admin'
494 if _updates and _updates[0][1] != admin_perm or \
494 if _updates and _updates[0][1] != admin_perm or \
495 _additions and _additions[0][1] != admin_perm or \
495 _additions and _additions[0][1] != admin_perm or \
496 _deletions and _deletions[0][1] != admin_perm:
496 _deletions and _deletions[0][1] != admin_perm:
497 return True
497 return True
498 return False
498 return False
499
499
500
500
501 class UserGroupAppView(BaseAppView):
501 class UserGroupAppView(BaseAppView):
502 def __init__(self, context, request):
502 def __init__(self, context, request):
503 super().__init__(context, request)
503 super().__init__(context, request)
504 self.db_user_group = request.db_user_group
504 self.db_user_group = request.db_user_group
505 self.db_user_group_name = self.db_user_group.users_group_name
505 self.db_user_group_name = self.db_user_group.users_group_name
506
506
507
507
508 class UserAppView(BaseAppView):
508 class UserAppView(BaseAppView):
509 def __init__(self, context, request):
509 def __init__(self, context, request):
510 super().__init__(context, request)
510 super().__init__(context, request)
511 self.db_user = request.db_user
511 self.db_user = request.db_user
512 self.db_user_id = self.db_user.user_id
512 self.db_user_id = self.db_user.user_id
513
513
514 _ = self.request.translate
514 _ = self.request.translate
515 if not request.db_user_supports_default:
515 if not request.db_user_supports_default:
516 if self.db_user.username == User.DEFAULT_USER:
516 if self.db_user.username == User.DEFAULT_USER:
517 h.flash(_("Editing user `{}` is disabled.".format(
517 h.flash(_("Editing user `{}` is disabled.".format(
518 User.DEFAULT_USER)), category='warning')
518 User.DEFAULT_USER)), category='warning')
519 raise HTTPFound(h.route_path('users'))
519 raise HTTPFound(h.route_path('users'))
520
520
521
521
522 class DataGridAppView(object):
522 class DataGridAppView(object):
523 """
523 """
524 Common class to have re-usable grid rendering components
524 Common class to have re-usable grid rendering components
525 """
525 """
526
526
527 def _extract_ordering(self, request, column_map=None):
527 def _extract_ordering(self, request, column_map=None):
528 column_map = column_map or {}
528 column_map = column_map or {}
529 column_index = safe_int(request.GET.get('order[0][column]'))
529 column_index = safe_int(request.GET.get('order[0][column]'))
530 order_dir = request.GET.get(
530 order_dir = request.GET.get(
531 'order[0][dir]', 'desc')
531 'order[0][dir]', 'desc')
532 order_by = request.GET.get(
532 order_by = request.GET.get(
533 'columns[%s][data][sort]' % column_index, 'name_raw')
533 'columns[%s][data][sort]' % column_index, 'name_raw')
534
534
535 # translate datatable to DB columns
535 # translate datatable to DB columns
536 order_by = column_map.get(order_by) or order_by
536 order_by = column_map.get(order_by) or order_by
537
537
538 search_q = request.GET.get('search[value]')
538 search_q = request.GET.get('search[value]')
539 return search_q, order_by, order_dir
539 return search_q, order_by, order_dir
540
540
541 def _extract_chunk(self, request):
541 def _extract_chunk(self, request):
542 start = safe_int(request.GET.get('start'), 0)
542 start = safe_int(request.GET.get('start'), 0)
543 length = safe_int(request.GET.get('length'), 25)
543 length = safe_int(request.GET.get('length'), 25)
544 draw = safe_int(request.GET.get('draw'))
544 draw = safe_int(request.GET.get('draw'))
545 return draw, start, length
545 return draw, start, length
546
546
547 def _get_order_col(self, order_by, model):
547 def _get_order_col(self, order_by, model):
548 if isinstance(order_by, str):
548 if isinstance(order_by, str):
549 try:
549 try:
550 return operator.attrgetter(order_by)(model)
550 return operator.attrgetter(order_by)(model)
551 except AttributeError:
551 except AttributeError:
552 return None
552 return None
553 else:
553 else:
554 return order_by
554 return order_by
555
555
556
556
557 class BaseReferencesView(RepoAppView):
557 class BaseReferencesView(RepoAppView):
558 """
558 """
559 Base for reference view for branches, tags and bookmarks.
559 Base for reference view for branches, tags and bookmarks.
560 """
560 """
561 def load_default_context(self):
561 def load_default_context(self):
562 c = self._get_local_tmpl_context()
562 c = self._get_local_tmpl_context()
563 return c
563 return c
564
564
565 def load_refs_context(self, ref_items, partials_template):
565 def load_refs_context(self, ref_items, partials_template):
566 _render = self.request.get_partial_renderer(partials_template)
566 _render = self.request.get_partial_renderer(partials_template)
567 pre_load = ["author", "date", "message", "parents"]
567 pre_load = ["author", "date", "message", "parents"]
568
568
569 is_svn = h.is_svn(self.rhodecode_vcs_repo)
569 is_svn = h.is_svn(self.rhodecode_vcs_repo)
570 is_hg = h.is_hg(self.rhodecode_vcs_repo)
570 is_hg = h.is_hg(self.rhodecode_vcs_repo)
571
571
572 format_ref_id = get_format_ref_id(self.rhodecode_vcs_repo)
572 format_ref_id = get_format_ref_id(self.rhodecode_vcs_repo)
573
573
574 closed_refs = {}
574 closed_refs = {}
575 if is_hg:
575 if is_hg:
576 closed_refs = self.rhodecode_vcs_repo.branches_closed
576 closed_refs = self.rhodecode_vcs_repo.branches_closed
577
577
578 data = []
578 data = []
579 for ref_name, commit_id in ref_items:
579 for ref_name, commit_id in ref_items:
580 commit = self.rhodecode_vcs_repo.get_commit(
580 commit = self.rhodecode_vcs_repo.get_commit(
581 commit_id=commit_id, pre_load=pre_load)
581 commit_id=commit_id, pre_load=pre_load)
582 closed = ref_name in closed_refs
582 closed = ref_name in closed_refs
583
583
584 # TODO: johbo: Unify generation of reference links
584 # TODO: johbo: Unify generation of reference links
585 use_commit_id = '/' in ref_name or is_svn
585 use_commit_id = '/' in ref_name or is_svn
586
586
587 if use_commit_id:
587 if use_commit_id:
588 files_url = h.route_path(
588 files_url = h.route_path(
589 'repo_files',
589 'repo_files',
590 repo_name=self.db_repo_name,
590 repo_name=self.db_repo_name,
591 f_path=ref_name if is_svn else '',
591 f_path=ref_name if is_svn else '',
592 commit_id=commit_id,
592 commit_id=commit_id,
593 _query=dict(at=ref_name)
593 _query=dict(at=ref_name)
594 )
594 )
595
595
596 else:
596 else:
597 files_url = h.route_path(
597 files_url = h.route_path(
598 'repo_files',
598 'repo_files',
599 repo_name=self.db_repo_name,
599 repo_name=self.db_repo_name,
600 f_path=ref_name if is_svn else '',
600 f_path=ref_name if is_svn else '',
601 commit_id=ref_name,
601 commit_id=ref_name,
602 _query=dict(at=ref_name)
602 _query=dict(at=ref_name)
603 )
603 )
604
604
605 data.append({
605 data.append({
606 "name": _render('name', ref_name, files_url, closed),
606 "name": _render('name', ref_name, files_url, closed),
607 "name_raw": ref_name,
607 "name_raw": ref_name,
608 "date": _render('date', commit.date),
608 "date": _render('date', commit.date),
609 "date_raw": datetime_to_time(commit.date),
609 "date_raw": datetime_to_time(commit.date),
610 "author": _render('author', commit.author),
610 "author": _render('author', commit.author),
611 "commit": _render(
611 "commit": _render(
612 'commit', commit.message, commit.raw_id, commit.idx),
612 'commit', commit.message, commit.raw_id, commit.idx),
613 "commit_raw": commit.idx,
613 "commit_raw": commit.idx,
614 "compare": _render(
614 "compare": _render(
615 'compare', format_ref_id(ref_name, commit.raw_id)),
615 'compare', format_ref_id(ref_name, commit.raw_id)),
616 })
616 })
617
617
618 return data
618 return data
619
619
620
620
621 class RepoRoutePredicate(object):
621 class RepoRoutePredicate(object):
622 def __init__(self, val, config):
622 def __init__(self, val, config):
623 self.val = val
623 self.val = val
624
624
625 def text(self):
625 def text(self):
626 return f'repo_route = {self.val}'
626 return f'repo_route = {self.val}'
627
627
628 phash = text
628 phash = text
629
629
630 def __call__(self, info, request):
630 def __call__(self, info, request):
631 if hasattr(request, 'vcs_call'):
631 if hasattr(request, 'vcs_call'):
632 # skip vcs calls
632 # skip vcs calls
633 return
633 return
634
634
635 repo_name = info['match']['repo_name']
635 repo_name = info['match']['repo_name']
636
636
637 repo_name_parts = repo_name.split('/')
637 repo_name_parts = repo_name.split('/')
638 repo_slugs = [x for x in (repo_name_slug(x) for x in repo_name_parts)]
638 repo_slugs = [x for x in (repo_name_slug(x) for x in repo_name_parts)]
639
639
640 if repo_name_parts != repo_slugs:
640 if repo_name_parts != repo_slugs:
641 # short-skip if the repo-name doesn't follow slug rule
641 # short-skip if the repo-name doesn't follow slug rule
642 log.warning('repo_name: %s is different than slug %s', repo_name_parts, repo_slugs)
642 log.warning('repo_name: %s is different than slug %s', repo_name_parts, repo_slugs)
643 return False
643 return False
644
644
645 repo_model = repo.RepoModel()
645 repo_model = repo.RepoModel()
646
646
647 by_name_match = repo_model.get_by_repo_name(repo_name, cache=False)
647 by_name_match = repo_model.get_by_repo_name(repo_name, cache=False)
648
648
649 def redirect_if_creating(route_info, db_repo):
649 def redirect_if_creating(route_info, db_repo):
650 skip_views = ['edit_repo_advanced_delete']
650 skip_views = ['edit_repo_advanced_delete']
651 route = route_info['route']
651 route = route_info['route']
652 # we should skip delete view so we can actually "remove" repositories
652 # we should skip delete view so we can actually "remove" repositories
653 # if they get stuck in creating state.
653 # if they get stuck in creating state.
654 if route.name in skip_views:
654 if route.name in skip_views:
655 return
655 return
656
656
657 if db_repo.repo_state in [repo.Repository.STATE_PENDING]:
657 if db_repo.repo_state in [repo.Repository.STATE_PENDING]:
658 repo_creating_url = request.route_path(
658 repo_creating_url = request.route_path(
659 'repo_creating', repo_name=db_repo.repo_name)
659 'repo_creating', repo_name=db_repo.repo_name)
660 raise HTTPFound(repo_creating_url)
660 raise HTTPFound(repo_creating_url)
661
661
662 if by_name_match:
662 if by_name_match:
663 # register this as request object we can re-use later
663 # register this as request object we can re-use later
664 request.db_repo = by_name_match
664 request.db_repo = by_name_match
665 request.db_repo_name = request.db_repo.repo_name
665 request.db_repo_name = request.db_repo.repo_name
666
666
667 redirect_if_creating(info, by_name_match)
667 redirect_if_creating(info, by_name_match)
668 return True
668 return True
669
669
670 by_id_match = repo_model.get_repo_by_id(repo_name)
670 by_id_match = repo_model.get_repo_by_id(repo_name)
671 if by_id_match:
671 if by_id_match:
672 request.db_repo = by_id_match
672 request.db_repo = by_id_match
673 request.db_repo_name = request.db_repo.repo_name
673 request.db_repo_name = request.db_repo.repo_name
674 redirect_if_creating(info, by_id_match)
674 redirect_if_creating(info, by_id_match)
675 return True
675 return True
676
676
677 return False
677 return False
678
678
679
679
680 class RepoForbidArchivedRoutePredicate(object):
680 class RepoForbidArchivedRoutePredicate(object):
681 def __init__(self, val, config):
681 def __init__(self, val, config):
682 self.val = val
682 self.val = val
683
683
684 def text(self):
684 def text(self):
685 return f'repo_forbid_archived = {self.val}'
685 return f'repo_forbid_archived = {self.val}'
686
686
687 phash = text
687 phash = text
688
688
689 def __call__(self, info, request):
689 def __call__(self, info, request):
690 _ = request.translate
690 _ = request.translate
691 rhodecode_db_repo = request.db_repo
691 rhodecode_db_repo = request.db_repo
692
692
693 log.debug(
693 log.debug(
694 '%s checking if archived flag for repo for %s',
694 '%s checking if archived flag for repo for %s',
695 self.__class__.__name__, rhodecode_db_repo.repo_name)
695 self.__class__.__name__, rhodecode_db_repo.repo_name)
696
696
697 if rhodecode_db_repo.archived:
697 if rhodecode_db_repo.archived:
698 log.warning('Current view is not supported for archived repo:%s',
698 log.warning('Current view is not supported for archived repo:%s',
699 rhodecode_db_repo.repo_name)
699 rhodecode_db_repo.repo_name)
700
700
701 h.flash(
701 h.flash(
702 h.literal(_('Action not supported for archived repository.')),
702 h.literal(_('Action not supported for archived repository.')),
703 category='warning')
703 category='warning')
704 summary_url = request.route_path(
704 summary_url = request.route_path(
705 'repo_summary', repo_name=rhodecode_db_repo.repo_name)
705 'repo_summary', repo_name=rhodecode_db_repo.repo_name)
706 raise HTTPFound(summary_url)
706 raise HTTPFound(summary_url)
707 return True
707 return True
708
708
709
709
710 class RepoTypeRoutePredicate(object):
710 class RepoTypeRoutePredicate(object):
711 def __init__(self, val, config):
711 def __init__(self, val, config):
712 self.val = val or ['hg', 'git', 'svn']
712 self.val = val or ['hg', 'git', 'svn']
713
713
714 def text(self):
714 def text(self):
715 return f'repo_accepted_type = {self.val}'
715 return f'repo_accepted_type = {self.val}'
716
716
717 phash = text
717 phash = text
718
718
719 def __call__(self, info, request):
719 def __call__(self, info, request):
720 if hasattr(request, 'vcs_call'):
720 if hasattr(request, 'vcs_call'):
721 # skip vcs calls
721 # skip vcs calls
722 return
722 return
723
723
724 rhodecode_db_repo = request.db_repo
724 rhodecode_db_repo = request.db_repo
725
725
726 log.debug(
726 log.debug(
727 '%s checking repo type for %s in %s',
727 '%s checking repo type for %s in %s',
728 self.__class__.__name__, rhodecode_db_repo.repo_type, self.val)
728 self.__class__.__name__, rhodecode_db_repo.repo_type, self.val)
729
729
730 if rhodecode_db_repo.repo_type in self.val:
730 if rhodecode_db_repo.repo_type in self.val:
731 return True
731 return True
732 else:
732 else:
733 log.warning('Current view is not supported for repo type:%s',
733 log.warning('Current view is not supported for repo type:%s',
734 rhodecode_db_repo.repo_type)
734 rhodecode_db_repo.repo_type)
735 return False
735 return False
736
736
737
737
738 class RepoGroupRoutePredicate(object):
738 class RepoGroupRoutePredicate(object):
739 def __init__(self, val, config):
739 def __init__(self, val, config):
740 self.val = val
740 self.val = val
741
741
742 def text(self):
742 def text(self):
743 return f'repo_group_route = {self.val}'
743 return f'repo_group_route = {self.val}'
744
744
745 phash = text
745 phash = text
746
746
747 def __call__(self, info, request):
747 def __call__(self, info, request):
748 if hasattr(request, 'vcs_call'):
748 if hasattr(request, 'vcs_call'):
749 # skip vcs calls
749 # skip vcs calls
750 return
750 return
751
751
752 repo_group_name = info['match']['repo_group_name']
752 repo_group_name = info['match']['repo_group_name']
753
753
754 repo_group_name_parts = repo_group_name.split('/')
754 repo_group_name_parts = repo_group_name.split('/')
755 repo_group_slugs = [x for x in [repo_name_slug(x) for x in repo_group_name_parts]]
755 repo_group_slugs = [x for x in [repo_name_slug(x) for x in repo_group_name_parts]]
756 if repo_group_name_parts != repo_group_slugs:
756 if repo_group_name_parts != repo_group_slugs:
757 # short-skip if the repo-name doesn't follow slug rule
757 # short-skip if the repo-name doesn't follow slug rule
758 log.warning('repo_group_name: %s is different than slug %s', repo_group_name_parts, repo_group_slugs)
758 log.warning('repo_group_name: %s is different than slug %s', repo_group_name_parts, repo_group_slugs)
759 return False
759 return False
760
760
761 repo_group_model = repo_group.RepoGroupModel()
761 repo_group_model = repo_group.RepoGroupModel()
762 by_name_match = repo_group_model.get_by_group_name(repo_group_name, cache=False)
762 by_name_match = repo_group_model.get_by_group_name(repo_group_name, cache=False)
763
763
764 if by_name_match:
764 if by_name_match:
765 # register this as request object we can re-use later
765 # register this as request object we can re-use later
766 request.db_repo_group = by_name_match
766 request.db_repo_group = by_name_match
767 request.db_repo_group_name = request.db_repo_group.group_name
767 request.db_repo_group_name = request.db_repo_group.group_name
768 return True
768 return True
769
769
770 return False
770 return False
771
771
772
772
773 class UserGroupRoutePredicate(object):
773 class UserGroupRoutePredicate(object):
774 def __init__(self, val, config):
774 def __init__(self, val, config):
775 self.val = val
775 self.val = val
776
776
777 def text(self):
777 def text(self):
778 return f'user_group_route = {self.val}'
778 return f'user_group_route = {self.val}'
779
779
780 phash = text
780 phash = text
781
781
782 def __call__(self, info, request):
782 def __call__(self, info, request):
783 if hasattr(request, 'vcs_call'):
783 if hasattr(request, 'vcs_call'):
784 # skip vcs calls
784 # skip vcs calls
785 return
785 return
786
786
787 user_group_id = info['match']['user_group_id']
787 user_group_id = info['match']['user_group_id']
788 user_group_model = user_group.UserGroup()
788 user_group_model = user_group.UserGroup()
789 by_id_match = user_group_model.get(user_group_id, cache=False)
789 by_id_match = user_group_model.get(user_group_id, cache=False)
790
790
791 if by_id_match:
791 if by_id_match:
792 # register this as request object we can re-use later
792 # register this as request object we can re-use later
793 request.db_user_group = by_id_match
793 request.db_user_group = by_id_match
794 return True
794 return True
795
795
796 return False
796 return False
797
797
798
798
799 class UserRoutePredicateBase(object):
799 class UserRoutePredicateBase(object):
800 supports_default = None
800 supports_default = None
801
801
802 def __init__(self, val, config):
802 def __init__(self, val, config):
803 self.val = val
803 self.val = val
804
804
805 def text(self):
805 def text(self):
806 raise NotImplementedError()
806 raise NotImplementedError()
807
807
808 def __call__(self, info, request):
808 def __call__(self, info, request):
809 if hasattr(request, 'vcs_call'):
809 if hasattr(request, 'vcs_call'):
810 # skip vcs calls
810 # skip vcs calls
811 return
811 return
812
812
813 user_id = info['match']['user_id']
813 user_id = info['match']['user_id']
814 user_model = user.User()
814 user_model = user.User()
815 by_id_match = user_model.get(user_id, cache=False)
815 by_id_match = user_model.get(user_id, cache=False)
816
816
817 if by_id_match:
817 if by_id_match:
818 # register this as request object we can re-use later
818 # register this as request object we can re-use later
819 request.db_user = by_id_match
819 request.db_user = by_id_match
820 request.db_user_supports_default = self.supports_default
820 request.db_user_supports_default = self.supports_default
821 return True
821 return True
822
822
823 return False
823 return False
824
824
825
825
826 class UserRoutePredicate(UserRoutePredicateBase):
826 class UserRoutePredicate(UserRoutePredicateBase):
827 supports_default = False
827 supports_default = False
828
828
829 def text(self):
829 def text(self):
830 return f'user_route = {self.val}'
830 return f'user_route = {self.val}'
831
831
832 phash = text
832 phash = text
833
833
834
834
835 class UserRouteWithDefaultPredicate(UserRoutePredicateBase):
835 class UserRouteWithDefaultPredicate(UserRoutePredicateBase):
836 supports_default = True
836 supports_default = True
837
837
838 def text(self):
838 def text(self):
839 return f'user_with_default_route = {self.val}'
839 return f'user_with_default_route = {self.val}'
840
840
841 phash = text
841 phash = text
842
842
843
843
844 def includeme(config):
844 def includeme(config):
845 config.add_route_predicate(
845 config.add_route_predicate(
846 'repo_route', RepoRoutePredicate)
846 'repo_route', RepoRoutePredicate)
847 config.add_route_predicate(
847 config.add_route_predicate(
848 'repo_accepted_types', RepoTypeRoutePredicate)
848 'repo_accepted_types', RepoTypeRoutePredicate)
849 config.add_route_predicate(
849 config.add_route_predicate(
850 'repo_forbid_when_archived', RepoForbidArchivedRoutePredicate)
850 'repo_forbid_when_archived', RepoForbidArchivedRoutePredicate)
851 config.add_route_predicate(
851 config.add_route_predicate(
852 'repo_group_route', RepoGroupRoutePredicate)
852 'repo_group_route', RepoGroupRoutePredicate)
853 config.add_route_predicate(
853 config.add_route_predicate(
854 'user_group_route', UserGroupRoutePredicate)
854 'user_group_route', UserGroupRoutePredicate)
855 config.add_route_predicate(
855 config.add_route_predicate(
856 'user_route_with_default', UserRouteWithDefaultPredicate)
856 'user_route_with_default', UserRouteWithDefaultPredicate)
857 config.add_route_predicate(
857 config.add_route_predicate(
858 'user_route', UserRoutePredicate)
858 'user_route', UserRoutePredicate)
@@ -1,154 +1,152 b''
1
1
2 import dataclasses
2 import dataclasses
3 # Copyright (C) 2016-2023 RhodeCode GmbH
3 # Copyright (C) 2016-2023 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 import logging
22 import logging
23 import collections
23 import collections
24
24
25 from zope.interface import implementer
25 from zope.interface import implementer
26
26
27 from rhodecode.apps._base.interfaces import IAdminNavigationRegistry
27 from rhodecode.apps._base.interfaces import IAdminNavigationRegistry
28 from rhodecode.lib.utils2 import str2bool
28 from rhodecode.lib.utils2 import str2bool
29 from rhodecode.translation import _
29 from rhodecode.translation import _
30
30
31
31
32 log = logging.getLogger(__name__)
32 log = logging.getLogger(__name__)
33
33
34
34
35 @dataclasses.dataclass
35 @dataclasses.dataclass
36 class NavListEntry:
36 class NavListEntry:
37 key: str
37 key: str
38 name: str
38 name: str
39 url: str
39 url: str
40 active_list: list
40 active_list: list
41
41
42
42
43 class NavEntry(object):
43 class NavEntry(object):
44 """
44 """
45 Represents an entry in the admin navigation.
45 Represents an entry in the admin navigation.
46
46
47 :param key: Unique identifier used to store reference in an OrderedDict.
47 :param key: Unique identifier used to store reference in an OrderedDict.
48 :param name: Display name, usually a translation string.
48 :param name: Display name, usually a translation string.
49 :param view_name: Name of the view, used generate the URL.
49 :param view_name: Name of the view, used generate the URL.
50 :param active_list: list of urls that we select active for this element
50 :param active_list: list of urls that we select active for this element
51 """
51 """
52
52
53 def __init__(self, key, name, view_name, active_list=None):
53 def __init__(self, key, name, view_name, active_list=None):
54 self.key = key
54 self.key = key
55 self.name = name
55 self.name = name
56 self.view_name = view_name
56 self.view_name = view_name
57 self._active_list = active_list or []
57 self._active_list = active_list or []
58
58
59 def generate_url(self, request):
59 def generate_url(self, request):
60 return request.route_path(self.view_name)
60 return request.route_path(self.view_name)
61
61
62 def get_localized_name(self, request):
62 def get_localized_name(self, request):
63 return request.translate(self.name)
63 return request.translate(self.name)
64
64
65 @property
65 @property
66 def active_list(self):
66 def active_list(self):
67 active_list = [self.key]
67 active_list = [self.key]
68 if self._active_list:
68 if self._active_list:
69 active_list = self._active_list
69 active_list = self._active_list
70 return active_list
70 return active_list
71
71
72
72
73 @implementer(IAdminNavigationRegistry)
73 @implementer(IAdminNavigationRegistry)
74 class NavigationRegistry(object):
74 class NavigationRegistry(object):
75
75
76 _base_entries = [
76 _base_entries = [
77 NavEntry('global', _('Global'),
77 NavEntry('global', _('Global'),
78 'admin_settings_global'),
78 'admin_settings_global'),
79 NavEntry('vcs', _('VCS'),
79 NavEntry('vcs', _('VCS'),
80 'admin_settings_vcs'),
80 'admin_settings_vcs'),
81 NavEntry('visual', _('Visual'),
81 NavEntry('visual', _('Visual'),
82 'admin_settings_visual'),
82 'admin_settings_visual'),
83 NavEntry('mapping', _('Remap and Rescan'),
83 NavEntry('mapping', _('Remap and Rescan'),
84 'admin_settings_mapping'),
84 'admin_settings_mapping'),
85 NavEntry('issuetracker', _('Issue Tracker'),
85 NavEntry('issuetracker', _('Issue Tracker'),
86 'admin_settings_issuetracker'),
86 'admin_settings_issuetracker'),
87 NavEntry('email', _('Email'),
87 NavEntry('email', _('Email'),
88 'admin_settings_email'),
88 'admin_settings_email'),
89 NavEntry('hooks', _('Hooks'),
89 NavEntry('hooks', _('Hooks'),
90 'admin_settings_hooks'),
90 'admin_settings_hooks'),
91 NavEntry('search', _('Full Text Search'),
91 NavEntry('search', _('Full Text Search'),
92 'admin_settings_search'),
92 'admin_settings_search'),
93 NavEntry('system', _('System Info'),
93 NavEntry('system', _('System Info'),
94 'admin_settings_system'),
94 'admin_settings_system'),
95 NavEntry('exceptions', _('Exceptions Tracker'),
95 NavEntry('exceptions', _('Exceptions Tracker'),
96 'admin_settings_exception_tracker',
96 'admin_settings_exception_tracker',
97 active_list=['exceptions', 'exceptions_browse']),
97 active_list=['exceptions', 'exceptions_browse']),
98 NavEntry('process_management', _('Processes'),
98 NavEntry('process_management', _('Processes'),
99 'admin_settings_process_management'),
99 'admin_settings_process_management'),
100 NavEntry('sessions', _('User Sessions'),
100 NavEntry('sessions', _('User Sessions'),
101 'admin_settings_sessions'),
101 'admin_settings_sessions'),
102 NavEntry('open_source', _('Open Source Licenses'),
102 NavEntry('open_source', _('Open Source Licenses'),
103 'admin_settings_open_source'),
103 'admin_settings_open_source'),
104 NavEntry('automation', _('Automation'),
105 'admin_settings_automation')
106 ]
104 ]
107
105
108 _labs_entry = NavEntry('labs', _('Labs'),
106 _labs_entry = NavEntry('labs', _('Labs'),
109 'admin_settings_labs')
107 'admin_settings_labs')
110
108
111 def __init__(self, labs_active=False):
109 def __init__(self, labs_active=False):
112 self._registered_entries = collections.OrderedDict()
110 self._registered_entries = collections.OrderedDict()
113 for item in self.__class__._base_entries:
111 for item in self.__class__._base_entries:
114 self.add_entry(item)
112 self.add_entry(item)
115
113
116 if labs_active:
114 if labs_active:
117 self.add_entry(self._labs_entry)
115 self.add_entry(self._labs_entry)
118
116
119 def add_entry(self, entry):
117 def add_entry(self, entry):
120 self._registered_entries[entry.key] = entry
118 self._registered_entries[entry.key] = entry
121
119
122 def get_navlist(self, request):
120 def get_navlist(self, request):
123 nav_list = [
121 nav_list = [
124 NavListEntry(i.key, i.get_localized_name(request),
122 NavListEntry(i.key, i.get_localized_name(request),
125 i.generate_url(request), i.active_list)
123 i.generate_url(request), i.active_list)
126 for i in self._registered_entries.values()
124 for i in self._registered_entries.values()
127 ]
125 ]
128 return nav_list
126 return nav_list
129
127
130
128
131 def navigation_registry(request, registry=None):
129 def navigation_registry(request, registry=None):
132 """
130 """
133 Helper that returns the admin navigation registry.
131 Helper that returns the admin navigation registry.
134 """
132 """
135 pyramid_registry = registry or request.registry
133 pyramid_registry = registry or request.registry
136 nav_registry = pyramid_registry.queryUtility(IAdminNavigationRegistry)
134 nav_registry = pyramid_registry.queryUtility(IAdminNavigationRegistry)
137 return nav_registry
135 return nav_registry
138
136
139
137
140 def navigation_list(request):
138 def navigation_list(request):
141 """
139 """
142 Helper that returns the admin navigation as list of NavListEntry objects.
140 Helper that returns the admin navigation as list of NavListEntry objects.
143 """
141 """
144 return navigation_registry(request).get_navlist(request)
142 return navigation_registry(request).get_navlist(request)
145
143
146
144
147 def includeme(config):
145 def includeme(config):
148 # Create admin navigation registry and add it to the pyramid registry.
146 # Create admin navigation registry and add it to the pyramid registry.
149 settings = config.get_settings()
147 settings = config.get_settings()
150 labs_active = str2bool(settings.get('labs_settings_active', False))
148 labs_active = str2bool(settings.get('labs_settings_active', False))
151 navigation_registry_instance = NavigationRegistry(labs_active=labs_active)
149 navigation_registry_instance = NavigationRegistry(labs_active=labs_active)
152 config.registry.registerUtility(navigation_registry_instance)
150 config.registry.registerUtility(navigation_registry_instance)
153 log.debug('Created new navigation instance, %s', navigation_registry_instance)
151 log.debug('Created new navigation instance, %s', navigation_registry_instance)
154
152
@@ -1,1082 +1,1093 b''
1 # Copyright (C) 2016-2023 RhodeCode GmbH
1 # Copyright (C) 2016-2023 RhodeCode GmbH
2 #
2 #
3 # This program is free software: you can redistribute it and/or modify
3 # This program is free software: you can redistribute it and/or modify
4 # it under the terms of the GNU Affero General Public License, version 3
4 # it under the terms of the GNU Affero General Public License, version 3
5 # (only), as published by the Free Software Foundation.
5 # (only), as published by the Free Software Foundation.
6 #
6 #
7 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # GNU General Public License for more details.
10 # GNU General Public License for more details.
11 #
11 #
12 # You should have received a copy of the GNU Affero General Public License
12 # You should have received a copy of the GNU Affero General Public License
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 #
14 #
15 # This program is dual-licensed. If you wish to learn more about the
15 # This program is dual-licensed. If you wish to learn more about the
16 # RhodeCode Enterprise Edition, including its added features, Support services,
16 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
18
18
19
19
20 from rhodecode.apps._base import ADMIN_PREFIX
20 from rhodecode.apps._base import ADMIN_PREFIX
21 from rhodecode.apps._base.navigation import includeme as nav_includeme
22 from rhodecode.apps.admin.views.main_views import AdminMainView
21
23
22
24
23 def admin_routes(config):
25 def admin_routes(config):
24 """
26 """
25 Admin prefixed routes
27 Admin prefixed routes
26 """
28 """
27 from rhodecode.apps.admin.views.audit_logs import AdminAuditLogsView
29 from rhodecode.apps.admin.views.audit_logs import AdminAuditLogsView
28 from rhodecode.apps.admin.views.artifacts import AdminArtifactsView
30 from rhodecode.apps.admin.views.artifacts import AdminArtifactsView
31 from rhodecode.apps.admin.views.automation import AdminAutomationView
32 from rhodecode.apps.admin.views.scheduler import AdminSchedulerView
29 from rhodecode.apps.admin.views.defaults import AdminDefaultSettingsView
33 from rhodecode.apps.admin.views.defaults import AdminDefaultSettingsView
30 from rhodecode.apps.admin.views.exception_tracker import ExceptionsTrackerView
34 from rhodecode.apps.admin.views.exception_tracker import ExceptionsTrackerView
31 from rhodecode.apps.admin.views.main_views import AdminMainView
32 from rhodecode.apps.admin.views.open_source_licenses import OpenSourceLicensesAdminSettingsView
35 from rhodecode.apps.admin.views.open_source_licenses import OpenSourceLicensesAdminSettingsView
33 from rhodecode.apps.admin.views.permissions import AdminPermissionsView
36 from rhodecode.apps.admin.views.permissions import AdminPermissionsView
34 from rhodecode.apps.admin.views.process_management import AdminProcessManagementView
37 from rhodecode.apps.admin.views.process_management import AdminProcessManagementView
35 from rhodecode.apps.admin.views.repo_groups import AdminRepoGroupsView
38 from rhodecode.apps.admin.views.repo_groups import AdminRepoGroupsView
36 from rhodecode.apps.admin.views.repositories import AdminReposView
39 from rhodecode.apps.admin.views.repositories import AdminReposView
37 from rhodecode.apps.admin.views.sessions import AdminSessionSettingsView
40 from rhodecode.apps.admin.views.sessions import AdminSessionSettingsView
38 from rhodecode.apps.admin.views.settings import AdminSettingsView
41 from rhodecode.apps.admin.views.settings import AdminSettingsView
39 from rhodecode.apps.admin.views.svn_config import AdminSvnConfigView
42 from rhodecode.apps.admin.views.svn_config import AdminSvnConfigView
40 from rhodecode.apps.admin.views.system_info import AdminSystemInfoSettingsView
43 from rhodecode.apps.admin.views.system_info import AdminSystemInfoSettingsView
41 from rhodecode.apps.admin.views.user_groups import AdminUserGroupsView
44 from rhodecode.apps.admin.views.user_groups import AdminUserGroupsView
42 from rhodecode.apps.admin.views.users import AdminUsersView, UsersView
45 from rhodecode.apps.admin.views.users import AdminUsersView, UsersView
43
46
44 config.add_route(
47 config.add_route(
45 name='admin_audit_logs',
48 name='admin_audit_logs',
46 pattern='/audit_logs')
49 pattern='/audit_logs')
47 config.add_view(
50 config.add_view(
48 AdminAuditLogsView,
51 AdminAuditLogsView,
49 attr='admin_audit_logs',
52 attr='admin_audit_logs',
50 route_name='admin_audit_logs', request_method='GET',
53 route_name='admin_audit_logs', request_method='GET',
51 renderer='rhodecode:templates/admin/admin_audit_logs.mako')
54 renderer='rhodecode:templates/admin/admin_audit_logs.mako')
52
55
53 config.add_route(
56 config.add_route(
54 name='admin_audit_log_entry',
57 name='admin_audit_log_entry',
55 pattern='/audit_logs/{audit_log_id}')
58 pattern='/audit_logs/{audit_log_id}')
56 config.add_view(
59 config.add_view(
57 AdminAuditLogsView,
60 AdminAuditLogsView,
58 attr='admin_audit_log_entry',
61 attr='admin_audit_log_entry',
59 route_name='admin_audit_log_entry', request_method='GET',
62 route_name='admin_audit_log_entry', request_method='GET',
60 renderer='rhodecode:templates/admin/admin_audit_log_entry.mako')
63 renderer='rhodecode:templates/admin/admin_audit_log_entry.mako')
61
64
62 # Artifacts EE feature
65 # Artifacts EE feature
63 config.add_route(
66 config.add_route(
64 'admin_artifacts',
67 'admin_artifacts',
65 pattern=ADMIN_PREFIX + '/artifacts')
68 pattern=ADMIN_PREFIX + '/artifacts')
66 config.add_route(
69 config.add_route(
67 'admin_artifacts_show_all',
70 'admin_artifacts_show_all',
68 pattern=ADMIN_PREFIX + '/artifacts')
71 pattern=ADMIN_PREFIX + '/artifacts')
69 config.add_view(
72 config.add_view(
70 AdminArtifactsView,
73 AdminArtifactsView,
71 attr='artifacts',
74 attr='artifacts',
72 route_name='admin_artifacts', request_method='GET',
75 route_name='admin_artifacts', request_method='GET',
73 renderer='rhodecode:templates/admin/artifacts/artifacts.mako')
76 renderer='rhodecode:templates/admin/artifacts/artifacts.mako')
74 config.add_view(
77 config.add_view(
75 AdminArtifactsView,
78 AdminArtifactsView,
76 attr='artifacts',
79 attr='artifacts',
77 route_name='admin_artifacts_show_all', request_method='GET',
80 route_name='admin_artifacts_show_all', request_method='GET',
78 renderer='rhodecode:templates/admin/artifacts/artifacts.mako')
81 renderer='rhodecode:templates/admin/artifacts/artifacts.mako')
82
79 # EE views
83 # EE views
80 config.add_route(
84 config.add_route(
81 name='admin_artifacts_show_info',
85 name='admin_artifacts_show_info',
82 pattern=ADMIN_PREFIX + '/artifacts/{uid}')
86 pattern=ADMIN_PREFIX + '/artifacts/{uid}')
83 config.add_route(
87 config.add_route(
84 name='admin_artifacts_delete',
88 name='admin_artifacts_delete',
85 pattern=ADMIN_PREFIX + '/artifacts/{uid}/delete')
89 pattern=ADMIN_PREFIX + '/artifacts/{uid}/delete')
86 config.add_route(
90 config.add_route(
87 name='admin_artifacts_update',
91 name='admin_artifacts_update',
88 pattern=ADMIN_PREFIX + '/artifacts/{uid}/update')
92 pattern=ADMIN_PREFIX + '/artifacts/{uid}/update')
89
93
94 # Automation EE feature
95 config.add_route(
96 'admin_automation',
97 pattern=ADMIN_PREFIX + '/automation')
98 config.add_view(
99 AdminAutomationView,
100 attr='automation',
101 route_name='admin_automation', request_method='GET',
102 renderer='rhodecode:templates/admin/automation/automation.mako')
103
104 # Scheduler EE feature
105 config.add_route(
106 'admin_scheduler',
107 pattern=ADMIN_PREFIX + '/scheduler')
108 config.add_view(
109 AdminSchedulerView,
110 attr='scheduler',
111 route_name='admin_scheduler', request_method='GET',
112 renderer='rhodecode:templates/admin/scheduler/scheduler.mako')
113
90 config.add_route(
114 config.add_route(
91 name='admin_settings_open_source',
115 name='admin_settings_open_source',
92 pattern='/settings/open_source')
116 pattern='/settings/open_source')
93 config.add_view(
117 config.add_view(
94 OpenSourceLicensesAdminSettingsView,
118 OpenSourceLicensesAdminSettingsView,
95 attr='open_source_licenses',
119 attr='open_source_licenses',
96 route_name='admin_settings_open_source', request_method='GET',
120 route_name='admin_settings_open_source', request_method='GET',
97 renderer='rhodecode:templates/admin/settings/settings.mako')
121 renderer='rhodecode:templates/admin/settings/settings.mako')
98
122
99 config.add_route(
123 config.add_route(
100 name='admin_settings_vcs_svn_generate_cfg',
124 name='admin_settings_vcs_svn_generate_cfg',
101 pattern='/settings/vcs/svn_generate_cfg')
125 pattern='/settings/vcs/svn_generate_cfg')
102 config.add_view(
126 config.add_view(
103 AdminSvnConfigView,
127 AdminSvnConfigView,
104 attr='vcs_svn_generate_config',
128 attr='vcs_svn_generate_config',
105 route_name='admin_settings_vcs_svn_generate_cfg',
129 route_name='admin_settings_vcs_svn_generate_cfg',
106 request_method='POST', renderer='json')
130 request_method='POST', renderer='json')
107
131
108 config.add_route(
132 config.add_route(
109 name='admin_settings_system',
133 name='admin_settings_system',
110 pattern='/settings/system')
134 pattern='/settings/system')
111 config.add_view(
135 config.add_view(
112 AdminSystemInfoSettingsView,
136 AdminSystemInfoSettingsView,
113 attr='settings_system_info',
137 attr='settings_system_info',
114 route_name='admin_settings_system', request_method='GET',
138 route_name='admin_settings_system', request_method='GET',
115 renderer='rhodecode:templates/admin/settings/settings.mako')
139 renderer='rhodecode:templates/admin/settings/settings.mako')
116
140
117 config.add_route(
141 config.add_route(
118 name='admin_settings_system_update',
142 name='admin_settings_system_update',
119 pattern='/settings/system/updates')
143 pattern='/settings/system/updates')
120 config.add_view(
144 config.add_view(
121 AdminSystemInfoSettingsView,
145 AdminSystemInfoSettingsView,
122 attr='settings_system_info_check_update',
146 attr='settings_system_info_check_update',
123 route_name='admin_settings_system_update', request_method='GET',
147 route_name='admin_settings_system_update', request_method='GET',
124 renderer='rhodecode:templates/admin/settings/settings_system_update.mako')
148 renderer='rhodecode:templates/admin/settings/settings_system_update.mako')
125
149
126 config.add_route(
150 config.add_route(
127 name='admin_settings_exception_tracker',
151 name='admin_settings_exception_tracker',
128 pattern='/settings/exceptions')
152 pattern='/settings/exceptions')
129 config.add_view(
153 config.add_view(
130 ExceptionsTrackerView,
154 ExceptionsTrackerView,
131 attr='browse_exceptions',
155 attr='browse_exceptions',
132 route_name='admin_settings_exception_tracker', request_method='GET',
156 route_name='admin_settings_exception_tracker', request_method='GET',
133 renderer='rhodecode:templates/admin/settings/settings.mako')
157 renderer='rhodecode:templates/admin/settings/settings.mako')
134
158
135 config.add_route(
159 config.add_route(
136 name='admin_settings_exception_tracker_delete_all',
160 name='admin_settings_exception_tracker_delete_all',
137 pattern='/settings/exceptions_delete_all')
161 pattern='/settings/exceptions_delete_all')
138 config.add_view(
162 config.add_view(
139 ExceptionsTrackerView,
163 ExceptionsTrackerView,
140 attr='exception_delete_all',
164 attr='exception_delete_all',
141 route_name='admin_settings_exception_tracker_delete_all', request_method='POST',
165 route_name='admin_settings_exception_tracker_delete_all', request_method='POST',
142 renderer='rhodecode:templates/admin/settings/settings.mako')
166 renderer='rhodecode:templates/admin/settings/settings.mako')
143
167
144 config.add_route(
168 config.add_route(
145 name='admin_settings_exception_tracker_show',
169 name='admin_settings_exception_tracker_show',
146 pattern='/settings/exceptions/{exception_id}')
170 pattern='/settings/exceptions/{exception_id}')
147 config.add_view(
171 config.add_view(
148 ExceptionsTrackerView,
172 ExceptionsTrackerView,
149 attr='exception_show',
173 attr='exception_show',
150 route_name='admin_settings_exception_tracker_show', request_method='GET',
174 route_name='admin_settings_exception_tracker_show', request_method='GET',
151 renderer='rhodecode:templates/admin/settings/settings.mako')
175 renderer='rhodecode:templates/admin/settings/settings.mako')
152
176
153 config.add_route(
177 config.add_route(
154 name='admin_settings_exception_tracker_delete',
178 name='admin_settings_exception_tracker_delete',
155 pattern='/settings/exceptions/{exception_id}/delete')
179 pattern='/settings/exceptions/{exception_id}/delete')
156 config.add_view(
180 config.add_view(
157 ExceptionsTrackerView,
181 ExceptionsTrackerView,
158 attr='exception_delete',
182 attr='exception_delete',
159 route_name='admin_settings_exception_tracker_delete', request_method='POST',
183 route_name='admin_settings_exception_tracker_delete', request_method='POST',
160 renderer='rhodecode:templates/admin/settings/settings.mako')
184 renderer='rhodecode:templates/admin/settings/settings.mako')
161
185
162 config.add_route(
186 config.add_route(
163 name='admin_settings_sessions',
187 name='admin_settings_sessions',
164 pattern='/settings/sessions')
188 pattern='/settings/sessions')
165 config.add_view(
189 config.add_view(
166 AdminSessionSettingsView,
190 AdminSessionSettingsView,
167 attr='settings_sessions',
191 attr='settings_sessions',
168 route_name='admin_settings_sessions', request_method='GET',
192 route_name='admin_settings_sessions', request_method='GET',
169 renderer='rhodecode:templates/admin/settings/settings.mako')
193 renderer='rhodecode:templates/admin/settings/settings.mako')
170
194
171 config.add_route(
195 config.add_route(
172 name='admin_settings_sessions_cleanup',
196 name='admin_settings_sessions_cleanup',
173 pattern='/settings/sessions/cleanup')
197 pattern='/settings/sessions/cleanup')
174 config.add_view(
198 config.add_view(
175 AdminSessionSettingsView,
199 AdminSessionSettingsView,
176 attr='settings_sessions_cleanup',
200 attr='settings_sessions_cleanup',
177 route_name='admin_settings_sessions_cleanup', request_method='POST')
201 route_name='admin_settings_sessions_cleanup', request_method='POST')
178
202
179 config.add_route(
203 config.add_route(
180 name='admin_settings_process_management',
204 name='admin_settings_process_management',
181 pattern='/settings/process_management')
205 pattern='/settings/process_management')
182 config.add_view(
206 config.add_view(
183 AdminProcessManagementView,
207 AdminProcessManagementView,
184 attr='process_management',
208 attr='process_management',
185 route_name='admin_settings_process_management', request_method='GET',
209 route_name='admin_settings_process_management', request_method='GET',
186 renderer='rhodecode:templates/admin/settings/settings.mako')
210 renderer='rhodecode:templates/admin/settings/settings.mako')
187
211
188 config.add_route(
212 config.add_route(
189 name='admin_settings_process_management_data',
213 name='admin_settings_process_management_data',
190 pattern='/settings/process_management/data')
214 pattern='/settings/process_management/data')
191 config.add_view(
215 config.add_view(
192 AdminProcessManagementView,
216 AdminProcessManagementView,
193 attr='process_management_data',
217 attr='process_management_data',
194 route_name='admin_settings_process_management_data', request_method='GET',
218 route_name='admin_settings_process_management_data', request_method='GET',
195 renderer='rhodecode:templates/admin/settings/settings_process_management_data.mako')
219 renderer='rhodecode:templates/admin/settings/settings_process_management_data.mako')
196
220
197 config.add_route(
221 config.add_route(
198 name='admin_settings_process_management_signal',
222 name='admin_settings_process_management_signal',
199 pattern='/settings/process_management/signal')
223 pattern='/settings/process_management/signal')
200 config.add_view(
224 config.add_view(
201 AdminProcessManagementView,
225 AdminProcessManagementView,
202 attr='process_management_signal',
226 attr='process_management_signal',
203 route_name='admin_settings_process_management_signal',
227 route_name='admin_settings_process_management_signal',
204 request_method='POST', renderer='json_ext')
228 request_method='POST', renderer='json_ext')
205
229
206 config.add_route(
230 config.add_route(
207 name='admin_settings_process_management_master_signal',
231 name='admin_settings_process_management_master_signal',
208 pattern='/settings/process_management/master_signal')
232 pattern='/settings/process_management/master_signal')
209 config.add_view(
233 config.add_view(
210 AdminProcessManagementView,
234 AdminProcessManagementView,
211 attr='process_management_master_signal',
235 attr='process_management_master_signal',
212 route_name='admin_settings_process_management_master_signal',
236 route_name='admin_settings_process_management_master_signal',
213 request_method='POST', renderer='json_ext')
237 request_method='POST', renderer='json_ext')
214
238
215 # default settings
239 # default settings
216 config.add_route(
240 config.add_route(
217 name='admin_defaults_repositories',
241 name='admin_defaults_repositories',
218 pattern='/defaults/repositories')
242 pattern='/defaults/repositories')
219 config.add_view(
243 config.add_view(
220 AdminDefaultSettingsView,
244 AdminDefaultSettingsView,
221 attr='defaults_repository_show',
245 attr='defaults_repository_show',
222 route_name='admin_defaults_repositories', request_method='GET',
246 route_name='admin_defaults_repositories', request_method='GET',
223 renderer='rhodecode:templates/admin/defaults/defaults.mako')
247 renderer='rhodecode:templates/admin/defaults/defaults.mako')
224
248
225 config.add_route(
249 config.add_route(
226 name='admin_defaults_repositories_update',
250 name='admin_defaults_repositories_update',
227 pattern='/defaults/repositories/update')
251 pattern='/defaults/repositories/update')
228 config.add_view(
252 config.add_view(
229 AdminDefaultSettingsView,
253 AdminDefaultSettingsView,
230 attr='defaults_repository_update',
254 attr='defaults_repository_update',
231 route_name='admin_defaults_repositories_update', request_method='POST',
255 route_name='admin_defaults_repositories_update', request_method='POST',
232 renderer='rhodecode:templates/admin/defaults/defaults.mako')
256 renderer='rhodecode:templates/admin/defaults/defaults.mako')
233
257
234 # admin settings
258 # admin settings
235
259
236 config.add_route(
260 config.add_route(
237 name='admin_settings',
261 name='admin_settings',
238 pattern='/settings')
262 pattern='/settings')
239 config.add_view(
263 config.add_view(
240 AdminSettingsView,
264 AdminSettingsView,
241 attr='settings_global',
265 attr='settings_global',
242 route_name='admin_settings', request_method='GET',
266 route_name='admin_settings', request_method='GET',
243 renderer='rhodecode:templates/admin/settings/settings.mako')
267 renderer='rhodecode:templates/admin/settings/settings.mako')
244
268
245 config.add_route(
269 config.add_route(
246 name='admin_settings_update',
270 name='admin_settings_update',
247 pattern='/settings/update')
271 pattern='/settings/update')
248 config.add_view(
272 config.add_view(
249 AdminSettingsView,
273 AdminSettingsView,
250 attr='settings_global_update',
274 attr='settings_global_update',
251 route_name='admin_settings_update', request_method='POST',
275 route_name='admin_settings_update', request_method='POST',
252 renderer='rhodecode:templates/admin/settings/settings.mako')
276 renderer='rhodecode:templates/admin/settings/settings.mako')
253
277
254 config.add_route(
278 config.add_route(
255 name='admin_settings_global',
279 name='admin_settings_global',
256 pattern='/settings/global')
280 pattern='/settings/global')
257 config.add_view(
281 config.add_view(
258 AdminSettingsView,
282 AdminSettingsView,
259 attr='settings_global',
283 attr='settings_global',
260 route_name='admin_settings_global', request_method='GET',
284 route_name='admin_settings_global', request_method='GET',
261 renderer='rhodecode:templates/admin/settings/settings.mako')
285 renderer='rhodecode:templates/admin/settings/settings.mako')
262
286
263 config.add_route(
287 config.add_route(
264 name='admin_settings_global_update',
288 name='admin_settings_global_update',
265 pattern='/settings/global/update')
289 pattern='/settings/global/update')
266 config.add_view(
290 config.add_view(
267 AdminSettingsView,
291 AdminSettingsView,
268 attr='settings_global_update',
292 attr='settings_global_update',
269 route_name='admin_settings_global_update', request_method='POST',
293 route_name='admin_settings_global_update', request_method='POST',
270 renderer='rhodecode:templates/admin/settings/settings.mako')
294 renderer='rhodecode:templates/admin/settings/settings.mako')
271
295
272 config.add_route(
296 config.add_route(
273 name='admin_settings_vcs',
297 name='admin_settings_vcs',
274 pattern='/settings/vcs')
298 pattern='/settings/vcs')
275 config.add_view(
299 config.add_view(
276 AdminSettingsView,
300 AdminSettingsView,
277 attr='settings_vcs',
301 attr='settings_vcs',
278 route_name='admin_settings_vcs', request_method='GET',
302 route_name='admin_settings_vcs', request_method='GET',
279 renderer='rhodecode:templates/admin/settings/settings.mako')
303 renderer='rhodecode:templates/admin/settings/settings.mako')
280
304
281 config.add_route(
305 config.add_route(
282 name='admin_settings_vcs_update',
306 name='admin_settings_vcs_update',
283 pattern='/settings/vcs/update')
307 pattern='/settings/vcs/update')
284 config.add_view(
308 config.add_view(
285 AdminSettingsView,
309 AdminSettingsView,
286 attr='settings_vcs_update',
310 attr='settings_vcs_update',
287 route_name='admin_settings_vcs_update', request_method='POST',
311 route_name='admin_settings_vcs_update', request_method='POST',
288 renderer='rhodecode:templates/admin/settings/settings.mako')
312 renderer='rhodecode:templates/admin/settings/settings.mako')
289
313
290 config.add_route(
314 config.add_route(
291 name='admin_settings_vcs_svn_pattern_delete',
315 name='admin_settings_vcs_svn_pattern_delete',
292 pattern='/settings/vcs/svn_pattern_delete')
316 pattern='/settings/vcs/svn_pattern_delete')
293 config.add_view(
317 config.add_view(
294 AdminSettingsView,
318 AdminSettingsView,
295 attr='settings_vcs_delete_svn_pattern',
319 attr='settings_vcs_delete_svn_pattern',
296 route_name='admin_settings_vcs_svn_pattern_delete', request_method='POST',
320 route_name='admin_settings_vcs_svn_pattern_delete', request_method='POST',
297 renderer='json_ext', xhr=True)
321 renderer='json_ext', xhr=True)
298
322
299 config.add_route(
323 config.add_route(
300 name='admin_settings_mapping',
324 name='admin_settings_mapping',
301 pattern='/settings/mapping')
325 pattern='/settings/mapping')
302 config.add_view(
326 config.add_view(
303 AdminSettingsView,
327 AdminSettingsView,
304 attr='settings_mapping',
328 attr='settings_mapping',
305 route_name='admin_settings_mapping', request_method='GET',
329 route_name='admin_settings_mapping', request_method='GET',
306 renderer='rhodecode:templates/admin/settings/settings.mako')
330 renderer='rhodecode:templates/admin/settings/settings.mako')
307
331
308 config.add_route(
332 config.add_route(
309 name='admin_settings_mapping_update',
333 name='admin_settings_mapping_update',
310 pattern='/settings/mapping/update')
334 pattern='/settings/mapping/update')
311 config.add_view(
335 config.add_view(
312 AdminSettingsView,
336 AdminSettingsView,
313 attr='settings_mapping_update',
337 attr='settings_mapping_update',
314 route_name='admin_settings_mapping_update', request_method='POST',
338 route_name='admin_settings_mapping_update', request_method='POST',
315 renderer='rhodecode:templates/admin/settings/settings.mako')
339 renderer='rhodecode:templates/admin/settings/settings.mako')
316
340
317 config.add_route(
341 config.add_route(
318 name='admin_settings_visual',
342 name='admin_settings_visual',
319 pattern='/settings/visual')
343 pattern='/settings/visual')
320 config.add_view(
344 config.add_view(
321 AdminSettingsView,
345 AdminSettingsView,
322 attr='settings_visual',
346 attr='settings_visual',
323 route_name='admin_settings_visual', request_method='GET',
347 route_name='admin_settings_visual', request_method='GET',
324 renderer='rhodecode:templates/admin/settings/settings.mako')
348 renderer='rhodecode:templates/admin/settings/settings.mako')
325
349
326 config.add_route(
350 config.add_route(
327 name='admin_settings_visual_update',
351 name='admin_settings_visual_update',
328 pattern='/settings/visual/update')
352 pattern='/settings/visual/update')
329 config.add_view(
353 config.add_view(
330 AdminSettingsView,
354 AdminSettingsView,
331 attr='settings_visual_update',
355 attr='settings_visual_update',
332 route_name='admin_settings_visual_update', request_method='POST',
356 route_name='admin_settings_visual_update', request_method='POST',
333 renderer='rhodecode:templates/admin/settings/settings.mako')
357 renderer='rhodecode:templates/admin/settings/settings.mako')
334
358
335 config.add_route(
359 config.add_route(
336 name='admin_settings_issuetracker',
360 name='admin_settings_issuetracker',
337 pattern='/settings/issue-tracker')
361 pattern='/settings/issue-tracker')
338 config.add_view(
362 config.add_view(
339 AdminSettingsView,
363 AdminSettingsView,
340 attr='settings_issuetracker',
364 attr='settings_issuetracker',
341 route_name='admin_settings_issuetracker', request_method='GET',
365 route_name='admin_settings_issuetracker', request_method='GET',
342 renderer='rhodecode:templates/admin/settings/settings.mako')
366 renderer='rhodecode:templates/admin/settings/settings.mako')
343
367
344 config.add_route(
368 config.add_route(
345 name='admin_settings_issuetracker_update',
369 name='admin_settings_issuetracker_update',
346 pattern='/settings/issue-tracker/update')
370 pattern='/settings/issue-tracker/update')
347 config.add_view(
371 config.add_view(
348 AdminSettingsView,
372 AdminSettingsView,
349 attr='settings_issuetracker_update',
373 attr='settings_issuetracker_update',
350 route_name='admin_settings_issuetracker_update', request_method='POST',
374 route_name='admin_settings_issuetracker_update', request_method='POST',
351 renderer='rhodecode:templates/admin/settings/settings.mako')
375 renderer='rhodecode:templates/admin/settings/settings.mako')
352
376
353 config.add_route(
377 config.add_route(
354 name='admin_settings_issuetracker_test',
378 name='admin_settings_issuetracker_test',
355 pattern='/settings/issue-tracker/test')
379 pattern='/settings/issue-tracker/test')
356 config.add_view(
380 config.add_view(
357 AdminSettingsView,
381 AdminSettingsView,
358 attr='settings_issuetracker_test',
382 attr='settings_issuetracker_test',
359 route_name='admin_settings_issuetracker_test', request_method='POST',
383 route_name='admin_settings_issuetracker_test', request_method='POST',
360 renderer='string', xhr=True)
384 renderer='string', xhr=True)
361
385
362 config.add_route(
386 config.add_route(
363 name='admin_settings_issuetracker_delete',
387 name='admin_settings_issuetracker_delete',
364 pattern='/settings/issue-tracker/delete')
388 pattern='/settings/issue-tracker/delete')
365 config.add_view(
389 config.add_view(
366 AdminSettingsView,
390 AdminSettingsView,
367 attr='settings_issuetracker_delete',
391 attr='settings_issuetracker_delete',
368 route_name='admin_settings_issuetracker_delete', request_method='POST',
392 route_name='admin_settings_issuetracker_delete', request_method='POST',
369 renderer='json_ext', xhr=True)
393 renderer='json_ext', xhr=True)
370
394
371 config.add_route(
395 config.add_route(
372 name='admin_settings_email',
396 name='admin_settings_email',
373 pattern='/settings/email')
397 pattern='/settings/email')
374 config.add_view(
398 config.add_view(
375 AdminSettingsView,
399 AdminSettingsView,
376 attr='settings_email',
400 attr='settings_email',
377 route_name='admin_settings_email', request_method='GET',
401 route_name='admin_settings_email', request_method='GET',
378 renderer='rhodecode:templates/admin/settings/settings.mako')
402 renderer='rhodecode:templates/admin/settings/settings.mako')
379
403
380 config.add_route(
404 config.add_route(
381 name='admin_settings_email_update',
405 name='admin_settings_email_update',
382 pattern='/settings/email/update')
406 pattern='/settings/email/update')
383 config.add_view(
407 config.add_view(
384 AdminSettingsView,
408 AdminSettingsView,
385 attr='settings_email_update',
409 attr='settings_email_update',
386 route_name='admin_settings_email_update', request_method='POST',
410 route_name='admin_settings_email_update', request_method='POST',
387 renderer='rhodecode:templates/admin/settings/settings.mako')
411 renderer='rhodecode:templates/admin/settings/settings.mako')
388
412
389 config.add_route(
413 config.add_route(
390 name='admin_settings_hooks',
414 name='admin_settings_hooks',
391 pattern='/settings/hooks')
415 pattern='/settings/hooks')
392 config.add_view(
416 config.add_view(
393 AdminSettingsView,
417 AdminSettingsView,
394 attr='settings_hooks',
418 attr='settings_hooks',
395 route_name='admin_settings_hooks', request_method='GET',
419 route_name='admin_settings_hooks', request_method='GET',
396 renderer='rhodecode:templates/admin/settings/settings.mako')
420 renderer='rhodecode:templates/admin/settings/settings.mako')
397
421
398 config.add_route(
422 config.add_route(
399 name='admin_settings_hooks_update',
423 name='admin_settings_hooks_update',
400 pattern='/settings/hooks/update')
424 pattern='/settings/hooks/update')
401 config.add_view(
425 config.add_view(
402 AdminSettingsView,
426 AdminSettingsView,
403 attr='settings_hooks_update',
427 attr='settings_hooks_update',
404 route_name='admin_settings_hooks_update', request_method='POST',
428 route_name='admin_settings_hooks_update', request_method='POST',
405 renderer='rhodecode:templates/admin/settings/settings.mako')
429 renderer='rhodecode:templates/admin/settings/settings.mako')
406
430
407 config.add_route(
431 config.add_route(
408 name='admin_settings_hooks_delete',
432 name='admin_settings_hooks_delete',
409 pattern='/settings/hooks/delete')
433 pattern='/settings/hooks/delete')
410 config.add_view(
434 config.add_view(
411 AdminSettingsView,
435 AdminSettingsView,
412 attr='settings_hooks_update',
436 attr='settings_hooks_update',
413 route_name='admin_settings_hooks_delete', request_method='POST',
437 route_name='admin_settings_hooks_delete', request_method='POST',
414 renderer='rhodecode:templates/admin/settings/settings.mako')
438 renderer='rhodecode:templates/admin/settings/settings.mako')
415
439
416 config.add_route(
440 config.add_route(
417 name='admin_settings_search',
441 name='admin_settings_search',
418 pattern='/settings/search')
442 pattern='/settings/search')
419 config.add_view(
443 config.add_view(
420 AdminSettingsView,
444 AdminSettingsView,
421 attr='settings_search',
445 attr='settings_search',
422 route_name='admin_settings_search', request_method='GET',
446 route_name='admin_settings_search', request_method='GET',
423 renderer='rhodecode:templates/admin/settings/settings.mako')
447 renderer='rhodecode:templates/admin/settings/settings.mako')
424
448
425 config.add_route(
449 config.add_route(
426 name='admin_settings_labs',
450 name='admin_settings_labs',
427 pattern='/settings/labs')
451 pattern='/settings/labs')
428 config.add_view(
452 config.add_view(
429 AdminSettingsView,
453 AdminSettingsView,
430 attr='settings_labs',
454 attr='settings_labs',
431 route_name='admin_settings_labs', request_method='GET',
455 route_name='admin_settings_labs', request_method='GET',
432 renderer='rhodecode:templates/admin/settings/settings.mako')
456 renderer='rhodecode:templates/admin/settings/settings.mako')
433
457
434 config.add_route(
458 config.add_route(
435 name='admin_settings_labs_update',
459 name='admin_settings_labs_update',
436 pattern='/settings/labs/update')
460 pattern='/settings/labs/update')
437 config.add_view(
461 config.add_view(
438 AdminSettingsView,
462 AdminSettingsView,
439 attr='settings_labs_update',
463 attr='settings_labs_update',
440 route_name='admin_settings_labs_update', request_method='POST',
464 route_name='admin_settings_labs_update', request_method='POST',
441 renderer='rhodecode:templates/admin/settings/settings.mako')
465 renderer='rhodecode:templates/admin/settings/settings.mako')
442
466
443 # Automation EE feature
444 config.add_route(
445 'admin_settings_automation',
446 pattern=ADMIN_PREFIX + '/settings/automation')
447 config.add_view(
448 AdminSettingsView,
449 attr='settings_automation',
450 route_name='admin_settings_automation', request_method='GET',
451 renderer='rhodecode:templates/admin/settings/settings.mako')
452
453 # global permissions
467 # global permissions
454
468
455 config.add_route(
469 config.add_route(
456 name='admin_permissions_application',
470 name='admin_permissions_application',
457 pattern='/permissions/application')
471 pattern='/permissions/application')
458 config.add_view(
472 config.add_view(
459 AdminPermissionsView,
473 AdminPermissionsView,
460 attr='permissions_application',
474 attr='permissions_application',
461 route_name='admin_permissions_application', request_method='GET',
475 route_name='admin_permissions_application', request_method='GET',
462 renderer='rhodecode:templates/admin/permissions/permissions.mako')
476 renderer='rhodecode:templates/admin/permissions/permissions.mako')
463
477
464 config.add_route(
478 config.add_route(
465 name='admin_permissions_application_update',
479 name='admin_permissions_application_update',
466 pattern='/permissions/application/update')
480 pattern='/permissions/application/update')
467 config.add_view(
481 config.add_view(
468 AdminPermissionsView,
482 AdminPermissionsView,
469 attr='permissions_application_update',
483 attr='permissions_application_update',
470 route_name='admin_permissions_application_update', request_method='POST',
484 route_name='admin_permissions_application_update', request_method='POST',
471 renderer='rhodecode:templates/admin/permissions/permissions.mako')
485 renderer='rhodecode:templates/admin/permissions/permissions.mako')
472
486
473 config.add_route(
487 config.add_route(
474 name='admin_permissions_global',
488 name='admin_permissions_global',
475 pattern='/permissions/global')
489 pattern='/permissions/global')
476 config.add_view(
490 config.add_view(
477 AdminPermissionsView,
491 AdminPermissionsView,
478 attr='permissions_global',
492 attr='permissions_global',
479 route_name='admin_permissions_global', request_method='GET',
493 route_name='admin_permissions_global', request_method='GET',
480 renderer='rhodecode:templates/admin/permissions/permissions.mako')
494 renderer='rhodecode:templates/admin/permissions/permissions.mako')
481
495
482 config.add_route(
496 config.add_route(
483 name='admin_permissions_global_update',
497 name='admin_permissions_global_update',
484 pattern='/permissions/global/update')
498 pattern='/permissions/global/update')
485 config.add_view(
499 config.add_view(
486 AdminPermissionsView,
500 AdminPermissionsView,
487 attr='permissions_global_update',
501 attr='permissions_global_update',
488 route_name='admin_permissions_global_update', request_method='POST',
502 route_name='admin_permissions_global_update', request_method='POST',
489 renderer='rhodecode:templates/admin/permissions/permissions.mako')
503 renderer='rhodecode:templates/admin/permissions/permissions.mako')
490
504
491 config.add_route(
505 config.add_route(
492 name='admin_permissions_object',
506 name='admin_permissions_object',
493 pattern='/permissions/object')
507 pattern='/permissions/object')
494 config.add_view(
508 config.add_view(
495 AdminPermissionsView,
509 AdminPermissionsView,
496 attr='permissions_objects',
510 attr='permissions_objects',
497 route_name='admin_permissions_object', request_method='GET',
511 route_name='admin_permissions_object', request_method='GET',
498 renderer='rhodecode:templates/admin/permissions/permissions.mako')
512 renderer='rhodecode:templates/admin/permissions/permissions.mako')
499
513
500 config.add_route(
514 config.add_route(
501 name='admin_permissions_object_update',
515 name='admin_permissions_object_update',
502 pattern='/permissions/object/update')
516 pattern='/permissions/object/update')
503 config.add_view(
517 config.add_view(
504 AdminPermissionsView,
518 AdminPermissionsView,
505 attr='permissions_objects_update',
519 attr='permissions_objects_update',
506 route_name='admin_permissions_object_update', request_method='POST',
520 route_name='admin_permissions_object_update', request_method='POST',
507 renderer='rhodecode:templates/admin/permissions/permissions.mako')
521 renderer='rhodecode:templates/admin/permissions/permissions.mako')
508
522
509 # Branch perms EE feature
523 # Branch perms EE feature
510 config.add_route(
524 config.add_route(
511 name='admin_permissions_branch',
525 name='admin_permissions_branch',
512 pattern='/permissions/branch')
526 pattern='/permissions/branch')
513 config.add_view(
527 config.add_view(
514 AdminPermissionsView,
528 AdminPermissionsView,
515 attr='permissions_branch',
529 attr='permissions_branch',
516 route_name='admin_permissions_branch', request_method='GET',
530 route_name='admin_permissions_branch', request_method='GET',
517 renderer='rhodecode:templates/admin/permissions/permissions.mako')
531 renderer='rhodecode:templates/admin/permissions/permissions.mako')
518
532
519 config.add_route(
533 config.add_route(
520 name='admin_permissions_ips',
534 name='admin_permissions_ips',
521 pattern='/permissions/ips')
535 pattern='/permissions/ips')
522 config.add_view(
536 config.add_view(
523 AdminPermissionsView,
537 AdminPermissionsView,
524 attr='permissions_ips',
538 attr='permissions_ips',
525 route_name='admin_permissions_ips', request_method='GET',
539 route_name='admin_permissions_ips', request_method='GET',
526 renderer='rhodecode:templates/admin/permissions/permissions.mako')
540 renderer='rhodecode:templates/admin/permissions/permissions.mako')
527
541
528 config.add_route(
542 config.add_route(
529 name='admin_permissions_overview',
543 name='admin_permissions_overview',
530 pattern='/permissions/overview')
544 pattern='/permissions/overview')
531 config.add_view(
545 config.add_view(
532 AdminPermissionsView,
546 AdminPermissionsView,
533 attr='permissions_overview',
547 attr='permissions_overview',
534 route_name='admin_permissions_overview', request_method='GET',
548 route_name='admin_permissions_overview', request_method='GET',
535 renderer='rhodecode:templates/admin/permissions/permissions.mako')
549 renderer='rhodecode:templates/admin/permissions/permissions.mako')
536
550
537 config.add_route(
551 config.add_route(
538 name='admin_permissions_auth_token_access',
552 name='admin_permissions_auth_token_access',
539 pattern='/permissions/auth_token_access')
553 pattern='/permissions/auth_token_access')
540 config.add_view(
554 config.add_view(
541 AdminPermissionsView,
555 AdminPermissionsView,
542 attr='auth_token_access',
556 attr='auth_token_access',
543 route_name='admin_permissions_auth_token_access', request_method='GET',
557 route_name='admin_permissions_auth_token_access', request_method='GET',
544 renderer='rhodecode:templates/admin/permissions/permissions.mako')
558 renderer='rhodecode:templates/admin/permissions/permissions.mako')
545
559
546 config.add_route(
560 config.add_route(
547 name='admin_permissions_ssh_keys',
561 name='admin_permissions_ssh_keys',
548 pattern='/permissions/ssh_keys')
562 pattern='/permissions/ssh_keys')
549 config.add_view(
563 config.add_view(
550 AdminPermissionsView,
564 AdminPermissionsView,
551 attr='ssh_keys',
565 attr='ssh_keys',
552 route_name='admin_permissions_ssh_keys', request_method='GET',
566 route_name='admin_permissions_ssh_keys', request_method='GET',
553 renderer='rhodecode:templates/admin/permissions/permissions.mako')
567 renderer='rhodecode:templates/admin/permissions/permissions.mako')
554
568
555 config.add_route(
569 config.add_route(
556 name='admin_permissions_ssh_keys_data',
570 name='admin_permissions_ssh_keys_data',
557 pattern='/permissions/ssh_keys/data')
571 pattern='/permissions/ssh_keys/data')
558 config.add_view(
572 config.add_view(
559 AdminPermissionsView,
573 AdminPermissionsView,
560 attr='ssh_keys_data',
574 attr='ssh_keys_data',
561 route_name='admin_permissions_ssh_keys_data', request_method='GET',
575 route_name='admin_permissions_ssh_keys_data', request_method='GET',
562 renderer='json_ext', xhr=True)
576 renderer='json_ext', xhr=True)
563
577
564 config.add_route(
578 config.add_route(
565 name='admin_permissions_ssh_keys_update',
579 name='admin_permissions_ssh_keys_update',
566 pattern='/permissions/ssh_keys/update')
580 pattern='/permissions/ssh_keys/update')
567 config.add_view(
581 config.add_view(
568 AdminPermissionsView,
582 AdminPermissionsView,
569 attr='ssh_keys_update',
583 attr='ssh_keys_update',
570 route_name='admin_permissions_ssh_keys_update', request_method='POST',
584 route_name='admin_permissions_ssh_keys_update', request_method='POST',
571 renderer='rhodecode:templates/admin/permissions/permissions.mako')
585 renderer='rhodecode:templates/admin/permissions/permissions.mako')
572
586
573 # users admin
587 # users admin
574 config.add_route(
588 config.add_route(
575 name='users',
589 name='users',
576 pattern='/users')
590 pattern='/users')
577 config.add_view(
591 config.add_view(
578 AdminUsersView,
592 AdminUsersView,
579 attr='users_list',
593 attr='users_list',
580 route_name='users', request_method='GET',
594 route_name='users', request_method='GET',
581 renderer='rhodecode:templates/admin/users/users.mako')
595 renderer='rhodecode:templates/admin/users/users.mako')
582
596
583 config.add_route(
597 config.add_route(
584 name='users_data',
598 name='users_data',
585 pattern='/users_data')
599 pattern='/users_data')
586 config.add_view(
600 config.add_view(
587 AdminUsersView,
601 AdminUsersView,
588 attr='users_list_data',
602 attr='users_list_data',
589 # renderer defined below
603 # renderer defined below
590 route_name='users_data', request_method='GET',
604 route_name='users_data', request_method='GET',
591 renderer='json_ext', xhr=True)
605 renderer='json_ext', xhr=True)
592
606
593 config.add_route(
607 config.add_route(
594 name='users_create',
608 name='users_create',
595 pattern='/users/create')
609 pattern='/users/create')
596 config.add_view(
610 config.add_view(
597 AdminUsersView,
611 AdminUsersView,
598 attr='users_create',
612 attr='users_create',
599 route_name='users_create', request_method='POST',
613 route_name='users_create', request_method='POST',
600 renderer='rhodecode:templates/admin/users/user_add.mako')
614 renderer='rhodecode:templates/admin/users/user_add.mako')
601
615
602 config.add_route(
616 config.add_route(
603 name='users_new',
617 name='users_new',
604 pattern='/users/new')
618 pattern='/users/new')
605 config.add_view(
619 config.add_view(
606 AdminUsersView,
620 AdminUsersView,
607 attr='users_new',
621 attr='users_new',
608 route_name='users_new', request_method='GET',
622 route_name='users_new', request_method='GET',
609 renderer='rhodecode:templates/admin/users/user_add.mako')
623 renderer='rhodecode:templates/admin/users/user_add.mako')
610
624
611 # user management
625 # user management
612 config.add_route(
626 config.add_route(
613 name='user_edit',
627 name='user_edit',
614 pattern=r'/users/{user_id:\d+}/edit',
628 pattern=r'/users/{user_id:\d+}/edit',
615 user_route=True)
629 user_route=True)
616 config.add_view(
630 config.add_view(
617 UsersView,
631 UsersView,
618 attr='user_edit',
632 attr='user_edit',
619 route_name='user_edit', request_method='GET',
633 route_name='user_edit', request_method='GET',
620 renderer='rhodecode:templates/admin/users/user_edit.mako')
634 renderer='rhodecode:templates/admin/users/user_edit.mako')
621
635
622 config.add_route(
636 config.add_route(
623 name='user_edit_advanced',
637 name='user_edit_advanced',
624 pattern=r'/users/{user_id:\d+}/edit/advanced',
638 pattern=r'/users/{user_id:\d+}/edit/advanced',
625 user_route=True)
639 user_route=True)
626 config.add_view(
640 config.add_view(
627 UsersView,
641 UsersView,
628 attr='user_edit_advanced',
642 attr='user_edit_advanced',
629 route_name='user_edit_advanced', request_method='GET',
643 route_name='user_edit_advanced', request_method='GET',
630 renderer='rhodecode:templates/admin/users/user_edit.mako')
644 renderer='rhodecode:templates/admin/users/user_edit.mako')
631
645
632 config.add_route(
646 config.add_route(
633 name='user_edit_global_perms',
647 name='user_edit_global_perms',
634 pattern=r'/users/{user_id:\d+}/edit/global_permissions',
648 pattern=r'/users/{user_id:\d+}/edit/global_permissions',
635 user_route=True)
649 user_route=True)
636 config.add_view(
650 config.add_view(
637 UsersView,
651 UsersView,
638 attr='user_edit_global_perms',
652 attr='user_edit_global_perms',
639 route_name='user_edit_global_perms', request_method='GET',
653 route_name='user_edit_global_perms', request_method='GET',
640 renderer='rhodecode:templates/admin/users/user_edit.mako')
654 renderer='rhodecode:templates/admin/users/user_edit.mako')
641
655
642 config.add_route(
656 config.add_route(
643 name='user_edit_global_perms_update',
657 name='user_edit_global_perms_update',
644 pattern=r'/users/{user_id:\d+}/edit/global_permissions/update',
658 pattern=r'/users/{user_id:\d+}/edit/global_permissions/update',
645 user_route=True)
659 user_route=True)
646 config.add_view(
660 config.add_view(
647 UsersView,
661 UsersView,
648 attr='user_edit_global_perms_update',
662 attr='user_edit_global_perms_update',
649 route_name='user_edit_global_perms_update', request_method='POST',
663 route_name='user_edit_global_perms_update', request_method='POST',
650 renderer='rhodecode:templates/admin/users/user_edit.mako')
664 renderer='rhodecode:templates/admin/users/user_edit.mako')
651
665
652 config.add_route(
666 config.add_route(
653 name='user_update',
667 name='user_update',
654 pattern=r'/users/{user_id:\d+}/update',
668 pattern=r'/users/{user_id:\d+}/update',
655 user_route=True)
669 user_route=True)
656 config.add_view(
670 config.add_view(
657 UsersView,
671 UsersView,
658 attr='user_update',
672 attr='user_update',
659 route_name='user_update', request_method='POST',
673 route_name='user_update', request_method='POST',
660 renderer='rhodecode:templates/admin/users/user_edit.mako')
674 renderer='rhodecode:templates/admin/users/user_edit.mako')
661
675
662 config.add_route(
676 config.add_route(
663 name='user_delete',
677 name='user_delete',
664 pattern=r'/users/{user_id:\d+}/delete',
678 pattern=r'/users/{user_id:\d+}/delete',
665 user_route=True)
679 user_route=True)
666 config.add_view(
680 config.add_view(
667 UsersView,
681 UsersView,
668 attr='user_delete',
682 attr='user_delete',
669 route_name='user_delete', request_method='POST',
683 route_name='user_delete', request_method='POST',
670 renderer='rhodecode:templates/admin/users/user_edit.mako')
684 renderer='rhodecode:templates/admin/users/user_edit.mako')
671
685
672 config.add_route(
686 config.add_route(
673 name='user_enable_force_password_reset',
687 name='user_enable_force_password_reset',
674 pattern=r'/users/{user_id:\d+}/password_reset_enable',
688 pattern=r'/users/{user_id:\d+}/password_reset_enable',
675 user_route=True)
689 user_route=True)
676 config.add_view(
690 config.add_view(
677 UsersView,
691 UsersView,
678 attr='user_enable_force_password_reset',
692 attr='user_enable_force_password_reset',
679 route_name='user_enable_force_password_reset', request_method='POST',
693 route_name='user_enable_force_password_reset', request_method='POST',
680 renderer='rhodecode:templates/admin/users/user_edit.mako')
694 renderer='rhodecode:templates/admin/users/user_edit.mako')
681
695
682 config.add_route(
696 config.add_route(
683 name='user_disable_force_password_reset',
697 name='user_disable_force_password_reset',
684 pattern=r'/users/{user_id:\d+}/password_reset_disable',
698 pattern=r'/users/{user_id:\d+}/password_reset_disable',
685 user_route=True)
699 user_route=True)
686 config.add_view(
700 config.add_view(
687 UsersView,
701 UsersView,
688 attr='user_disable_force_password_reset',
702 attr='user_disable_force_password_reset',
689 route_name='user_disable_force_password_reset', request_method='POST',
703 route_name='user_disable_force_password_reset', request_method='POST',
690 renderer='rhodecode:templates/admin/users/user_edit.mako')
704 renderer='rhodecode:templates/admin/users/user_edit.mako')
691
705
692 config.add_route(
706 config.add_route(
693 name='user_create_personal_repo_group',
707 name='user_create_personal_repo_group',
694 pattern=r'/users/{user_id:\d+}/create_repo_group',
708 pattern=r'/users/{user_id:\d+}/create_repo_group',
695 user_route=True)
709 user_route=True)
696 config.add_view(
710 config.add_view(
697 UsersView,
711 UsersView,
698 attr='user_create_personal_repo_group',
712 attr='user_create_personal_repo_group',
699 route_name='user_create_personal_repo_group', request_method='POST',
713 route_name='user_create_personal_repo_group', request_method='POST',
700 renderer='rhodecode:templates/admin/users/user_edit.mako')
714 renderer='rhodecode:templates/admin/users/user_edit.mako')
701
715
702 # user notice
716 # user notice
703 config.add_route(
717 config.add_route(
704 name='user_notice_dismiss',
718 name='user_notice_dismiss',
705 pattern=r'/users/{user_id:\d+}/notice_dismiss',
719 pattern=r'/users/{user_id:\d+}/notice_dismiss',
706 user_route=True)
720 user_route=True)
707 config.add_view(
721 config.add_view(
708 UsersView,
722 UsersView,
709 attr='user_notice_dismiss',
723 attr='user_notice_dismiss',
710 route_name='user_notice_dismiss', request_method='POST',
724 route_name='user_notice_dismiss', request_method='POST',
711 renderer='json_ext', xhr=True)
725 renderer='json_ext', xhr=True)
712
726
713 # user auth tokens
727 # user auth tokens
714 config.add_route(
728 config.add_route(
715 name='edit_user_auth_tokens',
729 name='edit_user_auth_tokens',
716 pattern=r'/users/{user_id:\d+}/edit/auth_tokens',
730 pattern=r'/users/{user_id:\d+}/edit/auth_tokens',
717 user_route=True)
731 user_route=True)
718 config.add_view(
732 config.add_view(
719 UsersView,
733 UsersView,
720 attr='auth_tokens',
734 attr='auth_tokens',
721 route_name='edit_user_auth_tokens', request_method='GET',
735 route_name='edit_user_auth_tokens', request_method='GET',
722 renderer='rhodecode:templates/admin/users/user_edit.mako')
736 renderer='rhodecode:templates/admin/users/user_edit.mako')
723
737
724 config.add_route(
738 config.add_route(
725 name='edit_user_auth_tokens_view',
739 name='edit_user_auth_tokens_view',
726 pattern=r'/users/{user_id:\d+}/edit/auth_tokens/view',
740 pattern=r'/users/{user_id:\d+}/edit/auth_tokens/view',
727 user_route=True)
741 user_route=True)
728 config.add_view(
742 config.add_view(
729 UsersView,
743 UsersView,
730 attr='auth_tokens_view',
744 attr='auth_tokens_view',
731 route_name='edit_user_auth_tokens_view', request_method='POST',
745 route_name='edit_user_auth_tokens_view', request_method='POST',
732 renderer='json_ext', xhr=True)
746 renderer='json_ext', xhr=True)
733
747
734 config.add_route(
748 config.add_route(
735 name='edit_user_auth_tokens_add',
749 name='edit_user_auth_tokens_add',
736 pattern=r'/users/{user_id:\d+}/edit/auth_tokens/new',
750 pattern=r'/users/{user_id:\d+}/edit/auth_tokens/new',
737 user_route=True)
751 user_route=True)
738 config.add_view(
752 config.add_view(
739 UsersView,
753 UsersView,
740 attr='auth_tokens_add',
754 attr='auth_tokens_add',
741 route_name='edit_user_auth_tokens_add', request_method='POST')
755 route_name='edit_user_auth_tokens_add', request_method='POST')
742
756
743 config.add_route(
757 config.add_route(
744 name='edit_user_auth_tokens_delete',
758 name='edit_user_auth_tokens_delete',
745 pattern=r'/users/{user_id:\d+}/edit/auth_tokens/delete',
759 pattern=r'/users/{user_id:\d+}/edit/auth_tokens/delete',
746 user_route=True)
760 user_route=True)
747 config.add_view(
761 config.add_view(
748 UsersView,
762 UsersView,
749 attr='auth_tokens_delete',
763 attr='auth_tokens_delete',
750 route_name='edit_user_auth_tokens_delete', request_method='POST')
764 route_name='edit_user_auth_tokens_delete', request_method='POST')
751
765
752 # user ssh keys
766 # user ssh keys
753 config.add_route(
767 config.add_route(
754 name='edit_user_ssh_keys',
768 name='edit_user_ssh_keys',
755 pattern=r'/users/{user_id:\d+}/edit/ssh_keys',
769 pattern=r'/users/{user_id:\d+}/edit/ssh_keys',
756 user_route=True)
770 user_route=True)
757 config.add_view(
771 config.add_view(
758 UsersView,
772 UsersView,
759 attr='ssh_keys',
773 attr='ssh_keys',
760 route_name='edit_user_ssh_keys', request_method='GET',
774 route_name='edit_user_ssh_keys', request_method='GET',
761 renderer='rhodecode:templates/admin/users/user_edit.mako')
775 renderer='rhodecode:templates/admin/users/user_edit.mako')
762
776
763 config.add_route(
777 config.add_route(
764 name='edit_user_ssh_keys_generate_keypair',
778 name='edit_user_ssh_keys_generate_keypair',
765 pattern=r'/users/{user_id:\d+}/edit/ssh_keys/generate',
779 pattern=r'/users/{user_id:\d+}/edit/ssh_keys/generate',
766 user_route=True)
780 user_route=True)
767 config.add_view(
781 config.add_view(
768 UsersView,
782 UsersView,
769 attr='ssh_keys_generate_keypair',
783 attr='ssh_keys_generate_keypair',
770 route_name='edit_user_ssh_keys_generate_keypair', request_method='GET',
784 route_name='edit_user_ssh_keys_generate_keypair', request_method='GET',
771 renderer='rhodecode:templates/admin/users/user_edit.mako')
785 renderer='rhodecode:templates/admin/users/user_edit.mako')
772
786
773 config.add_route(
787 config.add_route(
774 name='edit_user_ssh_keys_add',
788 name='edit_user_ssh_keys_add',
775 pattern=r'/users/{user_id:\d+}/edit/ssh_keys/new',
789 pattern=r'/users/{user_id:\d+}/edit/ssh_keys/new',
776 user_route=True)
790 user_route=True)
777 config.add_view(
791 config.add_view(
778 UsersView,
792 UsersView,
779 attr='ssh_keys_add',
793 attr='ssh_keys_add',
780 route_name='edit_user_ssh_keys_add', request_method='POST')
794 route_name='edit_user_ssh_keys_add', request_method='POST')
781
795
782 config.add_route(
796 config.add_route(
783 name='edit_user_ssh_keys_delete',
797 name='edit_user_ssh_keys_delete',
784 pattern=r'/users/{user_id:\d+}/edit/ssh_keys/delete',
798 pattern=r'/users/{user_id:\d+}/edit/ssh_keys/delete',
785 user_route=True)
799 user_route=True)
786 config.add_view(
800 config.add_view(
787 UsersView,
801 UsersView,
788 attr='ssh_keys_delete',
802 attr='ssh_keys_delete',
789 route_name='edit_user_ssh_keys_delete', request_method='POST')
803 route_name='edit_user_ssh_keys_delete', request_method='POST')
790
804
791 # user emails
805 # user emails
792 config.add_route(
806 config.add_route(
793 name='edit_user_emails',
807 name='edit_user_emails',
794 pattern=r'/users/{user_id:\d+}/edit/emails',
808 pattern=r'/users/{user_id:\d+}/edit/emails',
795 user_route=True)
809 user_route=True)
796 config.add_view(
810 config.add_view(
797 UsersView,
811 UsersView,
798 attr='emails',
812 attr='emails',
799 route_name='edit_user_emails', request_method='GET',
813 route_name='edit_user_emails', request_method='GET',
800 renderer='rhodecode:templates/admin/users/user_edit.mako')
814 renderer='rhodecode:templates/admin/users/user_edit.mako')
801
815
802 config.add_route(
816 config.add_route(
803 name='edit_user_emails_add',
817 name='edit_user_emails_add',
804 pattern=r'/users/{user_id:\d+}/edit/emails/new',
818 pattern=r'/users/{user_id:\d+}/edit/emails/new',
805 user_route=True)
819 user_route=True)
806 config.add_view(
820 config.add_view(
807 UsersView,
821 UsersView,
808 attr='emails_add',
822 attr='emails_add',
809 route_name='edit_user_emails_add', request_method='POST')
823 route_name='edit_user_emails_add', request_method='POST')
810
824
811 config.add_route(
825 config.add_route(
812 name='edit_user_emails_delete',
826 name='edit_user_emails_delete',
813 pattern=r'/users/{user_id:\d+}/edit/emails/delete',
827 pattern=r'/users/{user_id:\d+}/edit/emails/delete',
814 user_route=True)
828 user_route=True)
815 config.add_view(
829 config.add_view(
816 UsersView,
830 UsersView,
817 attr='emails_delete',
831 attr='emails_delete',
818 route_name='edit_user_emails_delete', request_method='POST')
832 route_name='edit_user_emails_delete', request_method='POST')
819
833
820 # user IPs
834 # user IPs
821 config.add_route(
835 config.add_route(
822 name='edit_user_ips',
836 name='edit_user_ips',
823 pattern=r'/users/{user_id:\d+}/edit/ips',
837 pattern=r'/users/{user_id:\d+}/edit/ips',
824 user_route=True)
838 user_route=True)
825 config.add_view(
839 config.add_view(
826 UsersView,
840 UsersView,
827 attr='ips',
841 attr='ips',
828 route_name='edit_user_ips', request_method='GET',
842 route_name='edit_user_ips', request_method='GET',
829 renderer='rhodecode:templates/admin/users/user_edit.mako')
843 renderer='rhodecode:templates/admin/users/user_edit.mako')
830
844
831 config.add_route(
845 config.add_route(
832 name='edit_user_ips_add',
846 name='edit_user_ips_add',
833 pattern=r'/users/{user_id:\d+}/edit/ips/new',
847 pattern=r'/users/{user_id:\d+}/edit/ips/new',
834 user_route_with_default=True) # enabled for default user too
848 user_route_with_default=True) # enabled for default user too
835 config.add_view(
849 config.add_view(
836 UsersView,
850 UsersView,
837 attr='ips_add',
851 attr='ips_add',
838 route_name='edit_user_ips_add', request_method='POST')
852 route_name='edit_user_ips_add', request_method='POST')
839
853
840 config.add_route(
854 config.add_route(
841 name='edit_user_ips_delete',
855 name='edit_user_ips_delete',
842 pattern=r'/users/{user_id:\d+}/edit/ips/delete',
856 pattern=r'/users/{user_id:\d+}/edit/ips/delete',
843 user_route_with_default=True) # enabled for default user too
857 user_route_with_default=True) # enabled for default user too
844 config.add_view(
858 config.add_view(
845 UsersView,
859 UsersView,
846 attr='ips_delete',
860 attr='ips_delete',
847 route_name='edit_user_ips_delete', request_method='POST')
861 route_name='edit_user_ips_delete', request_method='POST')
848
862
849 # user perms
863 # user perms
850 config.add_route(
864 config.add_route(
851 name='edit_user_perms_summary',
865 name='edit_user_perms_summary',
852 pattern=r'/users/{user_id:\d+}/edit/permissions_summary',
866 pattern=r'/users/{user_id:\d+}/edit/permissions_summary',
853 user_route=True)
867 user_route=True)
854 config.add_view(
868 config.add_view(
855 UsersView,
869 UsersView,
856 attr='user_perms_summary',
870 attr='user_perms_summary',
857 route_name='edit_user_perms_summary', request_method='GET',
871 route_name='edit_user_perms_summary', request_method='GET',
858 renderer='rhodecode:templates/admin/users/user_edit.mako')
872 renderer='rhodecode:templates/admin/users/user_edit.mako')
859
873
860 config.add_route(
874 config.add_route(
861 name='edit_user_perms_summary_json',
875 name='edit_user_perms_summary_json',
862 pattern=r'/users/{user_id:\d+}/edit/permissions_summary/json',
876 pattern=r'/users/{user_id:\d+}/edit/permissions_summary/json',
863 user_route=True)
877 user_route=True)
864 config.add_view(
878 config.add_view(
865 UsersView,
879 UsersView,
866 attr='user_perms_summary_json',
880 attr='user_perms_summary_json',
867 route_name='edit_user_perms_summary_json', request_method='GET',
881 route_name='edit_user_perms_summary_json', request_method='GET',
868 renderer='json_ext')
882 renderer='json_ext')
869
883
870 # user user groups management
884 # user user groups management
871 config.add_route(
885 config.add_route(
872 name='edit_user_groups_management',
886 name='edit_user_groups_management',
873 pattern=r'/users/{user_id:\d+}/edit/groups_management',
887 pattern=r'/users/{user_id:\d+}/edit/groups_management',
874 user_route=True)
888 user_route=True)
875 config.add_view(
889 config.add_view(
876 UsersView,
890 UsersView,
877 attr='groups_management',
891 attr='groups_management',
878 route_name='edit_user_groups_management', request_method='GET',
892 route_name='edit_user_groups_management', request_method='GET',
879 renderer='rhodecode:templates/admin/users/user_edit.mako')
893 renderer='rhodecode:templates/admin/users/user_edit.mako')
880
894
881 config.add_route(
895 config.add_route(
882 name='edit_user_groups_management_updates',
896 name='edit_user_groups_management_updates',
883 pattern=r'/users/{user_id:\d+}/edit/edit_user_groups_management/updates',
897 pattern=r'/users/{user_id:\d+}/edit/edit_user_groups_management/updates',
884 user_route=True)
898 user_route=True)
885 config.add_view(
899 config.add_view(
886 UsersView,
900 UsersView,
887 attr='groups_management_updates',
901 attr='groups_management_updates',
888 route_name='edit_user_groups_management_updates', request_method='POST')
902 route_name='edit_user_groups_management_updates', request_method='POST')
889
903
890 # user audit logs
904 # user audit logs
891 config.add_route(
905 config.add_route(
892 name='edit_user_audit_logs',
906 name='edit_user_audit_logs',
893 pattern=r'/users/{user_id:\d+}/edit/audit', user_route=True)
907 pattern=r'/users/{user_id:\d+}/edit/audit', user_route=True)
894 config.add_view(
908 config.add_view(
895 UsersView,
909 UsersView,
896 attr='user_audit_logs',
910 attr='user_audit_logs',
897 route_name='edit_user_audit_logs', request_method='GET',
911 route_name='edit_user_audit_logs', request_method='GET',
898 renderer='rhodecode:templates/admin/users/user_edit.mako')
912 renderer='rhodecode:templates/admin/users/user_edit.mako')
899
913
900 config.add_route(
914 config.add_route(
901 name='edit_user_audit_logs_download',
915 name='edit_user_audit_logs_download',
902 pattern=r'/users/{user_id:\d+}/edit/audit/download', user_route=True)
916 pattern=r'/users/{user_id:\d+}/edit/audit/download', user_route=True)
903 config.add_view(
917 config.add_view(
904 UsersView,
918 UsersView,
905 attr='user_audit_logs_download',
919 attr='user_audit_logs_download',
906 route_name='edit_user_audit_logs_download', request_method='GET',
920 route_name='edit_user_audit_logs_download', request_method='GET',
907 renderer='string')
921 renderer='string')
908
922
909 # user caches
923 # user caches
910 config.add_route(
924 config.add_route(
911 name='edit_user_caches',
925 name='edit_user_caches',
912 pattern=r'/users/{user_id:\d+}/edit/caches',
926 pattern=r'/users/{user_id:\d+}/edit/caches',
913 user_route=True)
927 user_route=True)
914 config.add_view(
928 config.add_view(
915 UsersView,
929 UsersView,
916 attr='user_caches',
930 attr='user_caches',
917 route_name='edit_user_caches', request_method='GET',
931 route_name='edit_user_caches', request_method='GET',
918 renderer='rhodecode:templates/admin/users/user_edit.mako')
932 renderer='rhodecode:templates/admin/users/user_edit.mako')
919
933
920 config.add_route(
934 config.add_route(
921 name='edit_user_caches_update',
935 name='edit_user_caches_update',
922 pattern=r'/users/{user_id:\d+}/edit/caches/update',
936 pattern=r'/users/{user_id:\d+}/edit/caches/update',
923 user_route=True)
937 user_route=True)
924 config.add_view(
938 config.add_view(
925 UsersView,
939 UsersView,
926 attr='user_caches_update',
940 attr='user_caches_update',
927 route_name='edit_user_caches_update', request_method='POST')
941 route_name='edit_user_caches_update', request_method='POST')
928
942
929 # user-groups admin
943 # user-groups admin
930 config.add_route(
944 config.add_route(
931 name='user_groups',
945 name='user_groups',
932 pattern='/user_groups')
946 pattern='/user_groups')
933 config.add_view(
947 config.add_view(
934 AdminUserGroupsView,
948 AdminUserGroupsView,
935 attr='user_groups_list',
949 attr='user_groups_list',
936 route_name='user_groups', request_method='GET',
950 route_name='user_groups', request_method='GET',
937 renderer='rhodecode:templates/admin/user_groups/user_groups.mako')
951 renderer='rhodecode:templates/admin/user_groups/user_groups.mako')
938
952
939 config.add_route(
953 config.add_route(
940 name='user_groups_data',
954 name='user_groups_data',
941 pattern='/user_groups_data')
955 pattern='/user_groups_data')
942 config.add_view(
956 config.add_view(
943 AdminUserGroupsView,
957 AdminUserGroupsView,
944 attr='user_groups_list_data',
958 attr='user_groups_list_data',
945 route_name='user_groups_data', request_method='GET',
959 route_name='user_groups_data', request_method='GET',
946 renderer='json_ext', xhr=True)
960 renderer='json_ext', xhr=True)
947
961
948 config.add_route(
962 config.add_route(
949 name='user_groups_new',
963 name='user_groups_new',
950 pattern='/user_groups/new')
964 pattern='/user_groups/new')
951 config.add_view(
965 config.add_view(
952 AdminUserGroupsView,
966 AdminUserGroupsView,
953 attr='user_groups_new',
967 attr='user_groups_new',
954 route_name='user_groups_new', request_method='GET',
968 route_name='user_groups_new', request_method='GET',
955 renderer='rhodecode:templates/admin/user_groups/user_group_add.mako')
969 renderer='rhodecode:templates/admin/user_groups/user_group_add.mako')
956
970
957 config.add_route(
971 config.add_route(
958 name='user_groups_create',
972 name='user_groups_create',
959 pattern='/user_groups/create')
973 pattern='/user_groups/create')
960 config.add_view(
974 config.add_view(
961 AdminUserGroupsView,
975 AdminUserGroupsView,
962 attr='user_groups_create',
976 attr='user_groups_create',
963 route_name='user_groups_create', request_method='POST',
977 route_name='user_groups_create', request_method='POST',
964 renderer='rhodecode:templates/admin/user_groups/user_group_add.mako')
978 renderer='rhodecode:templates/admin/user_groups/user_group_add.mako')
965
979
966 # repos admin
980 # repos admin
967 config.add_route(
981 config.add_route(
968 name='repos',
982 name='repos',
969 pattern='/repos')
983 pattern='/repos')
970 config.add_view(
984 config.add_view(
971 AdminReposView,
985 AdminReposView,
972 attr='repository_list',
986 attr='repository_list',
973 route_name='repos', request_method='GET',
987 route_name='repos', request_method='GET',
974 renderer='rhodecode:templates/admin/repos/repos.mako')
988 renderer='rhodecode:templates/admin/repos/repos.mako')
975
989
976 config.add_route(
990 config.add_route(
977 name='repos_data',
991 name='repos_data',
978 pattern='/repos_data')
992 pattern='/repos_data')
979 config.add_view(
993 config.add_view(
980 AdminReposView,
994 AdminReposView,
981 attr='repository_list_data',
995 attr='repository_list_data',
982 route_name='repos_data', request_method='GET',
996 route_name='repos_data', request_method='GET',
983 renderer='json_ext', xhr=True)
997 renderer='json_ext', xhr=True)
984
998
985 config.add_route(
999 config.add_route(
986 name='repo_new',
1000 name='repo_new',
987 pattern='/repos/new')
1001 pattern='/repos/new')
988 config.add_view(
1002 config.add_view(
989 AdminReposView,
1003 AdminReposView,
990 attr='repository_new',
1004 attr='repository_new',
991 route_name='repo_new', request_method='GET',
1005 route_name='repo_new', request_method='GET',
992 renderer='rhodecode:templates/admin/repos/repo_add.mako')
1006 renderer='rhodecode:templates/admin/repos/repo_add.mako')
993
1007
994 config.add_route(
1008 config.add_route(
995 name='repo_create',
1009 name='repo_create',
996 pattern='/repos/create')
1010 pattern='/repos/create')
997 config.add_view(
1011 config.add_view(
998 AdminReposView,
1012 AdminReposView,
999 attr='repository_create',
1013 attr='repository_create',
1000 route_name='repo_create', request_method='POST',
1014 route_name='repo_create', request_method='POST',
1001 renderer='rhodecode:templates/admin/repos/repos.mako')
1015 renderer='rhodecode:templates/admin/repos/repos.mako')
1002
1016
1003 # repo groups admin
1017 # repo groups admin
1004 config.add_route(
1018 config.add_route(
1005 name='repo_groups',
1019 name='repo_groups',
1006 pattern='/repo_groups')
1020 pattern='/repo_groups')
1007 config.add_view(
1021 config.add_view(
1008 AdminRepoGroupsView,
1022 AdminRepoGroupsView,
1009 attr='repo_group_list',
1023 attr='repo_group_list',
1010 route_name='repo_groups', request_method='GET',
1024 route_name='repo_groups', request_method='GET',
1011 renderer='rhodecode:templates/admin/repo_groups/repo_groups.mako')
1025 renderer='rhodecode:templates/admin/repo_groups/repo_groups.mako')
1012
1026
1013 config.add_route(
1027 config.add_route(
1014 name='repo_groups_data',
1028 name='repo_groups_data',
1015 pattern='/repo_groups_data')
1029 pattern='/repo_groups_data')
1016 config.add_view(
1030 config.add_view(
1017 AdminRepoGroupsView,
1031 AdminRepoGroupsView,
1018 attr='repo_group_list_data',
1032 attr='repo_group_list_data',
1019 route_name='repo_groups_data', request_method='GET',
1033 route_name='repo_groups_data', request_method='GET',
1020 renderer='json_ext', xhr=True)
1034 renderer='json_ext', xhr=True)
1021
1035
1022 config.add_route(
1036 config.add_route(
1023 name='repo_group_new',
1037 name='repo_group_new',
1024 pattern='/repo_group/new')
1038 pattern='/repo_group/new')
1025 config.add_view(
1039 config.add_view(
1026 AdminRepoGroupsView,
1040 AdminRepoGroupsView,
1027 attr='repo_group_new',
1041 attr='repo_group_new',
1028 route_name='repo_group_new', request_method='GET',
1042 route_name='repo_group_new', request_method='GET',
1029 renderer='rhodecode:templates/admin/repo_groups/repo_group_add.mako')
1043 renderer='rhodecode:templates/admin/repo_groups/repo_group_add.mako')
1030
1044
1031 config.add_route(
1045 config.add_route(
1032 name='repo_group_create',
1046 name='repo_group_create',
1033 pattern='/repo_group/create')
1047 pattern='/repo_group/create')
1034 config.add_view(
1048 config.add_view(
1035 AdminRepoGroupsView,
1049 AdminRepoGroupsView,
1036 attr='repo_group_create',
1050 attr='repo_group_create',
1037 route_name='repo_group_create', request_method='POST',
1051 route_name='repo_group_create', request_method='POST',
1038 renderer='rhodecode:templates/admin/repo_groups/repo_group_add.mako')
1052 renderer='rhodecode:templates/admin/repo_groups/repo_group_add.mako')
1039
1053
1040
1054
1041 def includeme(config):
1055 def includeme(config):
1042 from rhodecode.apps._base.navigation import includeme as nav_includeme
1043 from rhodecode.apps.admin.views.main_views import AdminMainView
1044
1045 # Create admin navigation registry and add it to the pyramid registry.
1056 # Create admin navigation registry and add it to the pyramid registry.
1046 nav_includeme(config)
1057 nav_includeme(config)
1047
1058
1048 # main admin routes
1059 # main admin routes
1049 config.add_route(
1060 config.add_route(
1050 name='admin_home', pattern=ADMIN_PREFIX)
1061 name='admin_home', pattern=ADMIN_PREFIX)
1051 config.add_view(
1062 config.add_view(
1052 AdminMainView,
1063 AdminMainView,
1053 attr='admin_main',
1064 attr='admin_main',
1054 route_name='admin_home', request_method='GET',
1065 route_name='admin_home', request_method='GET',
1055 renderer='rhodecode:templates/admin/main.mako')
1066 renderer='rhodecode:templates/admin/main.mako')
1056
1067
1057 # pr global redirect
1068 # pr global redirect
1058 config.add_route(
1069 config.add_route(
1059 name='pull_requests_global_0', # backward compat
1070 name='pull_requests_global_0', # backward compat
1060 pattern=ADMIN_PREFIX + r'/pull_requests/{pull_request_id:\d+}')
1071 pattern=ADMIN_PREFIX + r'/pull_requests/{pull_request_id:\d+}')
1061 config.add_view(
1072 config.add_view(
1062 AdminMainView,
1073 AdminMainView,
1063 attr='pull_requests',
1074 attr='pull_requests',
1064 route_name='pull_requests_global_0', request_method='GET')
1075 route_name='pull_requests_global_0', request_method='GET')
1065
1076
1066 config.add_route(
1077 config.add_route(
1067 name='pull_requests_global_1', # backward compat
1078 name='pull_requests_global_1', # backward compat
1068 pattern=ADMIN_PREFIX + r'/pull-requests/{pull_request_id:\d+}')
1079 pattern=ADMIN_PREFIX + r'/pull-requests/{pull_request_id:\d+}')
1069 config.add_view(
1080 config.add_view(
1070 AdminMainView,
1081 AdminMainView,
1071 attr='pull_requests',
1082 attr='pull_requests',
1072 route_name='pull_requests_global_1', request_method='GET')
1083 route_name='pull_requests_global_1', request_method='GET')
1073
1084
1074 config.add_route(
1085 config.add_route(
1075 name='pull_requests_global',
1086 name='pull_requests_global',
1076 pattern=ADMIN_PREFIX + r'/pull-request/{pull_request_id:\d+}')
1087 pattern=ADMIN_PREFIX + r'/pull-request/{pull_request_id:\d+}')
1077 config.add_view(
1088 config.add_view(
1078 AdminMainView,
1089 AdminMainView,
1079 attr='pull_requests',
1090 attr='pull_requests',
1080 route_name='pull_requests_global', request_method='GET')
1091 route_name='pull_requests_global', request_method='GET')
1081
1092
1082 config.include(admin_routes, route_prefix=ADMIN_PREFIX)
1093 config.include(admin_routes, route_prefix=ADMIN_PREFIX)
@@ -1,721 +1,714 b''
1 # Copyright (C) 2010-2023 RhodeCode GmbH
1 # Copyright (C) 2010-2023 RhodeCode GmbH
2 #
2 #
3 # This program is free software: you can redistribute it and/or modify
3 # This program is free software: you can redistribute it and/or modify
4 # it under the terms of the GNU Affero General Public License, version 3
4 # it under the terms of the GNU Affero General Public License, version 3
5 # (only), as published by the Free Software Foundation.
5 # (only), as published by the Free Software Foundation.
6 #
6 #
7 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # GNU General Public License for more details.
10 # GNU General Public License for more details.
11 #
11 #
12 # You should have received a copy of the GNU Affero General Public License
12 # You should have received a copy of the GNU Affero General Public License
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 #
14 #
15 # This program is dual-licensed. If you wish to learn more about the
15 # This program is dual-licensed. If you wish to learn more about the
16 # RhodeCode Enterprise Edition, including its added features, Support services,
16 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
18
18
19
19
20 import logging
20 import logging
21 import collections
21 import collections
22
22
23 import datetime
23 import datetime
24 import formencode
24 import formencode
25 import formencode.htmlfill
25 import formencode.htmlfill
26
26
27 import rhodecode
27 import rhodecode
28
28
29 from pyramid.httpexceptions import HTTPFound, HTTPNotFound
29 from pyramid.httpexceptions import HTTPFound, HTTPNotFound
30 from pyramid.renderers import render
30 from pyramid.renderers import render
31 from pyramid.response import Response
31 from pyramid.response import Response
32
32
33 from rhodecode.apps._base import BaseAppView
33 from rhodecode.apps._base import BaseAppView
34 from rhodecode.apps._base.navigation import navigation_list
34 from rhodecode.apps._base.navigation import navigation_list
35 from rhodecode.apps.svn_support.config_keys import generate_config
35 from rhodecode.apps.svn_support.config_keys import generate_config
36 from rhodecode.lib import helpers as h
36 from rhodecode.lib import helpers as h
37 from rhodecode.lib.auth import (
37 from rhodecode.lib.auth import (
38 LoginRequired, HasPermissionAllDecorator, CSRFRequired)
38 LoginRequired, HasPermissionAllDecorator, CSRFRequired)
39 from rhodecode.lib.celerylib import tasks, run_task
39 from rhodecode.lib.celerylib import tasks, run_task
40 from rhodecode.lib.str_utils import safe_str
40 from rhodecode.lib.str_utils import safe_str
41 from rhodecode.lib.utils import repo2db_mapper
41 from rhodecode.lib.utils import repo2db_mapper
42 from rhodecode.lib.utils2 import str2bool, AttributeDict
42 from rhodecode.lib.utils2 import str2bool, AttributeDict
43 from rhodecode.lib.index import searcher_from_config
43 from rhodecode.lib.index import searcher_from_config
44
44
45 from rhodecode.model.db import RhodeCodeUi, Repository
45 from rhodecode.model.db import RhodeCodeUi, Repository
46 from rhodecode.model.forms import (ApplicationSettingsForm,
46 from rhodecode.model.forms import (ApplicationSettingsForm,
47 ApplicationUiSettingsForm, ApplicationVisualisationForm,
47 ApplicationUiSettingsForm, ApplicationVisualisationForm,
48 LabsSettingsForm, IssueTrackerPatternsForm)
48 LabsSettingsForm, IssueTrackerPatternsForm)
49 from rhodecode.model.permission import PermissionModel
49 from rhodecode.model.permission import PermissionModel
50 from rhodecode.model.repo_group import RepoGroupModel
50 from rhodecode.model.repo_group import RepoGroupModel
51
51
52 from rhodecode.model.scm import ScmModel
52 from rhodecode.model.scm import ScmModel
53 from rhodecode.model.notification import EmailNotificationModel
53 from rhodecode.model.notification import EmailNotificationModel
54 from rhodecode.model.meta import Session
54 from rhodecode.model.meta import Session
55 from rhodecode.model.settings import (
55 from rhodecode.model.settings import (
56 IssueTrackerSettingsModel, VcsSettingsModel, SettingNotFound,
56 IssueTrackerSettingsModel, VcsSettingsModel, SettingNotFound,
57 SettingsModel)
57 SettingsModel)
58
58
59
59
60 log = logging.getLogger(__name__)
60 log = logging.getLogger(__name__)
61
61
62
62
63 class AdminSettingsView(BaseAppView):
63 class AdminSettingsView(BaseAppView):
64
64
65 def load_default_context(self):
65 def load_default_context(self):
66 c = self._get_local_tmpl_context()
66 c = self._get_local_tmpl_context()
67 c.labs_active = str2bool(
67 c.labs_active = str2bool(
68 rhodecode.CONFIG.get('labs_settings_active', 'true'))
68 rhodecode.CONFIG.get('labs_settings_active', 'true'))
69 c.navlist = navigation_list(self.request)
69 c.navlist = navigation_list(self.request)
70 return c
70 return c
71
71
72 @classmethod
72 @classmethod
73 def _get_ui_settings(cls):
73 def _get_ui_settings(cls):
74 ret = RhodeCodeUi.query().all()
74 ret = RhodeCodeUi.query().all()
75
75
76 if not ret:
76 if not ret:
77 raise Exception('Could not get application ui settings !')
77 raise Exception('Could not get application ui settings !')
78 settings = {}
78 settings = {}
79 for each in ret:
79 for each in ret:
80 k = each.ui_key
80 k = each.ui_key
81 v = each.ui_value
81 v = each.ui_value
82 if k == '/':
82 if k == '/':
83 k = 'root_path'
83 k = 'root_path'
84
84
85 if k in ['push_ssl', 'publish', 'enabled']:
85 if k in ['push_ssl', 'publish', 'enabled']:
86 v = str2bool(v)
86 v = str2bool(v)
87
87
88 if k.find('.') != -1:
88 if k.find('.') != -1:
89 k = k.replace('.', '_')
89 k = k.replace('.', '_')
90
90
91 if each.ui_section in ['hooks', 'extensions']:
91 if each.ui_section in ['hooks', 'extensions']:
92 v = each.ui_active
92 v = each.ui_active
93
93
94 settings[each.ui_section + '_' + k] = v
94 settings[each.ui_section + '_' + k] = v
95 return settings
95 return settings
96
96
97 @classmethod
97 @classmethod
98 def _form_defaults(cls):
98 def _form_defaults(cls):
99 defaults = SettingsModel().get_all_settings()
99 defaults = SettingsModel().get_all_settings()
100 defaults.update(cls._get_ui_settings())
100 defaults.update(cls._get_ui_settings())
101
101
102 defaults.update({
102 defaults.update({
103 'new_svn_branch': '',
103 'new_svn_branch': '',
104 'new_svn_tag': '',
104 'new_svn_tag': '',
105 })
105 })
106 return defaults
106 return defaults
107
107
108 @LoginRequired()
108 @LoginRequired()
109 @HasPermissionAllDecorator('hg.admin')
109 @HasPermissionAllDecorator('hg.admin')
110 def settings_vcs(self):
110 def settings_vcs(self):
111 c = self.load_default_context()
111 c = self.load_default_context()
112 c.active = 'vcs'
112 c.active = 'vcs'
113 model = VcsSettingsModel()
113 model = VcsSettingsModel()
114 c.svn_branch_patterns = model.get_global_svn_branch_patterns()
114 c.svn_branch_patterns = model.get_global_svn_branch_patterns()
115 c.svn_tag_patterns = model.get_global_svn_tag_patterns()
115 c.svn_tag_patterns = model.get_global_svn_tag_patterns()
116
116
117 settings = self.request.registry.settings
117 settings = self.request.registry.settings
118 c.svn_proxy_generate_config = settings[generate_config]
118 c.svn_proxy_generate_config = settings[generate_config]
119
119
120 defaults = self._form_defaults()
120 defaults = self._form_defaults()
121
121
122 model.create_largeobjects_dirs_if_needed(defaults['paths_root_path'])
122 model.create_largeobjects_dirs_if_needed(defaults['paths_root_path'])
123
123
124 data = render('rhodecode:templates/admin/settings/settings.mako',
124 data = render('rhodecode:templates/admin/settings/settings.mako',
125 self._get_template_context(c), self.request)
125 self._get_template_context(c), self.request)
126 html = formencode.htmlfill.render(
126 html = formencode.htmlfill.render(
127 data,
127 data,
128 defaults=defaults,
128 defaults=defaults,
129 encoding="UTF-8",
129 encoding="UTF-8",
130 force_defaults=False
130 force_defaults=False
131 )
131 )
132 return Response(html)
132 return Response(html)
133
133
134 @LoginRequired()
134 @LoginRequired()
135 @HasPermissionAllDecorator('hg.admin')
135 @HasPermissionAllDecorator('hg.admin')
136 @CSRFRequired()
136 @CSRFRequired()
137 def settings_vcs_update(self):
137 def settings_vcs_update(self):
138 _ = self.request.translate
138 _ = self.request.translate
139 c = self.load_default_context()
139 c = self.load_default_context()
140 c.active = 'vcs'
140 c.active = 'vcs'
141
141
142 model = VcsSettingsModel()
142 model = VcsSettingsModel()
143 c.svn_branch_patterns = model.get_global_svn_branch_patterns()
143 c.svn_branch_patterns = model.get_global_svn_branch_patterns()
144 c.svn_tag_patterns = model.get_global_svn_tag_patterns()
144 c.svn_tag_patterns = model.get_global_svn_tag_patterns()
145
145
146 settings = self.request.registry.settings
146 settings = self.request.registry.settings
147 c.svn_proxy_generate_config = settings[generate_config]
147 c.svn_proxy_generate_config = settings[generate_config]
148
148
149 application_form = ApplicationUiSettingsForm(self.request.translate)()
149 application_form = ApplicationUiSettingsForm(self.request.translate)()
150
150
151 try:
151 try:
152 form_result = application_form.to_python(dict(self.request.POST))
152 form_result = application_form.to_python(dict(self.request.POST))
153 except formencode.Invalid as errors:
153 except formencode.Invalid as errors:
154 h.flash(
154 h.flash(
155 _("Some form inputs contain invalid data."),
155 _("Some form inputs contain invalid data."),
156 category='error')
156 category='error')
157 data = render('rhodecode:templates/admin/settings/settings.mako',
157 data = render('rhodecode:templates/admin/settings/settings.mako',
158 self._get_template_context(c), self.request)
158 self._get_template_context(c), self.request)
159 html = formencode.htmlfill.render(
159 html = formencode.htmlfill.render(
160 data,
160 data,
161 defaults=errors.value,
161 defaults=errors.value,
162 errors=errors.unpack_errors() or {},
162 errors=errors.unpack_errors() or {},
163 prefix_error=False,
163 prefix_error=False,
164 encoding="UTF-8",
164 encoding="UTF-8",
165 force_defaults=False
165 force_defaults=False
166 )
166 )
167 return Response(html)
167 return Response(html)
168
168
169 try:
169 try:
170 if c.visual.allow_repo_location_change:
170 if c.visual.allow_repo_location_change:
171 model.update_global_path_setting(form_result['paths_root_path'])
171 model.update_global_path_setting(form_result['paths_root_path'])
172
172
173 model.update_global_ssl_setting(form_result['web_push_ssl'])
173 model.update_global_ssl_setting(form_result['web_push_ssl'])
174 model.update_global_hook_settings(form_result)
174 model.update_global_hook_settings(form_result)
175
175
176 model.create_or_update_global_svn_settings(form_result)
176 model.create_or_update_global_svn_settings(form_result)
177 model.create_or_update_global_hg_settings(form_result)
177 model.create_or_update_global_hg_settings(form_result)
178 model.create_or_update_global_git_settings(form_result)
178 model.create_or_update_global_git_settings(form_result)
179 model.create_or_update_global_pr_settings(form_result)
179 model.create_or_update_global_pr_settings(form_result)
180 except Exception:
180 except Exception:
181 log.exception("Exception while updating settings")
181 log.exception("Exception while updating settings")
182 h.flash(_('Error occurred during updating '
182 h.flash(_('Error occurred during updating '
183 'application settings'), category='error')
183 'application settings'), category='error')
184 else:
184 else:
185 Session().commit()
185 Session().commit()
186 h.flash(_('Updated VCS settings'), category='success')
186 h.flash(_('Updated VCS settings'), category='success')
187 raise HTTPFound(h.route_path('admin_settings_vcs'))
187 raise HTTPFound(h.route_path('admin_settings_vcs'))
188
188
189 data = render('rhodecode:templates/admin/settings/settings.mako',
189 data = render('rhodecode:templates/admin/settings/settings.mako',
190 self._get_template_context(c), self.request)
190 self._get_template_context(c), self.request)
191 html = formencode.htmlfill.render(
191 html = formencode.htmlfill.render(
192 data,
192 data,
193 defaults=self._form_defaults(),
193 defaults=self._form_defaults(),
194 encoding="UTF-8",
194 encoding="UTF-8",
195 force_defaults=False
195 force_defaults=False
196 )
196 )
197 return Response(html)
197 return Response(html)
198
198
199 @LoginRequired()
199 @LoginRequired()
200 @HasPermissionAllDecorator('hg.admin')
200 @HasPermissionAllDecorator('hg.admin')
201 @CSRFRequired()
201 @CSRFRequired()
202 def settings_vcs_delete_svn_pattern(self):
202 def settings_vcs_delete_svn_pattern(self):
203 delete_pattern_id = self.request.POST.get('delete_svn_pattern')
203 delete_pattern_id = self.request.POST.get('delete_svn_pattern')
204 model = VcsSettingsModel()
204 model = VcsSettingsModel()
205 try:
205 try:
206 model.delete_global_svn_pattern(delete_pattern_id)
206 model.delete_global_svn_pattern(delete_pattern_id)
207 except SettingNotFound:
207 except SettingNotFound:
208 log.exception(
208 log.exception(
209 'Failed to delete svn_pattern with id %s', delete_pattern_id)
209 'Failed to delete svn_pattern with id %s', delete_pattern_id)
210 raise HTTPNotFound()
210 raise HTTPNotFound()
211
211
212 Session().commit()
212 Session().commit()
213 return True
213 return True
214
214
215 @LoginRequired()
215 @LoginRequired()
216 @HasPermissionAllDecorator('hg.admin')
216 @HasPermissionAllDecorator('hg.admin')
217 def settings_mapping(self):
217 def settings_mapping(self):
218 c = self.load_default_context()
218 c = self.load_default_context()
219 c.active = 'mapping'
219 c.active = 'mapping'
220
220
221 data = render('rhodecode:templates/admin/settings/settings.mako',
221 data = render('rhodecode:templates/admin/settings/settings.mako',
222 self._get_template_context(c), self.request)
222 self._get_template_context(c), self.request)
223 html = formencode.htmlfill.render(
223 html = formencode.htmlfill.render(
224 data,
224 data,
225 defaults=self._form_defaults(),
225 defaults=self._form_defaults(),
226 encoding="UTF-8",
226 encoding="UTF-8",
227 force_defaults=False
227 force_defaults=False
228 )
228 )
229 return Response(html)
229 return Response(html)
230
230
231 @LoginRequired()
231 @LoginRequired()
232 @HasPermissionAllDecorator('hg.admin')
232 @HasPermissionAllDecorator('hg.admin')
233 @CSRFRequired()
233 @CSRFRequired()
234 def settings_mapping_update(self):
234 def settings_mapping_update(self):
235 _ = self.request.translate
235 _ = self.request.translate
236 c = self.load_default_context()
236 c = self.load_default_context()
237 c.active = 'mapping'
237 c.active = 'mapping'
238 rm_obsolete = self.request.POST.get('destroy', False)
238 rm_obsolete = self.request.POST.get('destroy', False)
239 invalidate_cache = self.request.POST.get('invalidate', False)
239 invalidate_cache = self.request.POST.get('invalidate', False)
240 log.debug('rescanning repo location with destroy obsolete=%s', rm_obsolete)
240 log.debug('rescanning repo location with destroy obsolete=%s', rm_obsolete)
241
241
242 if invalidate_cache:
242 if invalidate_cache:
243 log.debug('invalidating all repositories cache')
243 log.debug('invalidating all repositories cache')
244 for repo in Repository.get_all():
244 for repo in Repository.get_all():
245 ScmModel().mark_for_invalidation(repo.repo_name, delete=True)
245 ScmModel().mark_for_invalidation(repo.repo_name, delete=True)
246
246
247 filesystem_repos = ScmModel().repo_scan()
247 filesystem_repos = ScmModel().repo_scan()
248 added, removed = repo2db_mapper(filesystem_repos, rm_obsolete)
248 added, removed = repo2db_mapper(filesystem_repos, rm_obsolete)
249 PermissionModel().trigger_permission_flush()
249 PermissionModel().trigger_permission_flush()
250
250
251 def _repr(l):
251 def _repr(rm_repo):
252 return ', '.join(map(safe_str, l)) or '-'
252 return ', '.join(map(safe_str, rm_repo)) or '-'
253
253 h.flash(_('Repositories successfully '
254 h.flash(_('Repositories successfully '
254 'rescanned added: %s ; removed: %s') %
255 'rescanned added: %s ; removed: %s') %
255 (_repr(added), _repr(removed)),
256 (_repr(added), _repr(removed)),
256 category='success')
257 category='success')
257 raise HTTPFound(h.route_path('admin_settings_mapping'))
258 raise HTTPFound(h.route_path('admin_settings_mapping'))
258
259
259 @LoginRequired()
260 @LoginRequired()
260 @HasPermissionAllDecorator('hg.admin')
261 @HasPermissionAllDecorator('hg.admin')
261 def settings_global(self):
262 def settings_global(self):
262 c = self.load_default_context()
263 c = self.load_default_context()
263 c.active = 'global'
264 c.active = 'global'
264 c.personal_repo_group_default_pattern = RepoGroupModel()\
265 c.personal_repo_group_default_pattern = RepoGroupModel()\
265 .get_personal_group_name_pattern()
266 .get_personal_group_name_pattern()
266
267
267 data = render('rhodecode:templates/admin/settings/settings.mako',
268 data = render('rhodecode:templates/admin/settings/settings.mako',
268 self._get_template_context(c), self.request)
269 self._get_template_context(c), self.request)
269 html = formencode.htmlfill.render(
270 html = formencode.htmlfill.render(
270 data,
271 data,
271 defaults=self._form_defaults(),
272 defaults=self._form_defaults(),
272 encoding="UTF-8",
273 encoding="UTF-8",
273 force_defaults=False
274 force_defaults=False
274 )
275 )
275 return Response(html)
276 return Response(html)
276
277
277 @LoginRequired()
278 @LoginRequired()
278 @HasPermissionAllDecorator('hg.admin')
279 @HasPermissionAllDecorator('hg.admin')
279 @CSRFRequired()
280 @CSRFRequired()
280 def settings_global_update(self):
281 def settings_global_update(self):
281 _ = self.request.translate
282 _ = self.request.translate
282 c = self.load_default_context()
283 c = self.load_default_context()
283 c.active = 'global'
284 c.active = 'global'
284 c.personal_repo_group_default_pattern = RepoGroupModel()\
285 c.personal_repo_group_default_pattern = RepoGroupModel()\
285 .get_personal_group_name_pattern()
286 .get_personal_group_name_pattern()
286 application_form = ApplicationSettingsForm(self.request.translate)()
287 application_form = ApplicationSettingsForm(self.request.translate)()
287 try:
288 try:
288 form_result = application_form.to_python(dict(self.request.POST))
289 form_result = application_form.to_python(dict(self.request.POST))
289 except formencode.Invalid as errors:
290 except formencode.Invalid as errors:
290 h.flash(
291 h.flash(
291 _("Some form inputs contain invalid data."),
292 _("Some form inputs contain invalid data."),
292 category='error')
293 category='error')
293 data = render('rhodecode:templates/admin/settings/settings.mako',
294 data = render('rhodecode:templates/admin/settings/settings.mako',
294 self._get_template_context(c), self.request)
295 self._get_template_context(c), self.request)
295 html = formencode.htmlfill.render(
296 html = formencode.htmlfill.render(
296 data,
297 data,
297 defaults=errors.value,
298 defaults=errors.value,
298 errors=errors.unpack_errors() or {},
299 errors=errors.unpack_errors() or {},
299 prefix_error=False,
300 prefix_error=False,
300 encoding="UTF-8",
301 encoding="UTF-8",
301 force_defaults=False
302 force_defaults=False
302 )
303 )
303 return Response(html)
304 return Response(html)
304
305
305 settings = [
306 settings = [
306 ('title', 'rhodecode_title', 'unicode'),
307 ('title', 'rhodecode_title', 'unicode'),
307 ('realm', 'rhodecode_realm', 'unicode'),
308 ('realm', 'rhodecode_realm', 'unicode'),
308 ('pre_code', 'rhodecode_pre_code', 'unicode'),
309 ('pre_code', 'rhodecode_pre_code', 'unicode'),
309 ('post_code', 'rhodecode_post_code', 'unicode'),
310 ('post_code', 'rhodecode_post_code', 'unicode'),
310 ('captcha_public_key', 'rhodecode_captcha_public_key', 'unicode'),
311 ('captcha_public_key', 'rhodecode_captcha_public_key', 'unicode'),
311 ('captcha_private_key', 'rhodecode_captcha_private_key', 'unicode'),
312 ('captcha_private_key', 'rhodecode_captcha_private_key', 'unicode'),
312 ('create_personal_repo_group', 'rhodecode_create_personal_repo_group', 'bool'),
313 ('create_personal_repo_group', 'rhodecode_create_personal_repo_group', 'bool'),
313 ('personal_repo_group_pattern', 'rhodecode_personal_repo_group_pattern', 'unicode'),
314 ('personal_repo_group_pattern', 'rhodecode_personal_repo_group_pattern', 'unicode'),
314 ]
315 ]
315
316
316 try:
317 try:
317 for setting, form_key, type_ in settings:
318 for setting, form_key, type_ in settings:
318 sett = SettingsModel().create_or_update_setting(
319 sett = SettingsModel().create_or_update_setting(
319 setting, form_result[form_key], type_)
320 setting, form_result[form_key], type_)
320 Session().add(sett)
321 Session().add(sett)
321
322
322 Session().commit()
323 Session().commit()
323 SettingsModel().invalidate_settings_cache()
324 SettingsModel().invalidate_settings_cache()
324 h.flash(_('Updated application settings'), category='success')
325 h.flash(_('Updated application settings'), category='success')
325 except Exception:
326 except Exception:
326 log.exception("Exception while updating application settings")
327 log.exception("Exception while updating application settings")
327 h.flash(
328 h.flash(
328 _('Error occurred during updating application settings'),
329 _('Error occurred during updating application settings'),
329 category='error')
330 category='error')
330
331
331 raise HTTPFound(h.route_path('admin_settings_global'))
332 raise HTTPFound(h.route_path('admin_settings_global'))
332
333
333 @LoginRequired()
334 @LoginRequired()
334 @HasPermissionAllDecorator('hg.admin')
335 @HasPermissionAllDecorator('hg.admin')
335 def settings_visual(self):
336 def settings_visual(self):
336 c = self.load_default_context()
337 c = self.load_default_context()
337 c.active = 'visual'
338 c.active = 'visual'
338
339
339 data = render('rhodecode:templates/admin/settings/settings.mako',
340 data = render('rhodecode:templates/admin/settings/settings.mako',
340 self._get_template_context(c), self.request)
341 self._get_template_context(c), self.request)
341 html = formencode.htmlfill.render(
342 html = formencode.htmlfill.render(
342 data,
343 data,
343 defaults=self._form_defaults(),
344 defaults=self._form_defaults(),
344 encoding="UTF-8",
345 encoding="UTF-8",
345 force_defaults=False
346 force_defaults=False
346 )
347 )
347 return Response(html)
348 return Response(html)
348
349
349 @LoginRequired()
350 @LoginRequired()
350 @HasPermissionAllDecorator('hg.admin')
351 @HasPermissionAllDecorator('hg.admin')
351 @CSRFRequired()
352 @CSRFRequired()
352 def settings_visual_update(self):
353 def settings_visual_update(self):
353 _ = self.request.translate
354 _ = self.request.translate
354 c = self.load_default_context()
355 c = self.load_default_context()
355 c.active = 'visual'
356 c.active = 'visual'
356 application_form = ApplicationVisualisationForm(self.request.translate)()
357 application_form = ApplicationVisualisationForm(self.request.translate)()
357 try:
358 try:
358 form_result = application_form.to_python(dict(self.request.POST))
359 form_result = application_form.to_python(dict(self.request.POST))
359 except formencode.Invalid as errors:
360 except formencode.Invalid as errors:
360 h.flash(
361 h.flash(
361 _("Some form inputs contain invalid data."),
362 _("Some form inputs contain invalid data."),
362 category='error')
363 category='error')
363 data = render('rhodecode:templates/admin/settings/settings.mako',
364 data = render('rhodecode:templates/admin/settings/settings.mako',
364 self._get_template_context(c), self.request)
365 self._get_template_context(c), self.request)
365 html = formencode.htmlfill.render(
366 html = formencode.htmlfill.render(
366 data,
367 data,
367 defaults=errors.value,
368 defaults=errors.value,
368 errors=errors.unpack_errors() or {},
369 errors=errors.unpack_errors() or {},
369 prefix_error=False,
370 prefix_error=False,
370 encoding="UTF-8",
371 encoding="UTF-8",
371 force_defaults=False
372 force_defaults=False
372 )
373 )
373 return Response(html)
374 return Response(html)
374
375
375 try:
376 try:
376 settings = [
377 settings = [
377 ('show_public_icon', 'rhodecode_show_public_icon', 'bool'),
378 ('show_public_icon', 'rhodecode_show_public_icon', 'bool'),
378 ('show_private_icon', 'rhodecode_show_private_icon', 'bool'),
379 ('show_private_icon', 'rhodecode_show_private_icon', 'bool'),
379 ('stylify_metatags', 'rhodecode_stylify_metatags', 'bool'),
380 ('stylify_metatags', 'rhodecode_stylify_metatags', 'bool'),
380 ('repository_fields', 'rhodecode_repository_fields', 'bool'),
381 ('repository_fields', 'rhodecode_repository_fields', 'bool'),
381 ('dashboard_items', 'rhodecode_dashboard_items', 'int'),
382 ('dashboard_items', 'rhodecode_dashboard_items', 'int'),
382 ('admin_grid_items', 'rhodecode_admin_grid_items', 'int'),
383 ('admin_grid_items', 'rhodecode_admin_grid_items', 'int'),
383 ('show_version', 'rhodecode_show_version', 'bool'),
384 ('show_version', 'rhodecode_show_version', 'bool'),
384 ('use_gravatar', 'rhodecode_use_gravatar', 'bool'),
385 ('use_gravatar', 'rhodecode_use_gravatar', 'bool'),
385 ('markup_renderer', 'rhodecode_markup_renderer', 'unicode'),
386 ('markup_renderer', 'rhodecode_markup_renderer', 'unicode'),
386 ('gravatar_url', 'rhodecode_gravatar_url', 'unicode'),
387 ('gravatar_url', 'rhodecode_gravatar_url', 'unicode'),
387 ('clone_uri_tmpl', 'rhodecode_clone_uri_tmpl', 'unicode'),
388 ('clone_uri_tmpl', 'rhodecode_clone_uri_tmpl', 'unicode'),
388 ('clone_uri_id_tmpl', 'rhodecode_clone_uri_id_tmpl', 'unicode'),
389 ('clone_uri_id_tmpl', 'rhodecode_clone_uri_id_tmpl', 'unicode'),
389 ('clone_uri_ssh_tmpl', 'rhodecode_clone_uri_ssh_tmpl', 'unicode'),
390 ('clone_uri_ssh_tmpl', 'rhodecode_clone_uri_ssh_tmpl', 'unicode'),
390 ('support_url', 'rhodecode_support_url', 'unicode'),
391 ('support_url', 'rhodecode_support_url', 'unicode'),
391 ('show_revision_number', 'rhodecode_show_revision_number', 'bool'),
392 ('show_revision_number', 'rhodecode_show_revision_number', 'bool'),
392 ('show_sha_length', 'rhodecode_show_sha_length', 'int'),
393 ('show_sha_length', 'rhodecode_show_sha_length', 'int'),
393 ]
394 ]
394 for setting, form_key, type_ in settings:
395 for setting, form_key, type_ in settings:
395 sett = SettingsModel().create_or_update_setting(
396 sett = SettingsModel().create_or_update_setting(
396 setting, form_result[form_key], type_)
397 setting, form_result[form_key], type_)
397 Session().add(sett)
398 Session().add(sett)
398
399
399 Session().commit()
400 Session().commit()
400 SettingsModel().invalidate_settings_cache()
401 SettingsModel().invalidate_settings_cache()
401 h.flash(_('Updated visualisation settings'), category='success')
402 h.flash(_('Updated visualisation settings'), category='success')
402 except Exception:
403 except Exception:
403 log.exception("Exception updating visualization settings")
404 log.exception("Exception updating visualization settings")
404 h.flash(_('Error occurred during updating '
405 h.flash(_('Error occurred during updating '
405 'visualisation settings'),
406 'visualisation settings'),
406 category='error')
407 category='error')
407
408
408 raise HTTPFound(h.route_path('admin_settings_visual'))
409 raise HTTPFound(h.route_path('admin_settings_visual'))
409
410
410 @LoginRequired()
411 @LoginRequired()
411 @HasPermissionAllDecorator('hg.admin')
412 @HasPermissionAllDecorator('hg.admin')
412 def settings_issuetracker(self):
413 def settings_issuetracker(self):
413 c = self.load_default_context()
414 c = self.load_default_context()
414 c.active = 'issuetracker'
415 c.active = 'issuetracker'
415 defaults = c.rc_config
416 defaults = c.rc_config
416
417
417 entry_key = 'rhodecode_issuetracker_pat_'
418 entry_key = 'rhodecode_issuetracker_pat_'
418
419
419 c.issuetracker_entries = {}
420 c.issuetracker_entries = {}
420 for k, v in defaults.items():
421 for k, v in defaults.items():
421 if k.startswith(entry_key):
422 if k.startswith(entry_key):
422 uid = k[len(entry_key):]
423 uid = k[len(entry_key):]
423 c.issuetracker_entries[uid] = None
424 c.issuetracker_entries[uid] = None
424
425
425 for uid in c.issuetracker_entries:
426 for uid in c.issuetracker_entries:
426 c.issuetracker_entries[uid] = AttributeDict({
427 c.issuetracker_entries[uid] = AttributeDict({
427 'pat': defaults.get('rhodecode_issuetracker_pat_' + uid),
428 'pat': defaults.get('rhodecode_issuetracker_pat_' + uid),
428 'url': defaults.get('rhodecode_issuetracker_url_' + uid),
429 'url': defaults.get('rhodecode_issuetracker_url_' + uid),
429 'pref': defaults.get('rhodecode_issuetracker_pref_' + uid),
430 'pref': defaults.get('rhodecode_issuetracker_pref_' + uid),
430 'desc': defaults.get('rhodecode_issuetracker_desc_' + uid),
431 'desc': defaults.get('rhodecode_issuetracker_desc_' + uid),
431 })
432 })
432
433
433 return self._get_template_context(c)
434 return self._get_template_context(c)
434
435
435 @LoginRequired()
436 @LoginRequired()
436 @HasPermissionAllDecorator('hg.admin')
437 @HasPermissionAllDecorator('hg.admin')
437 @CSRFRequired()
438 @CSRFRequired()
438 def settings_issuetracker_test(self):
439 def settings_issuetracker_test(self):
439 error_container = []
440 error_container = []
440
441
441 urlified_commit = h.urlify_commit_message(
442 urlified_commit = h.urlify_commit_message(
442 self.request.POST.get('test_text', ''),
443 self.request.POST.get('test_text', ''),
443 'repo_group/test_repo1', error_container=error_container)
444 'repo_group/test_repo1', error_container=error_container)
444 if error_container:
445 if error_container:
445 def converter(inp):
446 def converter(inp):
446 return h.html_escape(inp)
447 return h.html_escape(inp)
447
448
448 return 'ERRORS: ' + '\n'.join(map(converter, error_container))
449 return 'ERRORS: ' + '\n'.join(map(converter, error_container))
449
450
450 return urlified_commit
451 return urlified_commit
451
452
452 @LoginRequired()
453 @LoginRequired()
453 @HasPermissionAllDecorator('hg.admin')
454 @HasPermissionAllDecorator('hg.admin')
454 @CSRFRequired()
455 @CSRFRequired()
455 def settings_issuetracker_update(self):
456 def settings_issuetracker_update(self):
456 _ = self.request.translate
457 _ = self.request.translate
457 self.load_default_context()
458 self.load_default_context()
458 settings_model = IssueTrackerSettingsModel()
459 settings_model = IssueTrackerSettingsModel()
459
460
460 try:
461 try:
461 form = IssueTrackerPatternsForm(self.request.translate)()
462 form = IssueTrackerPatternsForm(self.request.translate)()
462 data = form.to_python(self.request.POST)
463 data = form.to_python(self.request.POST)
463 except formencode.Invalid as errors:
464 except formencode.Invalid as errors:
464 log.exception('Failed to add new pattern')
465 log.exception('Failed to add new pattern')
465 error = errors
466 error = errors
466 h.flash(_(f'Invalid issue tracker pattern: {error}'),
467 h.flash(_(f'Invalid issue tracker pattern: {error}'),
467 category='error')
468 category='error')
468 raise HTTPFound(h.route_path('admin_settings_issuetracker'))
469 raise HTTPFound(h.route_path('admin_settings_issuetracker'))
469
470
470 if data:
471 if data:
471 for uid in data.get('delete_patterns', []):
472 for uid in data.get('delete_patterns', []):
472 settings_model.delete_entries(uid)
473 settings_model.delete_entries(uid)
473
474
474 for pattern in data.get('patterns', []):
475 for pattern in data.get('patterns', []):
475 for setting, value, type_ in pattern:
476 for setting, value, type_ in pattern:
476 sett = settings_model.create_or_update_setting(
477 sett = settings_model.create_or_update_setting(
477 setting, value, type_)
478 setting, value, type_)
478 Session().add(sett)
479 Session().add(sett)
479
480
480 Session().commit()
481 Session().commit()
481
482
482 SettingsModel().invalidate_settings_cache()
483 SettingsModel().invalidate_settings_cache()
483 h.flash(_('Updated issue tracker entries'), category='success')
484 h.flash(_('Updated issue tracker entries'), category='success')
484 raise HTTPFound(h.route_path('admin_settings_issuetracker'))
485 raise HTTPFound(h.route_path('admin_settings_issuetracker'))
485
486
486 @LoginRequired()
487 @LoginRequired()
487 @HasPermissionAllDecorator('hg.admin')
488 @HasPermissionAllDecorator('hg.admin')
488 @CSRFRequired()
489 @CSRFRequired()
489 def settings_issuetracker_delete(self):
490 def settings_issuetracker_delete(self):
490 _ = self.request.translate
491 _ = self.request.translate
491 self.load_default_context()
492 self.load_default_context()
492 uid = self.request.POST.get('uid')
493 uid = self.request.POST.get('uid')
493 try:
494 try:
494 IssueTrackerSettingsModel().delete_entries(uid)
495 IssueTrackerSettingsModel().delete_entries(uid)
495 except Exception:
496 except Exception:
496 log.exception('Failed to delete issue tracker setting %s', uid)
497 log.exception('Failed to delete issue tracker setting %s', uid)
497 raise HTTPNotFound()
498 raise HTTPNotFound()
498
499
499 SettingsModel().invalidate_settings_cache()
500 SettingsModel().invalidate_settings_cache()
500 h.flash(_('Removed issue tracker entry.'), category='success')
501 h.flash(_('Removed issue tracker entry.'), category='success')
501
502
502 return {'deleted': uid}
503 return {'deleted': uid}
503
504
504 @LoginRequired()
505 @LoginRequired()
505 @HasPermissionAllDecorator('hg.admin')
506 @HasPermissionAllDecorator('hg.admin')
506 def settings_email(self):
507 def settings_email(self):
507 c = self.load_default_context()
508 c = self.load_default_context()
508 c.active = 'email'
509 c.active = 'email'
509 c.rhodecode_ini = rhodecode.CONFIG
510 c.rhodecode_ini = rhodecode.CONFIG
510
511
511 data = render('rhodecode:templates/admin/settings/settings.mako',
512 data = render('rhodecode:templates/admin/settings/settings.mako',
512 self._get_template_context(c), self.request)
513 self._get_template_context(c), self.request)
513 html = formencode.htmlfill.render(
514 html = formencode.htmlfill.render(
514 data,
515 data,
515 defaults=self._form_defaults(),
516 defaults=self._form_defaults(),
516 encoding="UTF-8",
517 encoding="UTF-8",
517 force_defaults=False
518 force_defaults=False
518 )
519 )
519 return Response(html)
520 return Response(html)
520
521
521 @LoginRequired()
522 @LoginRequired()
522 @HasPermissionAllDecorator('hg.admin')
523 @HasPermissionAllDecorator('hg.admin')
523 @CSRFRequired()
524 @CSRFRequired()
524 def settings_email_update(self):
525 def settings_email_update(self):
525 _ = self.request.translate
526 _ = self.request.translate
526 c = self.load_default_context()
527 c = self.load_default_context()
527 c.active = 'email'
528 c.active = 'email'
528
529
529 test_email = self.request.POST.get('test_email')
530 test_email = self.request.POST.get('test_email')
530
531
531 if not test_email:
532 if not test_email:
532 h.flash(_('Please enter email address'), category='error')
533 h.flash(_('Please enter email address'), category='error')
533 raise HTTPFound(h.route_path('admin_settings_email'))
534 raise HTTPFound(h.route_path('admin_settings_email'))
534
535
535 email_kwargs = {
536 email_kwargs = {
536 'date': datetime.datetime.now(),
537 'date': datetime.datetime.now(),
537 'user': self._rhodecode_db_user
538 'user': self._rhodecode_db_user
538 }
539 }
539
540
540 (subject, email_body, email_body_plaintext) = EmailNotificationModel().render_email(
541 (subject, email_body, email_body_plaintext) = EmailNotificationModel().render_email(
541 EmailNotificationModel.TYPE_EMAIL_TEST, **email_kwargs)
542 EmailNotificationModel.TYPE_EMAIL_TEST, **email_kwargs)
542
543
543 recipients = [test_email] if test_email else None
544 recipients = [test_email] if test_email else None
544
545
545 run_task(tasks.send_email, recipients, subject,
546 run_task(tasks.send_email, recipients, subject,
546 email_body_plaintext, email_body)
547 email_body_plaintext, email_body)
547
548
548 h.flash(_('Send email task created'), category='success')
549 h.flash(_('Send email task created'), category='success')
549 raise HTTPFound(h.route_path('admin_settings_email'))
550 raise HTTPFound(h.route_path('admin_settings_email'))
550
551
551 @LoginRequired()
552 @LoginRequired()
552 @HasPermissionAllDecorator('hg.admin')
553 @HasPermissionAllDecorator('hg.admin')
553 def settings_hooks(self):
554 def settings_hooks(self):
554 c = self.load_default_context()
555 c = self.load_default_context()
555 c.active = 'hooks'
556 c.active = 'hooks'
556
557
557 model = SettingsModel()
558 model = SettingsModel()
558 c.hooks = model.get_builtin_hooks()
559 c.hooks = model.get_builtin_hooks()
559 c.custom_hooks = model.get_custom_hooks()
560 c.custom_hooks = model.get_custom_hooks()
560
561
561 data = render('rhodecode:templates/admin/settings/settings.mako',
562 data = render('rhodecode:templates/admin/settings/settings.mako',
562 self._get_template_context(c), self.request)
563 self._get_template_context(c), self.request)
563 html = formencode.htmlfill.render(
564 html = formencode.htmlfill.render(
564 data,
565 data,
565 defaults=self._form_defaults(),
566 defaults=self._form_defaults(),
566 encoding="UTF-8",
567 encoding="UTF-8",
567 force_defaults=False
568 force_defaults=False
568 )
569 )
569 return Response(html)
570 return Response(html)
570
571
571 @LoginRequired()
572 @LoginRequired()
572 @HasPermissionAllDecorator('hg.admin')
573 @HasPermissionAllDecorator('hg.admin')
573 @CSRFRequired()
574 @CSRFRequired()
574 def settings_hooks_update(self):
575 def settings_hooks_update(self):
575 _ = self.request.translate
576 _ = self.request.translate
576 c = self.load_default_context()
577 c = self.load_default_context()
577 c.active = 'hooks'
578 c.active = 'hooks'
578 if c.visual.allow_custom_hooks_settings:
579 if c.visual.allow_custom_hooks_settings:
579 ui_key = self.request.POST.get('new_hook_ui_key')
580 ui_key = self.request.POST.get('new_hook_ui_key')
580 ui_value = self.request.POST.get('new_hook_ui_value')
581 ui_value = self.request.POST.get('new_hook_ui_value')
581
582
582 hook_id = self.request.POST.get('hook_id')
583 hook_id = self.request.POST.get('hook_id')
583 new_hook = False
584 new_hook = False
584
585
585 model = SettingsModel()
586 model = SettingsModel()
586 try:
587 try:
587 if ui_value and ui_key:
588 if ui_value and ui_key:
588 model.create_or_update_hook(ui_key, ui_value)
589 model.create_or_update_hook(ui_key, ui_value)
589 h.flash(_('Added new hook'), category='success')
590 h.flash(_('Added new hook'), category='success')
590 new_hook = True
591 new_hook = True
591 elif hook_id:
592 elif hook_id:
592 RhodeCodeUi.delete(hook_id)
593 RhodeCodeUi.delete(hook_id)
593 Session().commit()
594 Session().commit()
594
595
595 # check for edits
596 # check for edits
596 update = False
597 update = False
597 _d = self.request.POST.dict_of_lists()
598 _d = self.request.POST.dict_of_lists()
598 for k, v in zip(_d.get('hook_ui_key', []),
599 for k, v in zip(_d.get('hook_ui_key', []),
599 _d.get('hook_ui_value_new', [])):
600 _d.get('hook_ui_value_new', [])):
600 model.create_or_update_hook(k, v)
601 model.create_or_update_hook(k, v)
601 update = True
602 update = True
602
603
603 if update and not new_hook:
604 if update and not new_hook:
604 h.flash(_('Updated hooks'), category='success')
605 h.flash(_('Updated hooks'), category='success')
605 Session().commit()
606 Session().commit()
606 except Exception:
607 except Exception:
607 log.exception("Exception during hook creation")
608 log.exception("Exception during hook creation")
608 h.flash(_('Error occurred during hook creation'),
609 h.flash(_('Error occurred during hook creation'),
609 category='error')
610 category='error')
610
611
611 raise HTTPFound(h.route_path('admin_settings_hooks'))
612 raise HTTPFound(h.route_path('admin_settings_hooks'))
612
613
613 @LoginRequired()
614 @LoginRequired()
614 @HasPermissionAllDecorator('hg.admin')
615 @HasPermissionAllDecorator('hg.admin')
615 def settings_search(self):
616 def settings_search(self):
616 c = self.load_default_context()
617 c = self.load_default_context()
617 c.active = 'search'
618 c.active = 'search'
618
619
619 c.searcher = searcher_from_config(self.request.registry.settings)
620 c.searcher = searcher_from_config(self.request.registry.settings)
620 c.statistics = c.searcher.statistics(self.request.translate)
621 c.statistics = c.searcher.statistics(self.request.translate)
621
622
622 return self._get_template_context(c)
623 return self._get_template_context(c)
623
624
624 @LoginRequired()
625 @LoginRequired()
625 @HasPermissionAllDecorator('hg.admin')
626 @HasPermissionAllDecorator('hg.admin')
626 def settings_automation(self):
627 c = self.load_default_context()
628 c.active = 'automation'
629
630 return self._get_template_context(c)
631
632 @LoginRequired()
633 @HasPermissionAllDecorator('hg.admin')
634 def settings_labs(self):
627 def settings_labs(self):
635 c = self.load_default_context()
628 c = self.load_default_context()
636 if not c.labs_active:
629 if not c.labs_active:
637 raise HTTPFound(h.route_path('admin_settings'))
630 raise HTTPFound(h.route_path('admin_settings'))
638
631
639 c.active = 'labs'
632 c.active = 'labs'
640 c.lab_settings = _LAB_SETTINGS
633 c.lab_settings = _LAB_SETTINGS
641
634
642 data = render('rhodecode:templates/admin/settings/settings.mako',
635 data = render('rhodecode:templates/admin/settings/settings.mako',
643 self._get_template_context(c), self.request)
636 self._get_template_context(c), self.request)
644 html = formencode.htmlfill.render(
637 html = formencode.htmlfill.render(
645 data,
638 data,
646 defaults=self._form_defaults(),
639 defaults=self._form_defaults(),
647 encoding="UTF-8",
640 encoding="UTF-8",
648 force_defaults=False
641 force_defaults=False
649 )
642 )
650 return Response(html)
643 return Response(html)
651
644
652 @LoginRequired()
645 @LoginRequired()
653 @HasPermissionAllDecorator('hg.admin')
646 @HasPermissionAllDecorator('hg.admin')
654 @CSRFRequired()
647 @CSRFRequired()
655 def settings_labs_update(self):
648 def settings_labs_update(self):
656 _ = self.request.translate
649 _ = self.request.translate
657 c = self.load_default_context()
650 c = self.load_default_context()
658 c.active = 'labs'
651 c.active = 'labs'
659
652
660 application_form = LabsSettingsForm(self.request.translate)()
653 application_form = LabsSettingsForm(self.request.translate)()
661 try:
654 try:
662 form_result = application_form.to_python(dict(self.request.POST))
655 form_result = application_form.to_python(dict(self.request.POST))
663 except formencode.Invalid as errors:
656 except formencode.Invalid as errors:
664 h.flash(
657 h.flash(
665 _("Some form inputs contain invalid data."),
658 _("Some form inputs contain invalid data."),
666 category='error')
659 category='error')
667 data = render('rhodecode:templates/admin/settings/settings.mako',
660 data = render('rhodecode:templates/admin/settings/settings.mako',
668 self._get_template_context(c), self.request)
661 self._get_template_context(c), self.request)
669 html = formencode.htmlfill.render(
662 html = formencode.htmlfill.render(
670 data,
663 data,
671 defaults=errors.value,
664 defaults=errors.value,
672 errors=errors.unpack_errors() or {},
665 errors=errors.unpack_errors() or {},
673 prefix_error=False,
666 prefix_error=False,
674 encoding="UTF-8",
667 encoding="UTF-8",
675 force_defaults=False
668 force_defaults=False
676 )
669 )
677 return Response(html)
670 return Response(html)
678
671
679 try:
672 try:
680 session = Session()
673 session = Session()
681 for setting in _LAB_SETTINGS:
674 for setting in _LAB_SETTINGS:
682 setting_name = setting.key[len('rhodecode_'):]
675 setting_name = setting.key[len('rhodecode_'):]
683 sett = SettingsModel().create_or_update_setting(
676 sett = SettingsModel().create_or_update_setting(
684 setting_name, form_result[setting.key], setting.type)
677 setting_name, form_result[setting.key], setting.type)
685 session.add(sett)
678 session.add(sett)
686
679
687 except Exception:
680 except Exception:
688 log.exception('Exception while updating lab settings')
681 log.exception('Exception while updating lab settings')
689 h.flash(_('Error occurred during updating labs settings'),
682 h.flash(_('Error occurred during updating labs settings'),
690 category='error')
683 category='error')
691 else:
684 else:
692 Session().commit()
685 Session().commit()
693 SettingsModel().invalidate_settings_cache()
686 SettingsModel().invalidate_settings_cache()
694 h.flash(_('Updated Labs settings'), category='success')
687 h.flash(_('Updated Labs settings'), category='success')
695 raise HTTPFound(h.route_path('admin_settings_labs'))
688 raise HTTPFound(h.route_path('admin_settings_labs'))
696
689
697 data = render('rhodecode:templates/admin/settings/settings.mako',
690 data = render('rhodecode:templates/admin/settings/settings.mako',
698 self._get_template_context(c), self.request)
691 self._get_template_context(c), self.request)
699 html = formencode.htmlfill.render(
692 html = formencode.htmlfill.render(
700 data,
693 data,
701 defaults=self._form_defaults(),
694 defaults=self._form_defaults(),
702 encoding="UTF-8",
695 encoding="UTF-8",
703 force_defaults=False
696 force_defaults=False
704 )
697 )
705 return Response(html)
698 return Response(html)
706
699
707
700
708 # :param key: name of the setting including the 'rhodecode_' prefix
701 # :param key: name of the setting including the 'rhodecode_' prefix
709 # :param type: the RhodeCodeSetting type to use.
702 # :param type: the RhodeCodeSetting type to use.
710 # :param group: the i18ned group in which we should dispaly this setting
703 # :param group: the i18ned group in which we should dispaly this setting
711 # :param label: the i18ned label we should display for this setting
704 # :param label: the i18ned label we should display for this setting
712 # :param help: the i18ned help we should dispaly for this setting
705 # :param help: the i18ned help we should dispaly for this setting
713 LabSetting = collections.namedtuple(
706 LabSetting = collections.namedtuple(
714 'LabSetting', ('key', 'type', 'group', 'label', 'help'))
707 'LabSetting', ('key', 'type', 'group', 'label', 'help'))
715
708
716
709
717 # This list has to be kept in sync with the form
710 # This list has to be kept in sync with the form
718 # rhodecode.model.forms.LabsSettingsForm.
711 # rhodecode.model.forms.LabsSettingsForm.
719 _LAB_SETTINGS = [
712 _LAB_SETTINGS = [
720
713
721 ]
714 ]
@@ -1,1582 +1,1583 b''
1 # Copyright (C) 2011-2023 RhodeCode GmbH
1 # Copyright (C) 2011-2023 RhodeCode GmbH
2 #
2 #
3 # This program is free software: you can redistribute it and/or modify
3 # This program is free software: you can redistribute it and/or modify
4 # it under the terms of the GNU Affero General Public License, version 3
4 # it under the terms of the GNU Affero General Public License, version 3
5 # (only), as published by the Free Software Foundation.
5 # (only), as published by the Free Software Foundation.
6 #
6 #
7 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # GNU General Public License for more details.
10 # GNU General Public License for more details.
11 #
11 #
12 # You should have received a copy of the GNU Affero General Public License
12 # You should have received a copy of the GNU Affero General Public License
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 #
14 #
15 # This program is dual-licensed. If you wish to learn more about the
15 # This program is dual-licensed. If you wish to learn more about the
16 # RhodeCode Enterprise Edition, including its added features, Support services,
16 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
18
18
19 import itertools
19 import itertools
20 import logging
20 import logging
21 import os
21 import os
22 import collections
22 import collections
23 import urllib.request
23 import urllib.request
24 import urllib.parse
24 import urllib.parse
25 import urllib.error
25 import urllib.error
26 import pathlib
26 import pathlib
27
27
28 from pyramid.httpexceptions import HTTPNotFound, HTTPBadRequest, HTTPFound
28 from pyramid.httpexceptions import HTTPNotFound, HTTPBadRequest, HTTPFound
29
29
30 from pyramid.renderers import render
30 from pyramid.renderers import render
31 from pyramid.response import Response
31 from pyramid.response import Response
32
32
33 import rhodecode
33 import rhodecode
34 from rhodecode.apps._base import RepoAppView
34 from rhodecode.apps._base import RepoAppView
35
35
36
36
37 from rhodecode.lib import diffs, helpers as h, rc_cache
37 from rhodecode.lib import diffs, helpers as h, rc_cache
38 from rhodecode.lib import audit_logger
38 from rhodecode.lib import audit_logger
39 from rhodecode.lib.hash_utils import sha1_safe
39 from rhodecode.lib.hash_utils import sha1_safe
40 from rhodecode.lib.rc_cache.archive_cache import get_archival_cache_store, get_archival_config, ReentrantLock
40 from rhodecode.lib.rc_cache.archive_cache import get_archival_cache_store, get_archival_config, ReentrantLock
41 from rhodecode.lib.str_utils import safe_bytes, convert_special_chars
41 from rhodecode.lib.str_utils import safe_bytes, convert_special_chars
42 from rhodecode.lib.view_utils import parse_path_ref
42 from rhodecode.lib.view_utils import parse_path_ref
43 from rhodecode.lib.exceptions import NonRelativePathError
43 from rhodecode.lib.exceptions import NonRelativePathError
44 from rhodecode.lib.codeblocks import (
44 from rhodecode.lib.codeblocks import (
45 filenode_as_lines_tokens, filenode_as_annotated_lines_tokens)
45 filenode_as_lines_tokens, filenode_as_annotated_lines_tokens)
46 from rhodecode.lib.utils2 import convert_line_endings, detect_mode
46 from rhodecode.lib.utils2 import convert_line_endings, detect_mode
47 from rhodecode.lib.type_utils import str2bool
47 from rhodecode.lib.type_utils import str2bool
48 from rhodecode.lib.str_utils import safe_str, safe_int
48 from rhodecode.lib.str_utils import safe_str, safe_int
49 from rhodecode.lib.auth import (
49 from rhodecode.lib.auth import (
50 LoginRequired, HasRepoPermissionAnyDecorator, CSRFRequired)
50 LoginRequired, HasRepoPermissionAnyDecorator, CSRFRequired)
51 from rhodecode.lib.vcs import path as vcspath
51 from rhodecode.lib.vcs import path as vcspath
52 from rhodecode.lib.vcs.backends.base import EmptyCommit
52 from rhodecode.lib.vcs.backends.base import EmptyCommit
53 from rhodecode.lib.vcs.conf import settings
53 from rhodecode.lib.vcs.conf import settings
54 from rhodecode.lib.vcs.nodes import FileNode
54 from rhodecode.lib.vcs.nodes import FileNode
55 from rhodecode.lib.vcs.exceptions import (
55 from rhodecode.lib.vcs.exceptions import (
56 RepositoryError, CommitDoesNotExistError, EmptyRepositoryError,
56 RepositoryError, CommitDoesNotExistError, EmptyRepositoryError,
57 ImproperArchiveTypeError, VCSError, NodeAlreadyExistsError,
57 ImproperArchiveTypeError, VCSError, NodeAlreadyExistsError,
58 NodeDoesNotExistError, CommitError, NodeError)
58 NodeDoesNotExistError, CommitError, NodeError)
59
59
60 from rhodecode.model.scm import ScmModel
60 from rhodecode.model.scm import ScmModel
61 from rhodecode.model.db import Repository
61 from rhodecode.model.db import Repository
62
62
63 log = logging.getLogger(__name__)
63 log = logging.getLogger(__name__)
64
64
65
65
66 def get_archive_name(db_repo_id, db_repo_name, commit_sha, ext, subrepos=False, path_sha='', with_hash=True):
66 def get_archive_name(db_repo_id, db_repo_name, commit_sha, ext, subrepos=False, path_sha='', with_hash=True):
67 # original backward compat name of archive
67 # original backward compat name of archive
68 clean_name = safe_str(convert_special_chars(db_repo_name).replace('/', '_'))
68 clean_name = safe_str(convert_special_chars(db_repo_name).replace('/', '_'))
69
69
70 # e.g vcsserver-id-abcd-sub-1-abcfdef-archive-all.zip
70 # e.g vcsserver-id-abcd-sub-1-abcfdef-archive-all.zip
71 # vcsserver-id-abcd-sub-0-abcfdef-COMMIT_SHA-PATH_SHA.zip
71 # vcsserver-id-abcd-sub-0-abcfdef-COMMIT_SHA-PATH_SHA.zip
72 id_sha = sha1_safe(str(db_repo_id))[:4]
72 id_sha = sha1_safe(str(db_repo_id))[:4]
73 sub_repo = 'sub-1' if subrepos else 'sub-0'
73 sub_repo = 'sub-1' if subrepos else 'sub-0'
74 commit = commit_sha if with_hash else 'archive'
74 commit = commit_sha if with_hash else 'archive'
75 path_marker = (path_sha if with_hash else '') or 'all'
75 path_marker = (path_sha if with_hash else '') or 'all'
76 archive_name = f'{clean_name}-id-{id_sha}-{sub_repo}-{commit}-{path_marker}{ext}'
76 archive_name = f'{clean_name}-id-{id_sha}-{sub_repo}-{commit}-{path_marker}{ext}'
77
77
78 return archive_name
78 return archive_name
79
79
80
80
81 def get_path_sha(at_path):
81 def get_path_sha(at_path):
82 return safe_str(sha1_safe(at_path)[:8])
82 return safe_str(sha1_safe(at_path)[:8])
83
83
84
84
85 def _get_archive_spec(fname):
85 def _get_archive_spec(fname):
86 log.debug('Detecting archive spec for: `%s`', fname)
86 log.debug('Detecting archive spec for: `%s`', fname)
87
87
88 fileformat = None
88 fileformat = None
89 ext = None
89 ext = None
90 content_type = None
90 content_type = None
91 for a_type, content_type, extension in settings.ARCHIVE_SPECS:
91 for a_type, content_type, extension in settings.ARCHIVE_SPECS:
92
92
93 if fname.endswith(extension):
93 if fname.endswith(extension):
94 fileformat = a_type
94 fileformat = a_type
95 log.debug('archive is of type: %s', fileformat)
95 log.debug('archive is of type: %s', fileformat)
96 ext = extension
96 ext = extension
97 break
97 break
98
98
99 if not fileformat:
99 if not fileformat:
100 raise ValueError()
100 raise ValueError()
101
101
102 # left over part of whole fname is the commit
102 # left over part of whole fname is the commit
103 commit_id = fname[:-len(ext)]
103 commit_id = fname[:-len(ext)]
104
104
105 return commit_id, ext, fileformat, content_type
105 return commit_id, ext, fileformat, content_type
106
106
107
107
108 class RepoFilesView(RepoAppView):
108 class RepoFilesView(RepoAppView):
109
109
110 @staticmethod
110 @staticmethod
111 def adjust_file_path_for_svn(f_path, repo):
111 def adjust_file_path_for_svn(f_path, repo):
112 """
112 """
113 Computes the relative path of `f_path`.
113 Computes the relative path of `f_path`.
114
114
115 This is mainly based on prefix matching of the recognized tags and
115 This is mainly based on prefix matching of the recognized tags and
116 branches in the underlying repository.
116 branches in the underlying repository.
117 """
117 """
118 tags_and_branches = itertools.chain(
118 tags_and_branches = itertools.chain(
119 repo.branches.keys(),
119 repo.branches.keys(),
120 repo.tags.keys())
120 repo.tags.keys())
121 tags_and_branches = sorted(tags_and_branches, key=len, reverse=True)
121 tags_and_branches = sorted(tags_and_branches, key=len, reverse=True)
122
122
123 for name in tags_and_branches:
123 for name in tags_and_branches:
124 if f_path.startswith(f'{name}/'):
124 if f_path.startswith(f'{name}/'):
125 f_path = vcspath.relpath(f_path, name)
125 f_path = vcspath.relpath(f_path, name)
126 break
126 break
127 return f_path
127 return f_path
128
128
129 def load_default_context(self):
129 def load_default_context(self):
130 c = self._get_local_tmpl_context(include_app_defaults=True)
130 c = self._get_local_tmpl_context(include_app_defaults=True)
131 c.rhodecode_repo = self.rhodecode_vcs_repo
131 c.rhodecode_repo = self.rhodecode_vcs_repo
132 c.enable_downloads = self.db_repo.enable_downloads
132 c.enable_downloads = self.db_repo.enable_downloads
133 return c
133 return c
134
134
135 def _ensure_not_locked(self, commit_id='tip'):
135 def _ensure_not_locked(self, commit_id='tip'):
136 _ = self.request.translate
136 _ = self.request.translate
137
137
138 repo = self.db_repo
138 repo = self.db_repo
139 if repo.enable_locking and repo.locked[0]:
139 if repo.enable_locking and repo.locked[0]:
140 h.flash(_('This repository has been locked by %s on %s')
140 h.flash(_('This repository has been locked by %s on %s')
141 % (h.person_by_id(repo.locked[0]),
141 % (h.person_by_id(repo.locked[0]),
142 h.format_date(h.time_to_datetime(repo.locked[1]))),
142 h.format_date(h.time_to_datetime(repo.locked[1]))),
143 'warning')
143 'warning')
144 files_url = h.route_path(
144 files_url = h.route_path(
145 'repo_files:default_path',
145 'repo_files:default_path',
146 repo_name=self.db_repo_name, commit_id=commit_id)
146 repo_name=self.db_repo_name, commit_id=commit_id)
147 raise HTTPFound(files_url)
147 raise HTTPFound(files_url)
148
148
149 def forbid_non_head(self, is_head, f_path, commit_id='tip', json_mode=False):
149 def forbid_non_head(self, is_head, f_path, commit_id='tip', json_mode=False):
150 _ = self.request.translate
150 _ = self.request.translate
151
151
152 if not is_head:
152 if not is_head:
153 message = _('Cannot modify file. '
153 message = _('Cannot modify file. '
154 'Given commit `{}` is not head of a branch.').format(commit_id)
154 'Given commit `{}` is not head of a branch.').format(commit_id)
155 h.flash(message, category='warning')
155 h.flash(message, category='warning')
156
156
157 if json_mode:
157 if json_mode:
158 return message
158 return message
159
159
160 files_url = h.route_path(
160 files_url = h.route_path(
161 'repo_files', repo_name=self.db_repo_name, commit_id=commit_id,
161 'repo_files', repo_name=self.db_repo_name, commit_id=commit_id,
162 f_path=f_path)
162 f_path=f_path)
163 raise HTTPFound(files_url)
163 raise HTTPFound(files_url)
164
164
165 def check_branch_permission(self, branch_name, commit_id='tip', json_mode=False):
165 def check_branch_permission(self, branch_name, commit_id='tip', json_mode=False):
166 _ = self.request.translate
166 _ = self.request.translate
167
167
168 rule, branch_perm = self._rhodecode_user.get_rule_and_branch_permission(
168 rule, branch_perm = self._rhodecode_user.get_rule_and_branch_permission(
169 self.db_repo_name, branch_name)
169 self.db_repo_name, branch_name)
170 if branch_perm and branch_perm not in ['branch.push', 'branch.push_force']:
170 if branch_perm and branch_perm not in ['branch.push', 'branch.push_force']:
171 message = _('Branch `{}` changes forbidden by rule {}.').format(
171 message = _('Branch `{}` changes forbidden by rule {}.').format(
172 h.escape(branch_name), h.escape(rule))
172 h.escape(branch_name), h.escape(rule))
173 h.flash(message, 'warning')
173 h.flash(message, 'warning')
174
174
175 if json_mode:
175 if json_mode:
176 return message
176 return message
177
177
178 files_url = h.route_path(
178 files_url = h.route_path(
179 'repo_files:default_path', repo_name=self.db_repo_name, commit_id=commit_id)
179 'repo_files:default_path', repo_name=self.db_repo_name, commit_id=commit_id)
180
180
181 raise HTTPFound(files_url)
181 raise HTTPFound(files_url)
182
182
183 def _get_commit_and_path(self):
183 def _get_commit_and_path(self):
184 default_commit_id = self.db_repo.landing_ref_name
184 default_commit_id = self.db_repo.landing_ref_name
185 default_f_path = '/'
185 default_f_path = '/'
186
186
187 commit_id = self.request.matchdict.get(
187 commit_id = self.request.matchdict.get(
188 'commit_id', default_commit_id)
188 'commit_id', default_commit_id)
189 f_path = self._get_f_path(self.request.matchdict, default_f_path)
189 f_path = self._get_f_path(self.request.matchdict, default_f_path)
190 return commit_id, f_path
190 return commit_id, f_path
191
191
192 def _get_default_encoding(self, c):
192 def _get_default_encoding(self, c):
193 enc_list = getattr(c, 'default_encodings', [])
193 enc_list = getattr(c, 'default_encodings', [])
194 return enc_list[0] if enc_list else 'UTF-8'
194 return enc_list[0] if enc_list else 'UTF-8'
195
195
196 def _get_commit_or_redirect(self, commit_id, redirect_after=True):
196 def _get_commit_or_redirect(self, commit_id, redirect_after=True):
197 """
197 """
198 This is a safe way to get commit. If an error occurs it redirects to
198 This is a safe way to get commit. If an error occurs it redirects to
199 tip with proper message
199 tip with proper message
200
200
201 :param commit_id: id of commit to fetch
201 :param commit_id: id of commit to fetch
202 :param redirect_after: toggle redirection
202 :param redirect_after: toggle redirection
203 """
203 """
204 _ = self.request.translate
204 _ = self.request.translate
205
205
206 try:
206 try:
207 return self.rhodecode_vcs_repo.get_commit(commit_id)
207 return self.rhodecode_vcs_repo.get_commit(commit_id)
208 except EmptyRepositoryError:
208 except EmptyRepositoryError:
209 if not redirect_after:
209 if not redirect_after:
210 return None
210 return None
211
211
212 add_new = upload_new = ""
212 add_new = upload_new = ""
213 if h.HasRepoPermissionAny(
213 if h.HasRepoPermissionAny(
214 'repository.write', 'repository.admin')(self.db_repo_name):
214 'repository.write', 'repository.admin')(self.db_repo_name):
215 _url = h.route_path(
215 _url = h.route_path(
216 'repo_files_add_file',
216 'repo_files_add_file',
217 repo_name=self.db_repo_name, commit_id=0, f_path='')
217 repo_name=self.db_repo_name, commit_id=0, f_path='')
218 add_new = h.link_to(
218 add_new = h.link_to(
219 _('add a new file'), _url, class_="alert-link")
219 _('add a new file'), _url, class_="alert-link")
220
220
221 _url_upld = h.route_path(
221 _url_upld = h.route_path(
222 'repo_files_upload_file',
222 'repo_files_upload_file',
223 repo_name=self.db_repo_name, commit_id=0, f_path='')
223 repo_name=self.db_repo_name, commit_id=0, f_path='')
224 upload_new = h.link_to(
224 upload_new = h.link_to(
225 _('upload a new file'), _url_upld, class_="alert-link")
225 _('upload a new file'), _url_upld, class_="alert-link")
226
226
227 h.flash(h.literal(
227 h.flash(h.literal(
228 _('There are no files yet. Click here to %s or %s.') % (add_new, upload_new)), category='warning')
228 _('There are no files yet. Click here to %s or %s.') % (add_new, upload_new)), category='warning')
229 raise HTTPFound(
229 raise HTTPFound(
230 h.route_path('repo_summary', repo_name=self.db_repo_name))
230 h.route_path('repo_summary', repo_name=self.db_repo_name))
231
231
232 except (CommitDoesNotExistError, LookupError) as e:
232 except (CommitDoesNotExistError, LookupError) as e:
233 msg = _('No such commit exists for this repository. Commit: {}').format(commit_id)
233 msg = _('No such commit exists for this repository. Commit: {}').format(commit_id)
234 h.flash(msg, category='error')
234 h.flash(msg, category='error')
235 raise HTTPNotFound()
235 raise HTTPNotFound()
236 except RepositoryError as e:
236 except RepositoryError as e:
237 h.flash(h.escape(safe_str(e)), category='error')
237 h.flash(h.escape(safe_str(e)), category='error')
238 raise HTTPNotFound()
238 raise HTTPNotFound()
239
239
240 def _get_filenode_or_redirect(self, commit_obj, path, pre_load=None):
240 def _get_filenode_or_redirect(self, commit_obj, path, pre_load=None):
241 """
241 """
242 Returns file_node, if error occurs or given path is directory,
242 Returns file_node, if error occurs or given path is directory,
243 it'll redirect to top level path
243 it'll redirect to top level path
244 """
244 """
245 _ = self.request.translate
245 _ = self.request.translate
246
246
247 try:
247 try:
248 file_node = commit_obj.get_node(path, pre_load=pre_load)
248 file_node = commit_obj.get_node(path, pre_load=pre_load)
249 if file_node.is_dir():
249 if file_node.is_dir():
250 raise RepositoryError('The given path is a directory')
250 raise RepositoryError('The given path is a directory')
251 except CommitDoesNotExistError:
251 except CommitDoesNotExistError:
252 log.exception('No such commit exists for this repository')
252 log.exception('No such commit exists for this repository')
253 h.flash(_('No such commit exists for this repository'), category='error')
253 h.flash(_('No such commit exists for this repository'), category='error')
254 raise HTTPNotFound()
254 raise HTTPNotFound()
255 except RepositoryError as e:
255 except RepositoryError as e:
256 log.warning('Repository error while fetching filenode `%s`. Err:%s', path, e)
256 log.warning('Repository error while fetching filenode `%s`. Err:%s', path, e)
257 h.flash(h.escape(safe_str(e)), category='error')
257 h.flash(h.escape(safe_str(e)), category='error')
258 raise HTTPNotFound()
258 raise HTTPNotFound()
259
259
260 return file_node
260 return file_node
261
261
262 def _is_valid_head(self, commit_id, repo, landing_ref):
262 def _is_valid_head(self, commit_id, repo, landing_ref):
263 branch_name = sha_commit_id = ''
263 branch_name = sha_commit_id = ''
264 is_head = False
264 is_head = False
265 log.debug('Checking if commit_id `%s` is a head for %s.', commit_id, repo)
265 log.debug('Checking if commit_id `%s` is a head for %s.', commit_id, repo)
266
266
267 for _branch_name, branch_commit_id in repo.branches.items():
267 for _branch_name, branch_commit_id in repo.branches.items():
268 # simple case we pass in branch name, it's a HEAD
268 # simple case we pass in branch name, it's a HEAD
269 if commit_id == _branch_name:
269 if commit_id == _branch_name:
270 is_head = True
270 is_head = True
271 branch_name = _branch_name
271 branch_name = _branch_name
272 sha_commit_id = branch_commit_id
272 sha_commit_id = branch_commit_id
273 break
273 break
274 # case when we pass in full sha commit_id, which is a head
274 # case when we pass in full sha commit_id, which is a head
275 elif commit_id == branch_commit_id:
275 elif commit_id == branch_commit_id:
276 is_head = True
276 is_head = True
277 branch_name = _branch_name
277 branch_name = _branch_name
278 sha_commit_id = branch_commit_id
278 sha_commit_id = branch_commit_id
279 break
279 break
280
280
281 if h.is_svn(repo) and not repo.is_empty():
281 if h.is_svn(repo) and not repo.is_empty():
282 # Note: Subversion only has one head.
282 # Note: Subversion only has one head.
283 if commit_id == repo.get_commit(commit_idx=-1).raw_id:
283 if commit_id == repo.get_commit(commit_idx=-1).raw_id:
284 is_head = True
284 is_head = True
285 return branch_name, sha_commit_id, is_head
285 return branch_name, sha_commit_id, is_head
286
286
287 # checked branches, means we only need to try to get the branch/commit_sha
287 # checked branches, means we only need to try to get the branch/commit_sha
288 if repo.is_empty():
288 if repo.is_empty():
289 is_head = True
289 is_head = True
290 branch_name = landing_ref
290 branch_name = landing_ref
291 sha_commit_id = EmptyCommit().raw_id
291 sha_commit_id = EmptyCommit().raw_id
292 else:
292 else:
293 commit = repo.get_commit(commit_id=commit_id)
293 commit = repo.get_commit(commit_id=commit_id)
294 if commit:
294 if commit:
295 branch_name = commit.branch
295 branch_name = commit.branch
296 sha_commit_id = commit.raw_id
296 sha_commit_id = commit.raw_id
297
297
298 return branch_name, sha_commit_id, is_head
298 return branch_name, sha_commit_id, is_head
299
299
300 def _get_tree_at_commit(self, c, commit_id, f_path, full_load=False, at_rev=None):
300 def _get_tree_at_commit(self, c, commit_id, f_path, full_load=False, at_rev=None):
301
301
302 repo_id = self.db_repo.repo_id
302 repo_id = self.db_repo.repo_id
303 force_recache = self.get_recache_flag()
303 force_recache = self.get_recache_flag()
304
304
305 cache_seconds = safe_int(
305 cache_seconds = safe_int(
306 rhodecode.CONFIG.get('rc_cache.cache_repo.expiration_time'))
306 rhodecode.CONFIG.get('rc_cache.cache_repo.expiration_time'))
307 cache_on = not force_recache and cache_seconds > 0
307 cache_on = not force_recache and cache_seconds > 0
308 log.debug(
308 log.debug(
309 'Computing FILE TREE for repo_id %s commit_id `%s` and path `%s`'
309 'Computing FILE TREE for repo_id %s commit_id `%s` and path `%s`'
310 'with caching: %s[TTL: %ss]' % (
310 'with caching: %s[TTL: %ss]' % (
311 repo_id, commit_id, f_path, cache_on, cache_seconds or 0))
311 repo_id, commit_id, f_path, cache_on, cache_seconds or 0))
312
312
313 cache_namespace_uid = f'repo.{rc_cache.FILE_TREE_CACHE_VER}.{repo_id}'
313 cache_namespace_uid = f'repo.{rc_cache.FILE_TREE_CACHE_VER}.{repo_id}'
314 region = rc_cache.get_or_create_region('cache_repo', cache_namespace_uid)
314 region = rc_cache.get_or_create_region('cache_repo', cache_namespace_uid)
315
315
316 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid, condition=cache_on)
316 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid, condition=cache_on)
317 def compute_file_tree(_name_hash, _repo_id, _commit_id, _f_path, _full_load, _at_rev):
317 def compute_file_tree(_name_hash, _repo_id, _commit_id, _f_path, _full_load, _at_rev):
318 log.debug('Generating cached file tree at for repo_id: %s, %s, %s',
318 log.debug('Generating cached file tree at for repo_id: %s, %s, %s',
319 _repo_id, _commit_id, _f_path)
319 _repo_id, _commit_id, _f_path)
320
320
321 c.full_load = _full_load
321 c.full_load = _full_load
322 return render(
322 return render(
323 'rhodecode:templates/files/files_browser_tree.mako',
323 'rhodecode:templates/files/files_browser_tree.mako',
324 self._get_template_context(c), self.request, _at_rev)
324 self._get_template_context(c), self.request, _at_rev)
325
325
326 return compute_file_tree(
326 return compute_file_tree(
327 self.db_repo.repo_name_hash, self.db_repo.repo_id, commit_id, f_path, full_load, at_rev)
327 self.db_repo.repo_name_hash, self.db_repo.repo_id, commit_id, f_path, full_load, at_rev)
328
328
329 def create_pure_path(self, *parts):
329 def create_pure_path(self, *parts):
330 # Split paths and sanitize them, removing any ../ etc
330 # Split paths and sanitize them, removing any ../ etc
331 sanitized_path = [
331 sanitized_path = [
332 x for x in pathlib.PurePath(*parts).parts
332 x for x in pathlib.PurePath(*parts).parts
333 if x not in ['.', '..']]
333 if x not in ['.', '..']]
334
334
335 pure_path = pathlib.PurePath(*sanitized_path)
335 pure_path = pathlib.PurePath(*sanitized_path)
336 return pure_path
336 return pure_path
337
337
338 def _is_lf_enabled(self, target_repo):
338 def _is_lf_enabled(self, target_repo):
339 lf_enabled = False
339 lf_enabled = False
340
340
341 lf_key_for_vcs_map = {
341 lf_key_for_vcs_map = {
342 'hg': 'extensions_largefiles',
342 'hg': 'extensions_largefiles',
343 'git': 'vcs_git_lfs_enabled'
343 'git': 'vcs_git_lfs_enabled'
344 }
344 }
345
345
346 lf_key_for_vcs = lf_key_for_vcs_map.get(target_repo.repo_type)
346 lf_key_for_vcs = lf_key_for_vcs_map.get(target_repo.repo_type)
347
347
348 if lf_key_for_vcs:
348 if lf_key_for_vcs:
349 lf_enabled = self._get_repo_setting(target_repo, lf_key_for_vcs)
349 lf_enabled = self._get_repo_setting(target_repo, lf_key_for_vcs)
350
350
351 return lf_enabled
351 return lf_enabled
352
352
353 @LoginRequired()
353 @LoginRequired()
354 @HasRepoPermissionAnyDecorator(
354 @HasRepoPermissionAnyDecorator(
355 'repository.read', 'repository.write', 'repository.admin')
355 'repository.read', 'repository.write', 'repository.admin')
356 def repo_archivefile(self):
356 def repo_archivefile(self):
357 # archive cache config
357 # archive cache config
358 from rhodecode import CONFIG
358 from rhodecode import CONFIG
359 _ = self.request.translate
359 _ = self.request.translate
360 self.load_default_context()
360 self.load_default_context()
361 default_at_path = '/'
361 default_at_path = '/'
362 fname = self.request.matchdict['fname']
362 fname = self.request.matchdict['fname']
363 subrepos = self.request.GET.get('subrepos') == 'true'
363 subrepos = self.request.GET.get('subrepos') == 'true'
364 with_hash = str2bool(self.request.GET.get('with_hash', '1'))
364 with_hash = str2bool(self.request.GET.get('with_hash', '1'))
365 at_path = self.request.GET.get('at_path') or default_at_path
365 at_path = self.request.GET.get('at_path') or default_at_path
366
366
367 if not self.db_repo.enable_downloads:
367 if not self.db_repo.enable_downloads:
368 return Response(_('Downloads disabled'))
368 return Response(_('Downloads disabled'))
369
369
370 try:
370 try:
371 commit_id, ext, fileformat, content_type = \
371 commit_id, ext, fileformat, content_type = \
372 _get_archive_spec(fname)
372 _get_archive_spec(fname)
373 except ValueError:
373 except ValueError:
374 return Response(_('Unknown archive type for: `{}`').format(
374 return Response(_('Unknown archive type for: `{}`').format(
375 h.escape(fname)))
375 h.escape(fname)))
376
376
377 try:
377 try:
378 commit = self.rhodecode_vcs_repo.get_commit(commit_id)
378 commit = self.rhodecode_vcs_repo.get_commit(commit_id)
379 except CommitDoesNotExistError:
379 except CommitDoesNotExistError:
380 return Response(_('Unknown commit_id {}').format(
380 return Response(_('Unknown commit_id {}').format(
381 h.escape(commit_id)))
381 h.escape(commit_id)))
382 except EmptyRepositoryError:
382 except EmptyRepositoryError:
383 return Response(_('Empty repository'))
383 return Response(_('Empty repository'))
384
384
385 # we used a ref, or a shorter version, lets redirect client ot use explicit hash
385 # we used a ref, or a shorter version, lets redirect client ot use explicit hash
386 if commit_id != commit.raw_id:
386 if commit_id != commit.raw_id:
387 fname=f'{commit.raw_id}{ext}'
387 fname=f'{commit.raw_id}{ext}'
388 raise HTTPFound(self.request.current_route_path(fname=fname))
388 raise HTTPFound(self.request.current_route_path(fname=fname))
389
389
390 try:
390 try:
391 at_path = commit.get_node(at_path).path or default_at_path
391 at_path = commit.get_node(at_path).path or default_at_path
392 except Exception:
392 except Exception:
393 return Response(_('No node at path {} for this repository').format(h.escape(at_path)))
393 return Response(_('No node at path {} for this repository').format(h.escape(at_path)))
394
394
395 path_sha = get_path_sha(at_path)
395 path_sha = get_path_sha(at_path)
396
396
397 # used for cache etc, consistent unique archive name
397 # used for cache etc, consistent unique archive name
398 archive_name_key = get_archive_name(
398 archive_name_key = get_archive_name(
399 self.db_repo.repo_id, self.db_repo_name, commit_sha=commit.short_id, ext=ext, subrepos=subrepos,
399 self.db_repo.repo_id, self.db_repo_name, commit_sha=commit.short_id, ext=ext, subrepos=subrepos,
400 path_sha=path_sha, with_hash=True)
400 path_sha=path_sha, with_hash=True)
401
401
402 if not with_hash:
402 if not with_hash:
403 path_sha = ''
403 path_sha = ''
404
404
405 # what end client gets served
405 # what end client gets served
406 response_archive_name = get_archive_name(
406 response_archive_name = get_archive_name(
407 self.db_repo.repo_id, self.db_repo_name, commit_sha=commit.short_id, ext=ext, subrepos=subrepos,
407 self.db_repo.repo_id, self.db_repo_name, commit_sha=commit.short_id, ext=ext, subrepos=subrepos,
408 path_sha=path_sha, with_hash=with_hash)
408 path_sha=path_sha, with_hash=with_hash)
409
409
410 # remove extension from our archive directory name
410 # remove extension from our archive directory name
411 archive_dir_name = response_archive_name[:-len(ext)]
411 archive_dir_name = response_archive_name[:-len(ext)]
412
412
413 archive_cache_disable = self.request.GET.get('no_cache')
413 archive_cache_disable = self.request.GET.get('no_cache')
414
414
415 d_cache = get_archival_cache_store(config=CONFIG)
415 d_cache = get_archival_cache_store(config=CONFIG)
416
416 # NOTE: we get the config to pass to a call to lazy-init the SAME type of cache on vcsserver
417 # NOTE: we get the config to pass to a call to lazy-init the SAME type of cache on vcsserver
417 d_cache_conf = get_archival_config(config=CONFIG)
418 d_cache_conf = get_archival_config(config=CONFIG)
418
419
419 reentrant_lock_key = archive_name_key + '.lock'
420 reentrant_lock_key = archive_name_key + '.lock'
420 with ReentrantLock(d_cache, reentrant_lock_key):
421 with ReentrantLock(d_cache, reentrant_lock_key):
421 # This is also a cache key
422 # This is also a cache key
422 use_cached_archive = False
423 use_cached_archive = False
423 if archive_name_key in d_cache and not archive_cache_disable:
424 if archive_name_key in d_cache and not archive_cache_disable:
424 reader, tag = d_cache.get(archive_name_key, read=True, tag=True, retry=True)
425 reader, tag = d_cache.get(archive_name_key, read=True, tag=True, retry=True)
425 use_cached_archive = True
426 use_cached_archive = True
426 log.debug('Found cached archive as key=%s tag=%s, serving archive from cache reader=%s',
427 log.debug('Found cached archive as key=%s tag=%s, serving archive from cache reader=%s',
427 archive_name_key, tag, reader.name)
428 archive_name_key, tag, reader.name)
428 else:
429 else:
429 reader = None
430 reader = None
430 log.debug('Archive with key=%s is not yet cached, creating one now...', archive_name_key)
431 log.debug('Archive with key=%s is not yet cached, creating one now...', archive_name_key)
431
432
432 # generate new archive, as previous was not found in the cache
433 # generate new archive, as previous was not found in the cache
433 if not reader:
434 if not reader:
434
435
435 try:
436 try:
436 commit.archive_repo(archive_name_key, archive_dir_name=archive_dir_name,
437 commit.archive_repo(archive_name_key, archive_dir_name=archive_dir_name,
437 kind=fileformat, subrepos=subrepos,
438 kind=fileformat, subrepos=subrepos,
438 archive_at_path=at_path, cache_config=d_cache_conf)
439 archive_at_path=at_path, cache_config=d_cache_conf)
439 except ImproperArchiveTypeError:
440 except ImproperArchiveTypeError:
440 return _('Unknown archive type')
441 return _('Unknown archive type')
441
442
442 reader, tag = d_cache.get(archive_name_key, read=True, tag=True, retry=True)
443 reader, tag = d_cache.get(archive_name_key, read=True, tag=True, retry=True)
443
444
444 if not reader:
445 if not reader:
445 raise ValueError('archive cache reader is empty, failed to fetch file from distributed archive cache')
446 raise ValueError('archive cache reader is empty, failed to fetch file from distributed archive cache')
446
447
447 def archive_iterator(_reader, block_size: int = 4096*512):
448 def archive_iterator(_reader, block_size: int = 4096*512):
448 # 4096 * 64 = 64KB
449 # 4096 * 64 = 64KB
449 while 1:
450 while 1:
450 data = _reader.read(block_size)
451 data = _reader.read(block_size)
451 if not data:
452 if not data:
452 break
453 break
453 yield data
454 yield data
454
455
455 response = Response(app_iter=archive_iterator(reader))
456 response = Response(app_iter=archive_iterator(reader))
456 response.content_disposition = f'attachment; filename={response_archive_name}'
457 response.content_disposition = f'attachment; filename={response_archive_name}'
457 response.content_type = str(content_type)
458 response.content_type = str(content_type)
458
459
459 try:
460 try:
460 return response
461 return response
461 finally:
462 finally:
462 # store download action
463 # store download action
463 audit_logger.store_web(
464 audit_logger.store_web(
464 'repo.archive.download', action_data={
465 'repo.archive.download', action_data={
465 'user_agent': self.request.user_agent,
466 'user_agent': self.request.user_agent,
466 'archive_name': archive_name_key,
467 'archive_name': archive_name_key,
467 'archive_spec': fname,
468 'archive_spec': fname,
468 'archive_cached': use_cached_archive},
469 'archive_cached': use_cached_archive},
469 user=self._rhodecode_user,
470 user=self._rhodecode_user,
470 repo=self.db_repo,
471 repo=self.db_repo,
471 commit=True
472 commit=True
472 )
473 )
473
474
474 def _get_file_node(self, commit_id, f_path):
475 def _get_file_node(self, commit_id, f_path):
475 if commit_id not in ['', None, 'None', '0' * 12, '0' * 40]:
476 if commit_id not in ['', None, 'None', '0' * 12, '0' * 40]:
476 commit = self.rhodecode_vcs_repo.get_commit(commit_id=commit_id)
477 commit = self.rhodecode_vcs_repo.get_commit(commit_id=commit_id)
477 try:
478 try:
478 node = commit.get_node(f_path)
479 node = commit.get_node(f_path)
479 if node.is_dir():
480 if node.is_dir():
480 raise NodeError(f'{node} path is a {type(node)} not a file')
481 raise NodeError(f'{node} path is a {type(node)} not a file')
481 except NodeDoesNotExistError:
482 except NodeDoesNotExistError:
482 commit = EmptyCommit(
483 commit = EmptyCommit(
483 commit_id=commit_id,
484 commit_id=commit_id,
484 idx=commit.idx,
485 idx=commit.idx,
485 repo=commit.repository,
486 repo=commit.repository,
486 alias=commit.repository.alias,
487 alias=commit.repository.alias,
487 message=commit.message,
488 message=commit.message,
488 author=commit.author,
489 author=commit.author,
489 date=commit.date)
490 date=commit.date)
490 node = FileNode(safe_bytes(f_path), b'', commit=commit)
491 node = FileNode(safe_bytes(f_path), b'', commit=commit)
491 else:
492 else:
492 commit = EmptyCommit(
493 commit = EmptyCommit(
493 repo=self.rhodecode_vcs_repo,
494 repo=self.rhodecode_vcs_repo,
494 alias=self.rhodecode_vcs_repo.alias)
495 alias=self.rhodecode_vcs_repo.alias)
495 node = FileNode(safe_bytes(f_path), b'', commit=commit)
496 node = FileNode(safe_bytes(f_path), b'', commit=commit)
496 return node
497 return node
497
498
498 @LoginRequired()
499 @LoginRequired()
499 @HasRepoPermissionAnyDecorator(
500 @HasRepoPermissionAnyDecorator(
500 'repository.read', 'repository.write', 'repository.admin')
501 'repository.read', 'repository.write', 'repository.admin')
501 def repo_files_diff(self):
502 def repo_files_diff(self):
502 c = self.load_default_context()
503 c = self.load_default_context()
503 f_path = self._get_f_path(self.request.matchdict)
504 f_path = self._get_f_path(self.request.matchdict)
504 diff1 = self.request.GET.get('diff1', '')
505 diff1 = self.request.GET.get('diff1', '')
505 diff2 = self.request.GET.get('diff2', '')
506 diff2 = self.request.GET.get('diff2', '')
506
507
507 path1, diff1 = parse_path_ref(diff1, default_path=f_path)
508 path1, diff1 = parse_path_ref(diff1, default_path=f_path)
508
509
509 ignore_whitespace = str2bool(self.request.GET.get('ignorews'))
510 ignore_whitespace = str2bool(self.request.GET.get('ignorews'))
510 line_context = self.request.GET.get('context', 3)
511 line_context = self.request.GET.get('context', 3)
511
512
512 if not any((diff1, diff2)):
513 if not any((diff1, diff2)):
513 h.flash(
514 h.flash(
514 'Need query parameter "diff1" or "diff2" to generate a diff.',
515 'Need query parameter "diff1" or "diff2" to generate a diff.',
515 category='error')
516 category='error')
516 raise HTTPBadRequest()
517 raise HTTPBadRequest()
517
518
518 c.action = self.request.GET.get('diff')
519 c.action = self.request.GET.get('diff')
519 if c.action not in ['download', 'raw']:
520 if c.action not in ['download', 'raw']:
520 compare_url = h.route_path(
521 compare_url = h.route_path(
521 'repo_compare',
522 'repo_compare',
522 repo_name=self.db_repo_name,
523 repo_name=self.db_repo_name,
523 source_ref_type='rev',
524 source_ref_type='rev',
524 source_ref=diff1,
525 source_ref=diff1,
525 target_repo=self.db_repo_name,
526 target_repo=self.db_repo_name,
526 target_ref_type='rev',
527 target_ref_type='rev',
527 target_ref=diff2,
528 target_ref=diff2,
528 _query=dict(f_path=f_path))
529 _query=dict(f_path=f_path))
529 # redirect to new view if we render diff
530 # redirect to new view if we render diff
530 raise HTTPFound(compare_url)
531 raise HTTPFound(compare_url)
531
532
532 try:
533 try:
533 node1 = self._get_file_node(diff1, path1)
534 node1 = self._get_file_node(diff1, path1)
534 node2 = self._get_file_node(diff2, f_path)
535 node2 = self._get_file_node(diff2, f_path)
535 except (RepositoryError, NodeError):
536 except (RepositoryError, NodeError):
536 log.exception("Exception while trying to get node from repository")
537 log.exception("Exception while trying to get node from repository")
537 raise HTTPFound(
538 raise HTTPFound(
538 h.route_path('repo_files', repo_name=self.db_repo_name,
539 h.route_path('repo_files', repo_name=self.db_repo_name,
539 commit_id='tip', f_path=f_path))
540 commit_id='tip', f_path=f_path))
540
541
541 if all(isinstance(node.commit, EmptyCommit)
542 if all(isinstance(node.commit, EmptyCommit)
542 for node in (node1, node2)):
543 for node in (node1, node2)):
543 raise HTTPNotFound()
544 raise HTTPNotFound()
544
545
545 c.commit_1 = node1.commit
546 c.commit_1 = node1.commit
546 c.commit_2 = node2.commit
547 c.commit_2 = node2.commit
547
548
548 if c.action == 'download':
549 if c.action == 'download':
549 _diff = diffs.get_gitdiff(node1, node2,
550 _diff = diffs.get_gitdiff(node1, node2,
550 ignore_whitespace=ignore_whitespace,
551 ignore_whitespace=ignore_whitespace,
551 context=line_context)
552 context=line_context)
552 # NOTE: this was using diff_format='gitdiff'
553 # NOTE: this was using diff_format='gitdiff'
553 diff = diffs.DiffProcessor(_diff, diff_format='newdiff')
554 diff = diffs.DiffProcessor(_diff, diff_format='newdiff')
554
555
555 response = Response(self.path_filter.get_raw_patch(diff))
556 response = Response(self.path_filter.get_raw_patch(diff))
556 response.content_type = 'text/plain'
557 response.content_type = 'text/plain'
557 response.content_disposition = (
558 response.content_disposition = (
558 f'attachment; filename={f_path}_{diff1}_vs_{diff2}.diff'
559 f'attachment; filename={f_path}_{diff1}_vs_{diff2}.diff'
559 )
560 )
560 charset = self._get_default_encoding(c)
561 charset = self._get_default_encoding(c)
561 if charset:
562 if charset:
562 response.charset = charset
563 response.charset = charset
563 return response
564 return response
564
565
565 elif c.action == 'raw':
566 elif c.action == 'raw':
566 _diff = diffs.get_gitdiff(node1, node2,
567 _diff = diffs.get_gitdiff(node1, node2,
567 ignore_whitespace=ignore_whitespace,
568 ignore_whitespace=ignore_whitespace,
568 context=line_context)
569 context=line_context)
569 # NOTE: this was using diff_format='gitdiff'
570 # NOTE: this was using diff_format='gitdiff'
570 diff = diffs.DiffProcessor(_diff, diff_format='newdiff')
571 diff = diffs.DiffProcessor(_diff, diff_format='newdiff')
571
572
572 response = Response(self.path_filter.get_raw_patch(diff))
573 response = Response(self.path_filter.get_raw_patch(diff))
573 response.content_type = 'text/plain'
574 response.content_type = 'text/plain'
574 charset = self._get_default_encoding(c)
575 charset = self._get_default_encoding(c)
575 if charset:
576 if charset:
576 response.charset = charset
577 response.charset = charset
577 return response
578 return response
578
579
579 # in case we ever end up here
580 # in case we ever end up here
580 raise HTTPNotFound()
581 raise HTTPNotFound()
581
582
582 @LoginRequired()
583 @LoginRequired()
583 @HasRepoPermissionAnyDecorator(
584 @HasRepoPermissionAnyDecorator(
584 'repository.read', 'repository.write', 'repository.admin')
585 'repository.read', 'repository.write', 'repository.admin')
585 def repo_files_diff_2way_redirect(self):
586 def repo_files_diff_2way_redirect(self):
586 """
587 """
587 Kept only to make OLD links work
588 Kept only to make OLD links work
588 """
589 """
589 f_path = self._get_f_path_unchecked(self.request.matchdict)
590 f_path = self._get_f_path_unchecked(self.request.matchdict)
590 diff1 = self.request.GET.get('diff1', '')
591 diff1 = self.request.GET.get('diff1', '')
591 diff2 = self.request.GET.get('diff2', '')
592 diff2 = self.request.GET.get('diff2', '')
592
593
593 if not any((diff1, diff2)):
594 if not any((diff1, diff2)):
594 h.flash(
595 h.flash(
595 'Need query parameter "diff1" or "diff2" to generate a diff.',
596 'Need query parameter "diff1" or "diff2" to generate a diff.',
596 category='error')
597 category='error')
597 raise HTTPBadRequest()
598 raise HTTPBadRequest()
598
599
599 compare_url = h.route_path(
600 compare_url = h.route_path(
600 'repo_compare',
601 'repo_compare',
601 repo_name=self.db_repo_name,
602 repo_name=self.db_repo_name,
602 source_ref_type='rev',
603 source_ref_type='rev',
603 source_ref=diff1,
604 source_ref=diff1,
604 target_ref_type='rev',
605 target_ref_type='rev',
605 target_ref=diff2,
606 target_ref=diff2,
606 _query=dict(f_path=f_path, diffmode='sideside',
607 _query=dict(f_path=f_path, diffmode='sideside',
607 target_repo=self.db_repo_name,))
608 target_repo=self.db_repo_name,))
608 raise HTTPFound(compare_url)
609 raise HTTPFound(compare_url)
609
610
610 @LoginRequired()
611 @LoginRequired()
611 def repo_files_default_commit_redirect(self):
612 def repo_files_default_commit_redirect(self):
612 """
613 """
613 Special page that redirects to the landing page of files based on the default
614 Special page that redirects to the landing page of files based on the default
614 commit for repository
615 commit for repository
615 """
616 """
616 c = self.load_default_context()
617 c = self.load_default_context()
617 ref_name = c.rhodecode_db_repo.landing_ref_name
618 ref_name = c.rhodecode_db_repo.landing_ref_name
618 landing_url = h.repo_files_by_ref_url(
619 landing_url = h.repo_files_by_ref_url(
619 c.rhodecode_db_repo.repo_name,
620 c.rhodecode_db_repo.repo_name,
620 c.rhodecode_db_repo.repo_type,
621 c.rhodecode_db_repo.repo_type,
621 f_path='',
622 f_path='',
622 ref_name=ref_name,
623 ref_name=ref_name,
623 commit_id='tip',
624 commit_id='tip',
624 query=dict(at=ref_name)
625 query=dict(at=ref_name)
625 )
626 )
626
627
627 raise HTTPFound(landing_url)
628 raise HTTPFound(landing_url)
628
629
629 @LoginRequired()
630 @LoginRequired()
630 @HasRepoPermissionAnyDecorator(
631 @HasRepoPermissionAnyDecorator(
631 'repository.read', 'repository.write', 'repository.admin')
632 'repository.read', 'repository.write', 'repository.admin')
632 def repo_files(self):
633 def repo_files(self):
633 c = self.load_default_context()
634 c = self.load_default_context()
634
635
635 view_name = getattr(self.request.matched_route, 'name', None)
636 view_name = getattr(self.request.matched_route, 'name', None)
636
637
637 c.annotate = view_name == 'repo_files:annotated'
638 c.annotate = view_name == 'repo_files:annotated'
638 # default is false, but .rst/.md files later are auto rendered, we can
639 # default is false, but .rst/.md files later are auto rendered, we can
639 # overwrite auto rendering by setting this GET flag
640 # overwrite auto rendering by setting this GET flag
640 c.renderer = view_name == 'repo_files:rendered' or not self.request.GET.get('no-render', False)
641 c.renderer = view_name == 'repo_files:rendered' or not self.request.GET.get('no-render', False)
641
642
642 commit_id, f_path = self._get_commit_and_path()
643 commit_id, f_path = self._get_commit_and_path()
643
644
644 c.commit = self._get_commit_or_redirect(commit_id)
645 c.commit = self._get_commit_or_redirect(commit_id)
645 c.branch = self.request.GET.get('branch', None)
646 c.branch = self.request.GET.get('branch', None)
646 c.f_path = f_path
647 c.f_path = f_path
647 at_rev = self.request.GET.get('at')
648 at_rev = self.request.GET.get('at')
648
649
649 # prev link
650 # prev link
650 try:
651 try:
651 prev_commit = c.commit.prev(c.branch)
652 prev_commit = c.commit.prev(c.branch)
652 c.prev_commit = prev_commit
653 c.prev_commit = prev_commit
653 c.url_prev = h.route_path(
654 c.url_prev = h.route_path(
654 'repo_files', repo_name=self.db_repo_name,
655 'repo_files', repo_name=self.db_repo_name,
655 commit_id=prev_commit.raw_id, f_path=f_path)
656 commit_id=prev_commit.raw_id, f_path=f_path)
656 if c.branch:
657 if c.branch:
657 c.url_prev += '?branch=%s' % c.branch
658 c.url_prev += '?branch=%s' % c.branch
658 except (CommitDoesNotExistError, VCSError):
659 except (CommitDoesNotExistError, VCSError):
659 c.url_prev = '#'
660 c.url_prev = '#'
660 c.prev_commit = EmptyCommit()
661 c.prev_commit = EmptyCommit()
661
662
662 # next link
663 # next link
663 try:
664 try:
664 next_commit = c.commit.next(c.branch)
665 next_commit = c.commit.next(c.branch)
665 c.next_commit = next_commit
666 c.next_commit = next_commit
666 c.url_next = h.route_path(
667 c.url_next = h.route_path(
667 'repo_files', repo_name=self.db_repo_name,
668 'repo_files', repo_name=self.db_repo_name,
668 commit_id=next_commit.raw_id, f_path=f_path)
669 commit_id=next_commit.raw_id, f_path=f_path)
669 if c.branch:
670 if c.branch:
670 c.url_next += '?branch=%s' % c.branch
671 c.url_next += '?branch=%s' % c.branch
671 except (CommitDoesNotExistError, VCSError):
672 except (CommitDoesNotExistError, VCSError):
672 c.url_next = '#'
673 c.url_next = '#'
673 c.next_commit = EmptyCommit()
674 c.next_commit = EmptyCommit()
674
675
675 # files or dirs
676 # files or dirs
676 try:
677 try:
677 c.file = c.commit.get_node(f_path, pre_load=['is_binary', 'size', 'data'])
678 c.file = c.commit.get_node(f_path, pre_load=['is_binary', 'size', 'data'])
678
679
679 c.file_author = True
680 c.file_author = True
680 c.file_tree = ''
681 c.file_tree = ''
681
682
682 # load file content
683 # load file content
683 if c.file.is_file():
684 if c.file.is_file():
684 c.lf_node = {}
685 c.lf_node = {}
685
686
686 has_lf_enabled = self._is_lf_enabled(self.db_repo)
687 has_lf_enabled = self._is_lf_enabled(self.db_repo)
687 if has_lf_enabled:
688 if has_lf_enabled:
688 c.lf_node = c.file.get_largefile_node()
689 c.lf_node = c.file.get_largefile_node()
689
690
690 c.file_source_page = 'true'
691 c.file_source_page = 'true'
691 c.file_last_commit = c.file.last_commit
692 c.file_last_commit = c.file.last_commit
692
693
693 c.file_size_too_big = c.file.size > c.visual.cut_off_limit_file
694 c.file_size_too_big = c.file.size > c.visual.cut_off_limit_file
694
695
695 if not (c.file_size_too_big or c.file.is_binary):
696 if not (c.file_size_too_big or c.file.is_binary):
696 if c.annotate: # annotation has precedence over renderer
697 if c.annotate: # annotation has precedence over renderer
697 c.annotated_lines = filenode_as_annotated_lines_tokens(
698 c.annotated_lines = filenode_as_annotated_lines_tokens(
698 c.file
699 c.file
699 )
700 )
700 else:
701 else:
701 c.renderer = (
702 c.renderer = (
702 c.renderer and h.renderer_from_filename(c.file.path)
703 c.renderer and h.renderer_from_filename(c.file.path)
703 )
704 )
704 if not c.renderer:
705 if not c.renderer:
705 c.lines = filenode_as_lines_tokens(c.file)
706 c.lines = filenode_as_lines_tokens(c.file)
706
707
707 _branch_name, _sha_commit_id, is_head = \
708 _branch_name, _sha_commit_id, is_head = \
708 self._is_valid_head(commit_id, self.rhodecode_vcs_repo,
709 self._is_valid_head(commit_id, self.rhodecode_vcs_repo,
709 landing_ref=self.db_repo.landing_ref_name)
710 landing_ref=self.db_repo.landing_ref_name)
710 c.on_branch_head = is_head
711 c.on_branch_head = is_head
711
712
712 branch = c.commit.branch if (
713 branch = c.commit.branch if (
713 c.commit.branch and '/' not in c.commit.branch) else None
714 c.commit.branch and '/' not in c.commit.branch) else None
714 c.branch_or_raw_id = branch or c.commit.raw_id
715 c.branch_or_raw_id = branch or c.commit.raw_id
715 c.branch_name = c.commit.branch or h.short_id(c.commit.raw_id)
716 c.branch_name = c.commit.branch or h.short_id(c.commit.raw_id)
716
717
717 author = c.file_last_commit.author
718 author = c.file_last_commit.author
718 c.authors = [[
719 c.authors = [[
719 h.email(author),
720 h.email(author),
720 h.person(author, 'username_or_name_or_email'),
721 h.person(author, 'username_or_name_or_email'),
721 1
722 1
722 ]]
723 ]]
723
724
724 else: # load tree content at path
725 else: # load tree content at path
725 c.file_source_page = 'false'
726 c.file_source_page = 'false'
726 c.authors = []
727 c.authors = []
727 # this loads a simple tree without metadata to speed things up
728 # this loads a simple tree without metadata to speed things up
728 # later via ajax we call repo_nodetree_full and fetch whole
729 # later via ajax we call repo_nodetree_full and fetch whole
729 c.file_tree = self._get_tree_at_commit(c, c.commit.raw_id, f_path, at_rev=at_rev)
730 c.file_tree = self._get_tree_at_commit(c, c.commit.raw_id, f_path, at_rev=at_rev)
730
731
731 c.readme_data, c.readme_file = \
732 c.readme_data, c.readme_file = \
732 self._get_readme_data(self.db_repo, c.visual.default_renderer,
733 self._get_readme_data(self.db_repo, c.visual.default_renderer,
733 c.commit.raw_id, f_path)
734 c.commit.raw_id, f_path)
734
735
735 except RepositoryError as e:
736 except RepositoryError as e:
736 h.flash(h.escape(safe_str(e)), category='error')
737 h.flash(h.escape(safe_str(e)), category='error')
737 raise HTTPNotFound()
738 raise HTTPNotFound()
738
739
739 if self.request.environ.get('HTTP_X_PJAX'):
740 if self.request.environ.get('HTTP_X_PJAX'):
740 html = render('rhodecode:templates/files/files_pjax.mako',
741 html = render('rhodecode:templates/files/files_pjax.mako',
741 self._get_template_context(c), self.request)
742 self._get_template_context(c), self.request)
742 else:
743 else:
743 html = render('rhodecode:templates/files/files.mako',
744 html = render('rhodecode:templates/files/files.mako',
744 self._get_template_context(c), self.request)
745 self._get_template_context(c), self.request)
745 return Response(html)
746 return Response(html)
746
747
747 @HasRepoPermissionAnyDecorator(
748 @HasRepoPermissionAnyDecorator(
748 'repository.read', 'repository.write', 'repository.admin')
749 'repository.read', 'repository.write', 'repository.admin')
749 def repo_files_annotated_previous(self):
750 def repo_files_annotated_previous(self):
750 self.load_default_context()
751 self.load_default_context()
751
752
752 commit_id, f_path = self._get_commit_and_path()
753 commit_id, f_path = self._get_commit_and_path()
753 commit = self._get_commit_or_redirect(commit_id)
754 commit = self._get_commit_or_redirect(commit_id)
754 prev_commit_id = commit.raw_id
755 prev_commit_id = commit.raw_id
755 line_anchor = self.request.GET.get('line_anchor')
756 line_anchor = self.request.GET.get('line_anchor')
756 is_file = False
757 is_file = False
757 try:
758 try:
758 _file = commit.get_node(f_path)
759 _file = commit.get_node(f_path)
759 is_file = _file.is_file()
760 is_file = _file.is_file()
760 except (NodeDoesNotExistError, CommitDoesNotExistError, VCSError):
761 except (NodeDoesNotExistError, CommitDoesNotExistError, VCSError):
761 pass
762 pass
762
763
763 if is_file:
764 if is_file:
764 history = commit.get_path_history(f_path)
765 history = commit.get_path_history(f_path)
765 prev_commit_id = history[1].raw_id \
766 prev_commit_id = history[1].raw_id \
766 if len(history) > 1 else prev_commit_id
767 if len(history) > 1 else prev_commit_id
767 prev_url = h.route_path(
768 prev_url = h.route_path(
768 'repo_files:annotated', repo_name=self.db_repo_name,
769 'repo_files:annotated', repo_name=self.db_repo_name,
769 commit_id=prev_commit_id, f_path=f_path,
770 commit_id=prev_commit_id, f_path=f_path,
770 _anchor=f'L{line_anchor}')
771 _anchor=f'L{line_anchor}')
771
772
772 raise HTTPFound(prev_url)
773 raise HTTPFound(prev_url)
773
774
774 @LoginRequired()
775 @LoginRequired()
775 @HasRepoPermissionAnyDecorator(
776 @HasRepoPermissionAnyDecorator(
776 'repository.read', 'repository.write', 'repository.admin')
777 'repository.read', 'repository.write', 'repository.admin')
777 def repo_nodetree_full(self):
778 def repo_nodetree_full(self):
778 """
779 """
779 Returns rendered html of file tree that contains commit date,
780 Returns rendered html of file tree that contains commit date,
780 author, commit_id for the specified combination of
781 author, commit_id for the specified combination of
781 repo, commit_id and file path
782 repo, commit_id and file path
782 """
783 """
783 c = self.load_default_context()
784 c = self.load_default_context()
784
785
785 commit_id, f_path = self._get_commit_and_path()
786 commit_id, f_path = self._get_commit_and_path()
786 commit = self._get_commit_or_redirect(commit_id)
787 commit = self._get_commit_or_redirect(commit_id)
787 try:
788 try:
788 dir_node = commit.get_node(f_path)
789 dir_node = commit.get_node(f_path)
789 except RepositoryError as e:
790 except RepositoryError as e:
790 return Response(f'error: {h.escape(safe_str(e))}')
791 return Response(f'error: {h.escape(safe_str(e))}')
791
792
792 if dir_node.is_file():
793 if dir_node.is_file():
793 return Response('')
794 return Response('')
794
795
795 c.file = dir_node
796 c.file = dir_node
796 c.commit = commit
797 c.commit = commit
797 at_rev = self.request.GET.get('at')
798 at_rev = self.request.GET.get('at')
798
799
799 html = self._get_tree_at_commit(
800 html = self._get_tree_at_commit(
800 c, commit.raw_id, dir_node.path, full_load=True, at_rev=at_rev)
801 c, commit.raw_id, dir_node.path, full_load=True, at_rev=at_rev)
801
802
802 return Response(html)
803 return Response(html)
803
804
804 def _get_attachement_headers(self, f_path):
805 def _get_attachement_headers(self, f_path):
805 f_name = safe_str(f_path.split(Repository.NAME_SEP)[-1])
806 f_name = safe_str(f_path.split(Repository.NAME_SEP)[-1])
806 safe_path = f_name.replace('"', '\\"')
807 safe_path = f_name.replace('"', '\\"')
807 encoded_path = urllib.parse.quote(f_name)
808 encoded_path = urllib.parse.quote(f_name)
808
809
809 return "attachment; " \
810 return "attachment; " \
810 "filename=\"{}\"; " \
811 "filename=\"{}\"; " \
811 "filename*=UTF-8\'\'{}".format(safe_path, encoded_path)
812 "filename*=UTF-8\'\'{}".format(safe_path, encoded_path)
812
813
813 @LoginRequired()
814 @LoginRequired()
814 @HasRepoPermissionAnyDecorator(
815 @HasRepoPermissionAnyDecorator(
815 'repository.read', 'repository.write', 'repository.admin')
816 'repository.read', 'repository.write', 'repository.admin')
816 def repo_file_raw(self):
817 def repo_file_raw(self):
817 """
818 """
818 Action for show as raw, some mimetypes are "rendered",
819 Action for show as raw, some mimetypes are "rendered",
819 those include images, icons.
820 those include images, icons.
820 """
821 """
821 c = self.load_default_context()
822 c = self.load_default_context()
822
823
823 commit_id, f_path = self._get_commit_and_path()
824 commit_id, f_path = self._get_commit_and_path()
824 commit = self._get_commit_or_redirect(commit_id)
825 commit = self._get_commit_or_redirect(commit_id)
825 file_node = self._get_filenode_or_redirect(commit, f_path)
826 file_node = self._get_filenode_or_redirect(commit, f_path)
826
827
827 raw_mimetype_mapping = {
828 raw_mimetype_mapping = {
828 # map original mimetype to a mimetype used for "show as raw"
829 # map original mimetype to a mimetype used for "show as raw"
829 # you can also provide a content-disposition to override the
830 # you can also provide a content-disposition to override the
830 # default "attachment" disposition.
831 # default "attachment" disposition.
831 # orig_type: (new_type, new_dispo)
832 # orig_type: (new_type, new_dispo)
832
833
833 # show images inline:
834 # show images inline:
834 # Do not re-add SVG: it is unsafe and permits XSS attacks. One can
835 # Do not re-add SVG: it is unsafe and permits XSS attacks. One can
835 # for example render an SVG with javascript inside or even render
836 # for example render an SVG with javascript inside or even render
836 # HTML.
837 # HTML.
837 'image/x-icon': ('image/x-icon', 'inline'),
838 'image/x-icon': ('image/x-icon', 'inline'),
838 'image/png': ('image/png', 'inline'),
839 'image/png': ('image/png', 'inline'),
839 'image/gif': ('image/gif', 'inline'),
840 'image/gif': ('image/gif', 'inline'),
840 'image/jpeg': ('image/jpeg', 'inline'),
841 'image/jpeg': ('image/jpeg', 'inline'),
841 'application/pdf': ('application/pdf', 'inline'),
842 'application/pdf': ('application/pdf', 'inline'),
842 }
843 }
843
844
844 mimetype = file_node.mimetype
845 mimetype = file_node.mimetype
845 try:
846 try:
846 mimetype, disposition = raw_mimetype_mapping[mimetype]
847 mimetype, disposition = raw_mimetype_mapping[mimetype]
847 except KeyError:
848 except KeyError:
848 # we don't know anything special about this, handle it safely
849 # we don't know anything special about this, handle it safely
849 if file_node.is_binary:
850 if file_node.is_binary:
850 # do same as download raw for binary files
851 # do same as download raw for binary files
851 mimetype, disposition = 'application/octet-stream', 'attachment'
852 mimetype, disposition = 'application/octet-stream', 'attachment'
852 else:
853 else:
853 # do not just use the original mimetype, but force text/plain,
854 # do not just use the original mimetype, but force text/plain,
854 # otherwise it would serve text/html and that might be unsafe.
855 # otherwise it would serve text/html and that might be unsafe.
855 # Note: underlying vcs library fakes text/plain mimetype if the
856 # Note: underlying vcs library fakes text/plain mimetype if the
856 # mimetype can not be determined and it thinks it is not
857 # mimetype can not be determined and it thinks it is not
857 # binary.This might lead to erroneous text display in some
858 # binary.This might lead to erroneous text display in some
858 # cases, but helps in other cases, like with text files
859 # cases, but helps in other cases, like with text files
859 # without extension.
860 # without extension.
860 mimetype, disposition = 'text/plain', 'inline'
861 mimetype, disposition = 'text/plain', 'inline'
861
862
862 if disposition == 'attachment':
863 if disposition == 'attachment':
863 disposition = self._get_attachement_headers(f_path)
864 disposition = self._get_attachement_headers(f_path)
864
865
865 stream_content = file_node.stream_bytes()
866 stream_content = file_node.stream_bytes()
866
867
867 response = Response(app_iter=stream_content)
868 response = Response(app_iter=stream_content)
868 response.content_disposition = disposition
869 response.content_disposition = disposition
869 response.content_type = mimetype
870 response.content_type = mimetype
870
871
871 charset = self._get_default_encoding(c)
872 charset = self._get_default_encoding(c)
872 if charset:
873 if charset:
873 response.charset = charset
874 response.charset = charset
874
875
875 return response
876 return response
876
877
877 @LoginRequired()
878 @LoginRequired()
878 @HasRepoPermissionAnyDecorator(
879 @HasRepoPermissionAnyDecorator(
879 'repository.read', 'repository.write', 'repository.admin')
880 'repository.read', 'repository.write', 'repository.admin')
880 def repo_file_download(self):
881 def repo_file_download(self):
881 c = self.load_default_context()
882 c = self.load_default_context()
882
883
883 commit_id, f_path = self._get_commit_and_path()
884 commit_id, f_path = self._get_commit_and_path()
884 commit = self._get_commit_or_redirect(commit_id)
885 commit = self._get_commit_or_redirect(commit_id)
885 file_node = self._get_filenode_or_redirect(commit, f_path)
886 file_node = self._get_filenode_or_redirect(commit, f_path)
886
887
887 if self.request.GET.get('lf'):
888 if self.request.GET.get('lf'):
888 # only if lf get flag is passed, we download this file
889 # only if lf get flag is passed, we download this file
889 # as LFS/Largefile
890 # as LFS/Largefile
890 lf_node = file_node.get_largefile_node()
891 lf_node = file_node.get_largefile_node()
891 if lf_node:
892 if lf_node:
892 # overwrite our pointer with the REAL large-file
893 # overwrite our pointer with the REAL large-file
893 file_node = lf_node
894 file_node = lf_node
894
895
895 disposition = self._get_attachement_headers(f_path)
896 disposition = self._get_attachement_headers(f_path)
896
897
897 stream_content = file_node.stream_bytes()
898 stream_content = file_node.stream_bytes()
898
899
899 response = Response(app_iter=stream_content)
900 response = Response(app_iter=stream_content)
900 response.content_disposition = disposition
901 response.content_disposition = disposition
901 response.content_type = file_node.mimetype
902 response.content_type = file_node.mimetype
902
903
903 charset = self._get_default_encoding(c)
904 charset = self._get_default_encoding(c)
904 if charset:
905 if charset:
905 response.charset = charset
906 response.charset = charset
906
907
907 return response
908 return response
908
909
909 def _get_nodelist_at_commit(self, repo_name, repo_id, commit_id, f_path):
910 def _get_nodelist_at_commit(self, repo_name, repo_id, commit_id, f_path):
910
911
911 cache_seconds = safe_int(
912 cache_seconds = safe_int(
912 rhodecode.CONFIG.get('rc_cache.cache_repo.expiration_time'))
913 rhodecode.CONFIG.get('rc_cache.cache_repo.expiration_time'))
913 cache_on = cache_seconds > 0
914 cache_on = cache_seconds > 0
914 log.debug(
915 log.debug(
915 'Computing FILE SEARCH for repo_id %s commit_id `%s` and path `%s`'
916 'Computing FILE SEARCH for repo_id %s commit_id `%s` and path `%s`'
916 'with caching: %s[TTL: %ss]' % (
917 'with caching: %s[TTL: %ss]' % (
917 repo_id, commit_id, f_path, cache_on, cache_seconds or 0))
918 repo_id, commit_id, f_path, cache_on, cache_seconds or 0))
918
919
919 cache_namespace_uid = f'repo.{repo_id}'
920 cache_namespace_uid = f'repo.{repo_id}'
920 region = rc_cache.get_or_create_region('cache_repo', cache_namespace_uid)
921 region = rc_cache.get_or_create_region('cache_repo', cache_namespace_uid)
921
922
922 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid, condition=cache_on)
923 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid, condition=cache_on)
923 def compute_file_search(_name_hash, _repo_id, _commit_id, _f_path):
924 def compute_file_search(_name_hash, _repo_id, _commit_id, _f_path):
924 log.debug('Generating cached nodelist for repo_id:%s, %s, %s',
925 log.debug('Generating cached nodelist for repo_id:%s, %s, %s',
925 _repo_id, commit_id, f_path)
926 _repo_id, commit_id, f_path)
926 try:
927 try:
927 _d, _f = ScmModel().get_quick_filter_nodes(repo_name, _commit_id, _f_path)
928 _d, _f = ScmModel().get_quick_filter_nodes(repo_name, _commit_id, _f_path)
928 except (RepositoryError, CommitDoesNotExistError, Exception) as e:
929 except (RepositoryError, CommitDoesNotExistError, Exception) as e:
929 log.exception(safe_str(e))
930 log.exception(safe_str(e))
930 h.flash(h.escape(safe_str(e)), category='error')
931 h.flash(h.escape(safe_str(e)), category='error')
931 raise HTTPFound(h.route_path(
932 raise HTTPFound(h.route_path(
932 'repo_files', repo_name=self.db_repo_name,
933 'repo_files', repo_name=self.db_repo_name,
933 commit_id='tip', f_path='/'))
934 commit_id='tip', f_path='/'))
934
935
935 return _d + _f
936 return _d + _f
936
937
937 result = compute_file_search(self.db_repo.repo_name_hash, self.db_repo.repo_id,
938 result = compute_file_search(self.db_repo.repo_name_hash, self.db_repo.repo_id,
938 commit_id, f_path)
939 commit_id, f_path)
939 return filter(lambda n: self.path_filter.path_access_allowed(n['name']), result)
940 return filter(lambda n: self.path_filter.path_access_allowed(n['name']), result)
940
941
941 @LoginRequired()
942 @LoginRequired()
942 @HasRepoPermissionAnyDecorator(
943 @HasRepoPermissionAnyDecorator(
943 'repository.read', 'repository.write', 'repository.admin')
944 'repository.read', 'repository.write', 'repository.admin')
944 def repo_nodelist(self):
945 def repo_nodelist(self):
945 self.load_default_context()
946 self.load_default_context()
946
947
947 commit_id, f_path = self._get_commit_and_path()
948 commit_id, f_path = self._get_commit_and_path()
948 commit = self._get_commit_or_redirect(commit_id)
949 commit = self._get_commit_or_redirect(commit_id)
949
950
950 metadata = self._get_nodelist_at_commit(
951 metadata = self._get_nodelist_at_commit(
951 self.db_repo_name, self.db_repo.repo_id, commit.raw_id, f_path)
952 self.db_repo_name, self.db_repo.repo_id, commit.raw_id, f_path)
952 return {'nodes': [x for x in metadata]}
953 return {'nodes': [x for x in metadata]}
953
954
954 def _create_references(self, branches_or_tags, symbolic_reference, f_path, ref_type):
955 def _create_references(self, branches_or_tags, symbolic_reference, f_path, ref_type):
955 items = []
956 items = []
956 for name, commit_id in branches_or_tags.items():
957 for name, commit_id in branches_or_tags.items():
957 sym_ref = symbolic_reference(commit_id, name, f_path, ref_type)
958 sym_ref = symbolic_reference(commit_id, name, f_path, ref_type)
958 items.append((sym_ref, name, ref_type))
959 items.append((sym_ref, name, ref_type))
959 return items
960 return items
960
961
961 def _symbolic_reference(self, commit_id, name, f_path, ref_type):
962 def _symbolic_reference(self, commit_id, name, f_path, ref_type):
962 return commit_id
963 return commit_id
963
964
964 def _symbolic_reference_svn(self, commit_id, name, f_path, ref_type):
965 def _symbolic_reference_svn(self, commit_id, name, f_path, ref_type):
965 return commit_id
966 return commit_id
966
967
967 # NOTE(dan): old code we used in "diff" mode compare
968 # NOTE(dan): old code we used in "diff" mode compare
968 new_f_path = vcspath.join(name, f_path)
969 new_f_path = vcspath.join(name, f_path)
969 return f'{new_f_path}@{commit_id}'
970 return f'{new_f_path}@{commit_id}'
970
971
971 def _get_node_history(self, commit_obj, f_path, commits=None):
972 def _get_node_history(self, commit_obj, f_path, commits=None):
972 """
973 """
973 get commit history for given node
974 get commit history for given node
974
975
975 :param commit_obj: commit to calculate history
976 :param commit_obj: commit to calculate history
976 :param f_path: path for node to calculate history for
977 :param f_path: path for node to calculate history for
977 :param commits: if passed don't calculate history and take
978 :param commits: if passed don't calculate history and take
978 commits defined in this list
979 commits defined in this list
979 """
980 """
980 _ = self.request.translate
981 _ = self.request.translate
981
982
982 # calculate history based on tip
983 # calculate history based on tip
983 tip = self.rhodecode_vcs_repo.get_commit()
984 tip = self.rhodecode_vcs_repo.get_commit()
984 if commits is None:
985 if commits is None:
985 pre_load = ["author", "branch"]
986 pre_load = ["author", "branch"]
986 try:
987 try:
987 commits = tip.get_path_history(f_path, pre_load=pre_load)
988 commits = tip.get_path_history(f_path, pre_load=pre_load)
988 except (NodeDoesNotExistError, CommitError):
989 except (NodeDoesNotExistError, CommitError):
989 # this node is not present at tip!
990 # this node is not present at tip!
990 commits = commit_obj.get_path_history(f_path, pre_load=pre_load)
991 commits = commit_obj.get_path_history(f_path, pre_load=pre_load)
991
992
992 history = []
993 history = []
993 commits_group = ([], _("Changesets"))
994 commits_group = ([], _("Changesets"))
994 for commit in commits:
995 for commit in commits:
995 branch = ' (%s)' % commit.branch if commit.branch else ''
996 branch = ' (%s)' % commit.branch if commit.branch else ''
996 n_desc = f'r{commit.idx}:{commit.short_id}{branch}'
997 n_desc = f'r{commit.idx}:{commit.short_id}{branch}'
997 commits_group[0].append((commit.raw_id, n_desc, 'sha'))
998 commits_group[0].append((commit.raw_id, n_desc, 'sha'))
998 history.append(commits_group)
999 history.append(commits_group)
999
1000
1000 symbolic_reference = self._symbolic_reference
1001 symbolic_reference = self._symbolic_reference
1001
1002
1002 if self.rhodecode_vcs_repo.alias == 'svn':
1003 if self.rhodecode_vcs_repo.alias == 'svn':
1003 adjusted_f_path = RepoFilesView.adjust_file_path_for_svn(
1004 adjusted_f_path = RepoFilesView.adjust_file_path_for_svn(
1004 f_path, self.rhodecode_vcs_repo)
1005 f_path, self.rhodecode_vcs_repo)
1005 if adjusted_f_path != f_path:
1006 if adjusted_f_path != f_path:
1006 log.debug(
1007 log.debug(
1007 'Recognized svn tag or branch in file "%s", using svn '
1008 'Recognized svn tag or branch in file "%s", using svn '
1008 'specific symbolic references', f_path)
1009 'specific symbolic references', f_path)
1009 f_path = adjusted_f_path
1010 f_path = adjusted_f_path
1010 symbolic_reference = self._symbolic_reference_svn
1011 symbolic_reference = self._symbolic_reference_svn
1011
1012
1012 branches = self._create_references(
1013 branches = self._create_references(
1013 self.rhodecode_vcs_repo.branches, symbolic_reference, f_path, 'branch')
1014 self.rhodecode_vcs_repo.branches, symbolic_reference, f_path, 'branch')
1014 branches_group = (branches, _("Branches"))
1015 branches_group = (branches, _("Branches"))
1015
1016
1016 tags = self._create_references(
1017 tags = self._create_references(
1017 self.rhodecode_vcs_repo.tags, symbolic_reference, f_path, 'tag')
1018 self.rhodecode_vcs_repo.tags, symbolic_reference, f_path, 'tag')
1018 tags_group = (tags, _("Tags"))
1019 tags_group = (tags, _("Tags"))
1019
1020
1020 history.append(branches_group)
1021 history.append(branches_group)
1021 history.append(tags_group)
1022 history.append(tags_group)
1022
1023
1023 return history, commits
1024 return history, commits
1024
1025
1025 @LoginRequired()
1026 @LoginRequired()
1026 @HasRepoPermissionAnyDecorator(
1027 @HasRepoPermissionAnyDecorator(
1027 'repository.read', 'repository.write', 'repository.admin')
1028 'repository.read', 'repository.write', 'repository.admin')
1028 def repo_file_history(self):
1029 def repo_file_history(self):
1029 self.load_default_context()
1030 self.load_default_context()
1030
1031
1031 commit_id, f_path = self._get_commit_and_path()
1032 commit_id, f_path = self._get_commit_and_path()
1032 commit = self._get_commit_or_redirect(commit_id)
1033 commit = self._get_commit_or_redirect(commit_id)
1033 file_node = self._get_filenode_or_redirect(commit, f_path)
1034 file_node = self._get_filenode_or_redirect(commit, f_path)
1034
1035
1035 if file_node.is_file():
1036 if file_node.is_file():
1036 file_history, _hist = self._get_node_history(commit, f_path)
1037 file_history, _hist = self._get_node_history(commit, f_path)
1037
1038
1038 res = []
1039 res = []
1039 for section_items, section in file_history:
1040 for section_items, section in file_history:
1040 items = []
1041 items = []
1041 for obj_id, obj_text, obj_type in section_items:
1042 for obj_id, obj_text, obj_type in section_items:
1042 at_rev = ''
1043 at_rev = ''
1043 if obj_type in ['branch', 'bookmark', 'tag']:
1044 if obj_type in ['branch', 'bookmark', 'tag']:
1044 at_rev = obj_text
1045 at_rev = obj_text
1045 entry = {
1046 entry = {
1046 'id': obj_id,
1047 'id': obj_id,
1047 'text': obj_text,
1048 'text': obj_text,
1048 'type': obj_type,
1049 'type': obj_type,
1049 'at_rev': at_rev
1050 'at_rev': at_rev
1050 }
1051 }
1051
1052
1052 items.append(entry)
1053 items.append(entry)
1053
1054
1054 res.append({
1055 res.append({
1055 'text': section,
1056 'text': section,
1056 'children': items
1057 'children': items
1057 })
1058 })
1058
1059
1059 data = {
1060 data = {
1060 'more': False,
1061 'more': False,
1061 'results': res
1062 'results': res
1062 }
1063 }
1063 return data
1064 return data
1064
1065
1065 log.warning('Cannot fetch history for directory')
1066 log.warning('Cannot fetch history for directory')
1066 raise HTTPBadRequest()
1067 raise HTTPBadRequest()
1067
1068
1068 @LoginRequired()
1069 @LoginRequired()
1069 @HasRepoPermissionAnyDecorator(
1070 @HasRepoPermissionAnyDecorator(
1070 'repository.read', 'repository.write', 'repository.admin')
1071 'repository.read', 'repository.write', 'repository.admin')
1071 def repo_file_authors(self):
1072 def repo_file_authors(self):
1072 c = self.load_default_context()
1073 c = self.load_default_context()
1073
1074
1074 commit_id, f_path = self._get_commit_and_path()
1075 commit_id, f_path = self._get_commit_and_path()
1075 commit = self._get_commit_or_redirect(commit_id)
1076 commit = self._get_commit_or_redirect(commit_id)
1076 file_node = self._get_filenode_or_redirect(commit, f_path)
1077 file_node = self._get_filenode_or_redirect(commit, f_path)
1077
1078
1078 if not file_node.is_file():
1079 if not file_node.is_file():
1079 raise HTTPBadRequest()
1080 raise HTTPBadRequest()
1080
1081
1081 c.file_last_commit = file_node.last_commit
1082 c.file_last_commit = file_node.last_commit
1082 if self.request.GET.get('annotate') == '1':
1083 if self.request.GET.get('annotate') == '1':
1083 # use _hist from annotation if annotation mode is on
1084 # use _hist from annotation if annotation mode is on
1084 commit_ids = {x[1] for x in file_node.annotate}
1085 commit_ids = {x[1] for x in file_node.annotate}
1085 _hist = (
1086 _hist = (
1086 self.rhodecode_vcs_repo.get_commit(commit_id)
1087 self.rhodecode_vcs_repo.get_commit(commit_id)
1087 for commit_id in commit_ids)
1088 for commit_id in commit_ids)
1088 else:
1089 else:
1089 _f_history, _hist = self._get_node_history(commit, f_path)
1090 _f_history, _hist = self._get_node_history(commit, f_path)
1090 c.file_author = False
1091 c.file_author = False
1091
1092
1092 unique = collections.OrderedDict()
1093 unique = collections.OrderedDict()
1093 for commit in _hist:
1094 for commit in _hist:
1094 author = commit.author
1095 author = commit.author
1095 if author not in unique:
1096 if author not in unique:
1096 unique[commit.author] = [
1097 unique[commit.author] = [
1097 h.email(author),
1098 h.email(author),
1098 h.person(author, 'username_or_name_or_email'),
1099 h.person(author, 'username_or_name_or_email'),
1099 1 # counter
1100 1 # counter
1100 ]
1101 ]
1101
1102
1102 else:
1103 else:
1103 # increase counter
1104 # increase counter
1104 unique[commit.author][2] += 1
1105 unique[commit.author][2] += 1
1105
1106
1106 c.authors = [val for val in unique.values()]
1107 c.authors = [val for val in unique.values()]
1107
1108
1108 return self._get_template_context(c)
1109 return self._get_template_context(c)
1109
1110
1110 @LoginRequired()
1111 @LoginRequired()
1111 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1112 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1112 def repo_files_check_head(self):
1113 def repo_files_check_head(self):
1113 self.load_default_context()
1114 self.load_default_context()
1114
1115
1115 commit_id, f_path = self._get_commit_and_path()
1116 commit_id, f_path = self._get_commit_and_path()
1116 _branch_name, _sha_commit_id, is_head = \
1117 _branch_name, _sha_commit_id, is_head = \
1117 self._is_valid_head(commit_id, self.rhodecode_vcs_repo,
1118 self._is_valid_head(commit_id, self.rhodecode_vcs_repo,
1118 landing_ref=self.db_repo.landing_ref_name)
1119 landing_ref=self.db_repo.landing_ref_name)
1119
1120
1120 new_path = self.request.POST.get('path')
1121 new_path = self.request.POST.get('path')
1121 operation = self.request.POST.get('operation')
1122 operation = self.request.POST.get('operation')
1122 path_exist = ''
1123 path_exist = ''
1123
1124
1124 if new_path and operation in ['create', 'upload']:
1125 if new_path and operation in ['create', 'upload']:
1125 new_f_path = os.path.join(f_path.lstrip('/'), new_path)
1126 new_f_path = os.path.join(f_path.lstrip('/'), new_path)
1126 try:
1127 try:
1127 commit_obj = self.rhodecode_vcs_repo.get_commit(commit_id)
1128 commit_obj = self.rhodecode_vcs_repo.get_commit(commit_id)
1128 # NOTE(dan): construct whole path without leading /
1129 # NOTE(dan): construct whole path without leading /
1129 file_node = commit_obj.get_node(new_f_path)
1130 file_node = commit_obj.get_node(new_f_path)
1130 if file_node is not None:
1131 if file_node is not None:
1131 path_exist = new_f_path
1132 path_exist = new_f_path
1132 except EmptyRepositoryError:
1133 except EmptyRepositoryError:
1133 pass
1134 pass
1134 except Exception:
1135 except Exception:
1135 pass
1136 pass
1136
1137
1137 return {
1138 return {
1138 'branch': _branch_name,
1139 'branch': _branch_name,
1139 'sha': _sha_commit_id,
1140 'sha': _sha_commit_id,
1140 'is_head': is_head,
1141 'is_head': is_head,
1141 'path_exists': path_exist
1142 'path_exists': path_exist
1142 }
1143 }
1143
1144
1144 @LoginRequired()
1145 @LoginRequired()
1145 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1146 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1146 def repo_files_remove_file(self):
1147 def repo_files_remove_file(self):
1147 _ = self.request.translate
1148 _ = self.request.translate
1148 c = self.load_default_context()
1149 c = self.load_default_context()
1149 commit_id, f_path = self._get_commit_and_path()
1150 commit_id, f_path = self._get_commit_and_path()
1150
1151
1151 self._ensure_not_locked()
1152 self._ensure_not_locked()
1152 _branch_name, _sha_commit_id, is_head = \
1153 _branch_name, _sha_commit_id, is_head = \
1153 self._is_valid_head(commit_id, self.rhodecode_vcs_repo,
1154 self._is_valid_head(commit_id, self.rhodecode_vcs_repo,
1154 landing_ref=self.db_repo.landing_ref_name)
1155 landing_ref=self.db_repo.landing_ref_name)
1155
1156
1156 self.forbid_non_head(is_head, f_path)
1157 self.forbid_non_head(is_head, f_path)
1157 self.check_branch_permission(_branch_name)
1158 self.check_branch_permission(_branch_name)
1158
1159
1159 c.commit = self._get_commit_or_redirect(commit_id)
1160 c.commit = self._get_commit_or_redirect(commit_id)
1160 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1161 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1161
1162
1162 c.default_message = _(
1163 c.default_message = _(
1163 'Deleted file {} via RhodeCode Enterprise').format(f_path)
1164 'Deleted file {} via RhodeCode Enterprise').format(f_path)
1164 c.f_path = f_path
1165 c.f_path = f_path
1165
1166
1166 return self._get_template_context(c)
1167 return self._get_template_context(c)
1167
1168
1168 @LoginRequired()
1169 @LoginRequired()
1169 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1170 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1170 @CSRFRequired()
1171 @CSRFRequired()
1171 def repo_files_delete_file(self):
1172 def repo_files_delete_file(self):
1172 _ = self.request.translate
1173 _ = self.request.translate
1173
1174
1174 c = self.load_default_context()
1175 c = self.load_default_context()
1175 commit_id, f_path = self._get_commit_and_path()
1176 commit_id, f_path = self._get_commit_and_path()
1176
1177
1177 self._ensure_not_locked()
1178 self._ensure_not_locked()
1178 _branch_name, _sha_commit_id, is_head = \
1179 _branch_name, _sha_commit_id, is_head = \
1179 self._is_valid_head(commit_id, self.rhodecode_vcs_repo,
1180 self._is_valid_head(commit_id, self.rhodecode_vcs_repo,
1180 landing_ref=self.db_repo.landing_ref_name)
1181 landing_ref=self.db_repo.landing_ref_name)
1181
1182
1182 self.forbid_non_head(is_head, f_path)
1183 self.forbid_non_head(is_head, f_path)
1183 self.check_branch_permission(_branch_name)
1184 self.check_branch_permission(_branch_name)
1184
1185
1185 c.commit = self._get_commit_or_redirect(commit_id)
1186 c.commit = self._get_commit_or_redirect(commit_id)
1186 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1187 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1187
1188
1188 c.default_message = _(
1189 c.default_message = _(
1189 'Deleted file {} via RhodeCode Enterprise').format(f_path)
1190 'Deleted file {} via RhodeCode Enterprise').format(f_path)
1190 c.f_path = f_path
1191 c.f_path = f_path
1191 node_path = f_path
1192 node_path = f_path
1192 author = self._rhodecode_db_user.full_contact
1193 author = self._rhodecode_db_user.full_contact
1193 message = self.request.POST.get('message') or c.default_message
1194 message = self.request.POST.get('message') or c.default_message
1194 try:
1195 try:
1195 nodes = {
1196 nodes = {
1196 safe_bytes(node_path): {
1197 safe_bytes(node_path): {
1197 'content': b''
1198 'content': b''
1198 }
1199 }
1199 }
1200 }
1200 ScmModel().delete_nodes(
1201 ScmModel().delete_nodes(
1201 user=self._rhodecode_db_user.user_id, repo=self.db_repo,
1202 user=self._rhodecode_db_user.user_id, repo=self.db_repo,
1202 message=message,
1203 message=message,
1203 nodes=nodes,
1204 nodes=nodes,
1204 parent_commit=c.commit,
1205 parent_commit=c.commit,
1205 author=author,
1206 author=author,
1206 )
1207 )
1207
1208
1208 h.flash(
1209 h.flash(
1209 _('Successfully deleted file `{}`').format(
1210 _('Successfully deleted file `{}`').format(
1210 h.escape(f_path)), category='success')
1211 h.escape(f_path)), category='success')
1211 except Exception:
1212 except Exception:
1212 log.exception('Error during commit operation')
1213 log.exception('Error during commit operation')
1213 h.flash(_('Error occurred during commit'), category='error')
1214 h.flash(_('Error occurred during commit'), category='error')
1214 raise HTTPFound(
1215 raise HTTPFound(
1215 h.route_path('repo_commit', repo_name=self.db_repo_name,
1216 h.route_path('repo_commit', repo_name=self.db_repo_name,
1216 commit_id='tip'))
1217 commit_id='tip'))
1217
1218
1218 @LoginRequired()
1219 @LoginRequired()
1219 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1220 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1220 def repo_files_edit_file(self):
1221 def repo_files_edit_file(self):
1221 _ = self.request.translate
1222 _ = self.request.translate
1222 c = self.load_default_context()
1223 c = self.load_default_context()
1223 commit_id, f_path = self._get_commit_and_path()
1224 commit_id, f_path = self._get_commit_and_path()
1224
1225
1225 self._ensure_not_locked()
1226 self._ensure_not_locked()
1226 _branch_name, _sha_commit_id, is_head = \
1227 _branch_name, _sha_commit_id, is_head = \
1227 self._is_valid_head(commit_id, self.rhodecode_vcs_repo,
1228 self._is_valid_head(commit_id, self.rhodecode_vcs_repo,
1228 landing_ref=self.db_repo.landing_ref_name)
1229 landing_ref=self.db_repo.landing_ref_name)
1229
1230
1230 self.forbid_non_head(is_head, f_path, commit_id=commit_id)
1231 self.forbid_non_head(is_head, f_path, commit_id=commit_id)
1231 self.check_branch_permission(_branch_name, commit_id=commit_id)
1232 self.check_branch_permission(_branch_name, commit_id=commit_id)
1232
1233
1233 c.commit = self._get_commit_or_redirect(commit_id)
1234 c.commit = self._get_commit_or_redirect(commit_id)
1234 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1235 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1235
1236
1236 if c.file.is_binary:
1237 if c.file.is_binary:
1237 files_url = h.route_path(
1238 files_url = h.route_path(
1238 'repo_files',
1239 'repo_files',
1239 repo_name=self.db_repo_name,
1240 repo_name=self.db_repo_name,
1240 commit_id=c.commit.raw_id, f_path=f_path)
1241 commit_id=c.commit.raw_id, f_path=f_path)
1241 raise HTTPFound(files_url)
1242 raise HTTPFound(files_url)
1242
1243
1243 c.default_message = _('Edited file {} via RhodeCode Enterprise').format(f_path)
1244 c.default_message = _('Edited file {} via RhodeCode Enterprise').format(f_path)
1244 c.f_path = f_path
1245 c.f_path = f_path
1245
1246
1246 return self._get_template_context(c)
1247 return self._get_template_context(c)
1247
1248
1248 @LoginRequired()
1249 @LoginRequired()
1249 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1250 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1250 @CSRFRequired()
1251 @CSRFRequired()
1251 def repo_files_update_file(self):
1252 def repo_files_update_file(self):
1252 _ = self.request.translate
1253 _ = self.request.translate
1253 c = self.load_default_context()
1254 c = self.load_default_context()
1254 commit_id, f_path = self._get_commit_and_path()
1255 commit_id, f_path = self._get_commit_and_path()
1255
1256
1256 self._ensure_not_locked()
1257 self._ensure_not_locked()
1257
1258
1258 c.commit = self._get_commit_or_redirect(commit_id)
1259 c.commit = self._get_commit_or_redirect(commit_id)
1259 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1260 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1260
1261
1261 if c.file.is_binary:
1262 if c.file.is_binary:
1262 raise HTTPFound(h.route_path('repo_files', repo_name=self.db_repo_name,
1263 raise HTTPFound(h.route_path('repo_files', repo_name=self.db_repo_name,
1263 commit_id=c.commit.raw_id, f_path=f_path))
1264 commit_id=c.commit.raw_id, f_path=f_path))
1264
1265
1265 _branch_name, _sha_commit_id, is_head = \
1266 _branch_name, _sha_commit_id, is_head = \
1266 self._is_valid_head(commit_id, self.rhodecode_vcs_repo,
1267 self._is_valid_head(commit_id, self.rhodecode_vcs_repo,
1267 landing_ref=self.db_repo.landing_ref_name)
1268 landing_ref=self.db_repo.landing_ref_name)
1268
1269
1269 self.forbid_non_head(is_head, f_path, commit_id=commit_id)
1270 self.forbid_non_head(is_head, f_path, commit_id=commit_id)
1270 self.check_branch_permission(_branch_name, commit_id=commit_id)
1271 self.check_branch_permission(_branch_name, commit_id=commit_id)
1271
1272
1272 c.default_message = _('Edited file {} via RhodeCode Enterprise').format(f_path)
1273 c.default_message = _('Edited file {} via RhodeCode Enterprise').format(f_path)
1273 c.f_path = f_path
1274 c.f_path = f_path
1274
1275
1275 old_content = c.file.str_content
1276 old_content = c.file.str_content
1276 sl = old_content.splitlines(1)
1277 sl = old_content.splitlines(1)
1277 first_line = sl[0] if sl else ''
1278 first_line = sl[0] if sl else ''
1278
1279
1279 r_post = self.request.POST
1280 r_post = self.request.POST
1280 # line endings: 0 - Unix, 1 - Mac, 2 - DOS
1281 # line endings: 0 - Unix, 1 - Mac, 2 - DOS
1281 line_ending_mode = detect_mode(first_line, 0)
1282 line_ending_mode = detect_mode(first_line, 0)
1282 content = convert_line_endings(r_post.get('content', ''), line_ending_mode)
1283 content = convert_line_endings(r_post.get('content', ''), line_ending_mode)
1283
1284
1284 message = r_post.get('message') or c.default_message
1285 message = r_post.get('message') or c.default_message
1285
1286
1286 org_node_path = c.file.str_path
1287 org_node_path = c.file.str_path
1287 filename = r_post['filename']
1288 filename = r_post['filename']
1288
1289
1289 root_path = c.file.dir_path
1290 root_path = c.file.dir_path
1290 pure_path = self.create_pure_path(root_path, filename)
1291 pure_path = self.create_pure_path(root_path, filename)
1291 node_path = pure_path.as_posix()
1292 node_path = pure_path.as_posix()
1292
1293
1293 default_redirect_url = h.route_path('repo_commit', repo_name=self.db_repo_name,
1294 default_redirect_url = h.route_path('repo_commit', repo_name=self.db_repo_name,
1294 commit_id=commit_id)
1295 commit_id=commit_id)
1295 if content == old_content and node_path == org_node_path:
1296 if content == old_content and node_path == org_node_path:
1296 h.flash(_('No changes detected on {}').format(h.escape(org_node_path)),
1297 h.flash(_('No changes detected on {}').format(h.escape(org_node_path)),
1297 category='warning')
1298 category='warning')
1298 raise HTTPFound(default_redirect_url)
1299 raise HTTPFound(default_redirect_url)
1299
1300
1300 try:
1301 try:
1301 mapping = {
1302 mapping = {
1302 c.file.bytes_path: {
1303 c.file.bytes_path: {
1303 'org_filename': org_node_path,
1304 'org_filename': org_node_path,
1304 'filename': safe_bytes(node_path),
1305 'filename': safe_bytes(node_path),
1305 'content': safe_bytes(content),
1306 'content': safe_bytes(content),
1306 'lexer': '',
1307 'lexer': '',
1307 'op': 'mod',
1308 'op': 'mod',
1308 'mode': c.file.mode
1309 'mode': c.file.mode
1309 }
1310 }
1310 }
1311 }
1311
1312
1312 commit = ScmModel().update_nodes(
1313 commit = ScmModel().update_nodes(
1313 user=self._rhodecode_db_user.user_id,
1314 user=self._rhodecode_db_user.user_id,
1314 repo=self.db_repo,
1315 repo=self.db_repo,
1315 message=message,
1316 message=message,
1316 nodes=mapping,
1317 nodes=mapping,
1317 parent_commit=c.commit,
1318 parent_commit=c.commit,
1318 )
1319 )
1319
1320
1320 h.flash(_('Successfully committed changes to file `{}`').format(
1321 h.flash(_('Successfully committed changes to file `{}`').format(
1321 h.escape(f_path)), category='success')
1322 h.escape(f_path)), category='success')
1322 default_redirect_url = h.route_path(
1323 default_redirect_url = h.route_path(
1323 'repo_commit', repo_name=self.db_repo_name, commit_id=commit.raw_id)
1324 'repo_commit', repo_name=self.db_repo_name, commit_id=commit.raw_id)
1324
1325
1325 except Exception:
1326 except Exception:
1326 log.exception('Error occurred during commit')
1327 log.exception('Error occurred during commit')
1327 h.flash(_('Error occurred during commit'), category='error')
1328 h.flash(_('Error occurred during commit'), category='error')
1328
1329
1329 raise HTTPFound(default_redirect_url)
1330 raise HTTPFound(default_redirect_url)
1330
1331
1331 @LoginRequired()
1332 @LoginRequired()
1332 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1333 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1333 def repo_files_add_file(self):
1334 def repo_files_add_file(self):
1334 _ = self.request.translate
1335 _ = self.request.translate
1335 c = self.load_default_context()
1336 c = self.load_default_context()
1336 commit_id, f_path = self._get_commit_and_path()
1337 commit_id, f_path = self._get_commit_and_path()
1337
1338
1338 self._ensure_not_locked()
1339 self._ensure_not_locked()
1339
1340
1340 c.commit = self._get_commit_or_redirect(commit_id, redirect_after=False)
1341 c.commit = self._get_commit_or_redirect(commit_id, redirect_after=False)
1341 if c.commit is None:
1342 if c.commit is None:
1342 c.commit = EmptyCommit(alias=self.rhodecode_vcs_repo.alias)
1343 c.commit = EmptyCommit(alias=self.rhodecode_vcs_repo.alias)
1343
1344
1344 if self.rhodecode_vcs_repo.is_empty():
1345 if self.rhodecode_vcs_repo.is_empty():
1345 # for empty repository we cannot check for current branch, we rely on
1346 # for empty repository we cannot check for current branch, we rely on
1346 # c.commit.branch instead
1347 # c.commit.branch instead
1347 _branch_name, _sha_commit_id, is_head = c.commit.branch, '', True
1348 _branch_name, _sha_commit_id, is_head = c.commit.branch, '', True
1348 else:
1349 else:
1349 _branch_name, _sha_commit_id, is_head = \
1350 _branch_name, _sha_commit_id, is_head = \
1350 self._is_valid_head(commit_id, self.rhodecode_vcs_repo,
1351 self._is_valid_head(commit_id, self.rhodecode_vcs_repo,
1351 landing_ref=self.db_repo.landing_ref_name)
1352 landing_ref=self.db_repo.landing_ref_name)
1352
1353
1353 self.forbid_non_head(is_head, f_path, commit_id=commit_id)
1354 self.forbid_non_head(is_head, f_path, commit_id=commit_id)
1354 self.check_branch_permission(_branch_name, commit_id=commit_id)
1355 self.check_branch_permission(_branch_name, commit_id=commit_id)
1355
1356
1356 c.default_message = (_('Added file via RhodeCode Enterprise'))
1357 c.default_message = (_('Added file via RhodeCode Enterprise'))
1357 c.f_path = f_path.lstrip('/') # ensure not relative path
1358 c.f_path = f_path.lstrip('/') # ensure not relative path
1358
1359
1359 return self._get_template_context(c)
1360 return self._get_template_context(c)
1360
1361
1361 @LoginRequired()
1362 @LoginRequired()
1362 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1363 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1363 @CSRFRequired()
1364 @CSRFRequired()
1364 def repo_files_create_file(self):
1365 def repo_files_create_file(self):
1365 _ = self.request.translate
1366 _ = self.request.translate
1366 c = self.load_default_context()
1367 c = self.load_default_context()
1367 commit_id, f_path = self._get_commit_and_path()
1368 commit_id, f_path = self._get_commit_and_path()
1368
1369
1369 self._ensure_not_locked()
1370 self._ensure_not_locked()
1370
1371
1371 c.commit = self._get_commit_or_redirect(commit_id, redirect_after=False)
1372 c.commit = self._get_commit_or_redirect(commit_id, redirect_after=False)
1372 if c.commit is None:
1373 if c.commit is None:
1373 c.commit = EmptyCommit(alias=self.rhodecode_vcs_repo.alias)
1374 c.commit = EmptyCommit(alias=self.rhodecode_vcs_repo.alias)
1374
1375
1375 # calculate redirect URL
1376 # calculate redirect URL
1376 if self.rhodecode_vcs_repo.is_empty():
1377 if self.rhodecode_vcs_repo.is_empty():
1377 default_redirect_url = h.route_path(
1378 default_redirect_url = h.route_path(
1378 'repo_summary', repo_name=self.db_repo_name)
1379 'repo_summary', repo_name=self.db_repo_name)
1379 else:
1380 else:
1380 default_redirect_url = h.route_path(
1381 default_redirect_url = h.route_path(
1381 'repo_commit', repo_name=self.db_repo_name, commit_id='tip')
1382 'repo_commit', repo_name=self.db_repo_name, commit_id='tip')
1382
1383
1383 if self.rhodecode_vcs_repo.is_empty():
1384 if self.rhodecode_vcs_repo.is_empty():
1384 # for empty repository we cannot check for current branch, we rely on
1385 # for empty repository we cannot check for current branch, we rely on
1385 # c.commit.branch instead
1386 # c.commit.branch instead
1386 _branch_name, _sha_commit_id, is_head = c.commit.branch, '', True
1387 _branch_name, _sha_commit_id, is_head = c.commit.branch, '', True
1387 else:
1388 else:
1388 _branch_name, _sha_commit_id, is_head = \
1389 _branch_name, _sha_commit_id, is_head = \
1389 self._is_valid_head(commit_id, self.rhodecode_vcs_repo,
1390 self._is_valid_head(commit_id, self.rhodecode_vcs_repo,
1390 landing_ref=self.db_repo.landing_ref_name)
1391 landing_ref=self.db_repo.landing_ref_name)
1391
1392
1392 self.forbid_non_head(is_head, f_path, commit_id=commit_id)
1393 self.forbid_non_head(is_head, f_path, commit_id=commit_id)
1393 self.check_branch_permission(_branch_name, commit_id=commit_id)
1394 self.check_branch_permission(_branch_name, commit_id=commit_id)
1394
1395
1395 c.default_message = (_('Added file via RhodeCode Enterprise'))
1396 c.default_message = (_('Added file via RhodeCode Enterprise'))
1396 c.f_path = f_path
1397 c.f_path = f_path
1397
1398
1398 r_post = self.request.POST
1399 r_post = self.request.POST
1399 message = r_post.get('message') or c.default_message
1400 message = r_post.get('message') or c.default_message
1400 filename = r_post.get('filename')
1401 filename = r_post.get('filename')
1401 unix_mode = 0
1402 unix_mode = 0
1402
1403
1403 if not filename:
1404 if not filename:
1404 # If there's no commit, redirect to repo summary
1405 # If there's no commit, redirect to repo summary
1405 if type(c.commit) is EmptyCommit:
1406 if type(c.commit) is EmptyCommit:
1406 redirect_url = h.route_path(
1407 redirect_url = h.route_path(
1407 'repo_summary', repo_name=self.db_repo_name)
1408 'repo_summary', repo_name=self.db_repo_name)
1408 else:
1409 else:
1409 redirect_url = default_redirect_url
1410 redirect_url = default_redirect_url
1410 h.flash(_('No filename specified'), category='warning')
1411 h.flash(_('No filename specified'), category='warning')
1411 raise HTTPFound(redirect_url)
1412 raise HTTPFound(redirect_url)
1412
1413
1413 root_path = f_path
1414 root_path = f_path
1414 pure_path = self.create_pure_path(root_path, filename)
1415 pure_path = self.create_pure_path(root_path, filename)
1415 node_path = pure_path.as_posix().lstrip('/')
1416 node_path = pure_path.as_posix().lstrip('/')
1416
1417
1417 author = self._rhodecode_db_user.full_contact
1418 author = self._rhodecode_db_user.full_contact
1418 content = convert_line_endings(r_post.get('content', ''), unix_mode)
1419 content = convert_line_endings(r_post.get('content', ''), unix_mode)
1419 nodes = {
1420 nodes = {
1420 safe_bytes(node_path): {
1421 safe_bytes(node_path): {
1421 'content': safe_bytes(content)
1422 'content': safe_bytes(content)
1422 }
1423 }
1423 }
1424 }
1424
1425
1425 try:
1426 try:
1426
1427
1427 commit = ScmModel().create_nodes(
1428 commit = ScmModel().create_nodes(
1428 user=self._rhodecode_db_user.user_id,
1429 user=self._rhodecode_db_user.user_id,
1429 repo=self.db_repo,
1430 repo=self.db_repo,
1430 message=message,
1431 message=message,
1431 nodes=nodes,
1432 nodes=nodes,
1432 parent_commit=c.commit,
1433 parent_commit=c.commit,
1433 author=author,
1434 author=author,
1434 )
1435 )
1435
1436
1436 h.flash(_('Successfully committed new file `{}`').format(
1437 h.flash(_('Successfully committed new file `{}`').format(
1437 h.escape(node_path)), category='success')
1438 h.escape(node_path)), category='success')
1438
1439
1439 default_redirect_url = h.route_path(
1440 default_redirect_url = h.route_path(
1440 'repo_commit', repo_name=self.db_repo_name, commit_id=commit.raw_id)
1441 'repo_commit', repo_name=self.db_repo_name, commit_id=commit.raw_id)
1441
1442
1442 except NonRelativePathError:
1443 except NonRelativePathError:
1443 log.exception('Non Relative path found')
1444 log.exception('Non Relative path found')
1444 h.flash(_('The location specified must be a relative path and must not '
1445 h.flash(_('The location specified must be a relative path and must not '
1445 'contain .. in the path'), category='warning')
1446 'contain .. in the path'), category='warning')
1446 raise HTTPFound(default_redirect_url)
1447 raise HTTPFound(default_redirect_url)
1447 except (NodeError, NodeAlreadyExistsError) as e:
1448 except (NodeError, NodeAlreadyExistsError) as e:
1448 h.flash(h.escape(safe_str(e)), category='error')
1449 h.flash(h.escape(safe_str(e)), category='error')
1449 except Exception:
1450 except Exception:
1450 log.exception('Error occurred during commit')
1451 log.exception('Error occurred during commit')
1451 h.flash(_('Error occurred during commit'), category='error')
1452 h.flash(_('Error occurred during commit'), category='error')
1452
1453
1453 raise HTTPFound(default_redirect_url)
1454 raise HTTPFound(default_redirect_url)
1454
1455
1455 @LoginRequired()
1456 @LoginRequired()
1456 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1457 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1457 @CSRFRequired()
1458 @CSRFRequired()
1458 def repo_files_upload_file(self):
1459 def repo_files_upload_file(self):
1459 _ = self.request.translate
1460 _ = self.request.translate
1460 c = self.load_default_context()
1461 c = self.load_default_context()
1461 commit_id, f_path = self._get_commit_and_path()
1462 commit_id, f_path = self._get_commit_and_path()
1462
1463
1463 self._ensure_not_locked()
1464 self._ensure_not_locked()
1464
1465
1465 c.commit = self._get_commit_or_redirect(commit_id, redirect_after=False)
1466 c.commit = self._get_commit_or_redirect(commit_id, redirect_after=False)
1466 if c.commit is None:
1467 if c.commit is None:
1467 c.commit = EmptyCommit(alias=self.rhodecode_vcs_repo.alias)
1468 c.commit = EmptyCommit(alias=self.rhodecode_vcs_repo.alias)
1468
1469
1469 # calculate redirect URL
1470 # calculate redirect URL
1470 if self.rhodecode_vcs_repo.is_empty():
1471 if self.rhodecode_vcs_repo.is_empty():
1471 default_redirect_url = h.route_path(
1472 default_redirect_url = h.route_path(
1472 'repo_summary', repo_name=self.db_repo_name)
1473 'repo_summary', repo_name=self.db_repo_name)
1473 else:
1474 else:
1474 default_redirect_url = h.route_path(
1475 default_redirect_url = h.route_path(
1475 'repo_commit', repo_name=self.db_repo_name, commit_id='tip')
1476 'repo_commit', repo_name=self.db_repo_name, commit_id='tip')
1476
1477
1477 if self.rhodecode_vcs_repo.is_empty():
1478 if self.rhodecode_vcs_repo.is_empty():
1478 # for empty repository we cannot check for current branch, we rely on
1479 # for empty repository we cannot check for current branch, we rely on
1479 # c.commit.branch instead
1480 # c.commit.branch instead
1480 _branch_name, _sha_commit_id, is_head = c.commit.branch, '', True
1481 _branch_name, _sha_commit_id, is_head = c.commit.branch, '', True
1481 else:
1482 else:
1482 _branch_name, _sha_commit_id, is_head = \
1483 _branch_name, _sha_commit_id, is_head = \
1483 self._is_valid_head(commit_id, self.rhodecode_vcs_repo,
1484 self._is_valid_head(commit_id, self.rhodecode_vcs_repo,
1484 landing_ref=self.db_repo.landing_ref_name)
1485 landing_ref=self.db_repo.landing_ref_name)
1485
1486
1486 error = self.forbid_non_head(is_head, f_path, json_mode=True)
1487 error = self.forbid_non_head(is_head, f_path, json_mode=True)
1487 if error:
1488 if error:
1488 return {
1489 return {
1489 'error': error,
1490 'error': error,
1490 'redirect_url': default_redirect_url
1491 'redirect_url': default_redirect_url
1491 }
1492 }
1492 error = self.check_branch_permission(_branch_name, json_mode=True)
1493 error = self.check_branch_permission(_branch_name, json_mode=True)
1493 if error:
1494 if error:
1494 return {
1495 return {
1495 'error': error,
1496 'error': error,
1496 'redirect_url': default_redirect_url
1497 'redirect_url': default_redirect_url
1497 }
1498 }
1498
1499
1499 c.default_message = (_('Uploaded file via RhodeCode Enterprise'))
1500 c.default_message = (_('Uploaded file via RhodeCode Enterprise'))
1500 c.f_path = f_path
1501 c.f_path = f_path
1501
1502
1502 r_post = self.request.POST
1503 r_post = self.request.POST
1503
1504
1504 message = c.default_message
1505 message = c.default_message
1505 user_message = r_post.getall('message')
1506 user_message = r_post.getall('message')
1506 if isinstance(user_message, list) and user_message:
1507 if isinstance(user_message, list) and user_message:
1507 # we take the first from duplicated results if it's not empty
1508 # we take the first from duplicated results if it's not empty
1508 message = user_message[0] if user_message[0] else message
1509 message = user_message[0] if user_message[0] else message
1509
1510
1510 nodes = {}
1511 nodes = {}
1511
1512
1512 for file_obj in r_post.getall('files_upload') or []:
1513 for file_obj in r_post.getall('files_upload') or []:
1513 content = file_obj.file
1514 content = file_obj.file
1514 filename = file_obj.filename
1515 filename = file_obj.filename
1515
1516
1516 root_path = f_path
1517 root_path = f_path
1517 pure_path = self.create_pure_path(root_path, filename)
1518 pure_path = self.create_pure_path(root_path, filename)
1518 node_path = pure_path.as_posix().lstrip('/')
1519 node_path = pure_path.as_posix().lstrip('/')
1519
1520
1520 nodes[safe_bytes(node_path)] = {
1521 nodes[safe_bytes(node_path)] = {
1521 'content': content
1522 'content': content
1522 }
1523 }
1523
1524
1524 if not nodes:
1525 if not nodes:
1525 error = 'missing files'
1526 error = 'missing files'
1526 return {
1527 return {
1527 'error': error,
1528 'error': error,
1528 'redirect_url': default_redirect_url
1529 'redirect_url': default_redirect_url
1529 }
1530 }
1530
1531
1531 author = self._rhodecode_db_user.full_contact
1532 author = self._rhodecode_db_user.full_contact
1532
1533
1533 try:
1534 try:
1534 commit = ScmModel().create_nodes(
1535 commit = ScmModel().create_nodes(
1535 user=self._rhodecode_db_user.user_id,
1536 user=self._rhodecode_db_user.user_id,
1536 repo=self.db_repo,
1537 repo=self.db_repo,
1537 message=message,
1538 message=message,
1538 nodes=nodes,
1539 nodes=nodes,
1539 parent_commit=c.commit,
1540 parent_commit=c.commit,
1540 author=author,
1541 author=author,
1541 )
1542 )
1542 if len(nodes) == 1:
1543 if len(nodes) == 1:
1543 flash_message = _('Successfully committed {} new files').format(len(nodes))
1544 flash_message = _('Successfully committed {} new files').format(len(nodes))
1544 else:
1545 else:
1545 flash_message = _('Successfully committed 1 new file')
1546 flash_message = _('Successfully committed 1 new file')
1546
1547
1547 h.flash(flash_message, category='success')
1548 h.flash(flash_message, category='success')
1548
1549
1549 default_redirect_url = h.route_path(
1550 default_redirect_url = h.route_path(
1550 'repo_commit', repo_name=self.db_repo_name, commit_id=commit.raw_id)
1551 'repo_commit', repo_name=self.db_repo_name, commit_id=commit.raw_id)
1551
1552
1552 except NonRelativePathError:
1553 except NonRelativePathError:
1553 log.exception('Non Relative path found')
1554 log.exception('Non Relative path found')
1554 error = _('The location specified must be a relative path and must not '
1555 error = _('The location specified must be a relative path and must not '
1555 'contain .. in the path')
1556 'contain .. in the path')
1556 h.flash(error, category='warning')
1557 h.flash(error, category='warning')
1557
1558
1558 return {
1559 return {
1559 'error': error,
1560 'error': error,
1560 'redirect_url': default_redirect_url
1561 'redirect_url': default_redirect_url
1561 }
1562 }
1562 except (NodeError, NodeAlreadyExistsError) as e:
1563 except (NodeError, NodeAlreadyExistsError) as e:
1563 error = h.escape(e)
1564 error = h.escape(e)
1564 h.flash(error, category='error')
1565 h.flash(error, category='error')
1565
1566
1566 return {
1567 return {
1567 'error': error,
1568 'error': error,
1568 'redirect_url': default_redirect_url
1569 'redirect_url': default_redirect_url
1569 }
1570 }
1570 except Exception:
1571 except Exception:
1571 log.exception('Error occurred during commit')
1572 log.exception('Error occurred during commit')
1572 error = _('Error occurred during commit')
1573 error = _('Error occurred during commit')
1573 h.flash(error, category='error')
1574 h.flash(error, category='error')
1574 return {
1575 return {
1575 'error': error,
1576 'error': error,
1576 'redirect_url': default_redirect_url
1577 'redirect_url': default_redirect_url
1577 }
1578 }
1578
1579
1579 return {
1580 return {
1580 'error': None,
1581 'error': None,
1581 'redirect_url': default_redirect_url
1582 'redirect_url': default_redirect_url
1582 }
1583 }
@@ -1,203 +1,206 b''
1 # Copyright (C) 2016-2023 RhodeCode GmbH
1 # Copyright (C) 2016-2023 RhodeCode GmbH
2 #
2 #
3 # This program is free software: you can redistribute it and/or modify
3 # This program is free software: you can redistribute it and/or modify
4 # it under the terms of the GNU Affero General Public License, version 3
4 # it under the terms of the GNU Affero General Public License, version 3
5 # (only), as published by the Free Software Foundation.
5 # (only), as published by the Free Software Foundation.
6 #
6 #
7 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # GNU General Public License for more details.
10 # GNU General Public License for more details.
11 #
11 #
12 # You should have received a copy of the GNU Affero General Public License
12 # You should have received a copy of the GNU Affero General Public License
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 #
14 #
15 # This program is dual-licensed. If you wish to learn more about the
15 # This program is dual-licensed. If you wish to learn more about the
16 # RhodeCode Enterprise Edition, including its added features, Support services,
16 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
18
18
19 import logging
19 import logging
20
20
21 from rhodecode.translation import lazy_ugettext
21 from rhodecode.translation import lazy_ugettext
22 from rhodecode.events.repo import (RepoEvent, _commits_as_dict, _issues_as_dict)
22 from rhodecode.events.repo import (RepoEvent, _commits_as_dict, _issues_as_dict)
23
23
24 log = logging.getLogger(__name__)
24 log = logging.getLogger(__name__)
25
25
26
26
27 class PullRequestEvent(RepoEvent):
27 class PullRequestEvent(RepoEvent):
28 """
28 """
29 Base class for pull request events.
29 Base class for pull request events.
30
30
31 :param pullrequest: a :class:`PullRequest` instance
31 :param pullrequest: a :class:`PullRequest` instance
32 """
32 """
33 name = 'pullrequest-event'
34 display_name = lazy_ugettext('pullrequest generic event')
35 description = lazy_ugettext('All events within a context of a pull request')
33
36
34 def __init__(self, pullrequest):
37 def __init__(self, pullrequest):
35 super().__init__(pullrequest.target_repo)
38 super().__init__(pullrequest.target_repo)
36 self.pullrequest = pullrequest
39 self.pullrequest = pullrequest
37
40
38 def as_dict(self):
41 def as_dict(self):
39 from rhodecode.lib.utils2 import md5_safe
42 from rhodecode.lib.utils2 import md5_safe
40 from rhodecode.model.pull_request import PullRequestModel
43 from rhodecode.model.pull_request import PullRequestModel
41 data = super().as_dict()
44 data = super().as_dict()
42
45
43 commits = _commits_as_dict(
46 commits = _commits_as_dict(
44 self,
47 self,
45 commit_ids=self.pullrequest.revisions,
48 commit_ids=self.pullrequest.revisions,
46 repos=[self.pullrequest.source_repo]
49 repos=[self.pullrequest.source_repo]
47 )
50 )
48 issues = _issues_as_dict(commits)
51 issues = _issues_as_dict(commits)
49 # calculate hashes of all commits for unique identifier of commits
52 # calculate hashes of all commits for unique identifier of commits
50 # inside that pull request
53 # inside that pull request
51 commits_hash = md5_safe(':'.join(x.get('raw_id', '') for x in commits))
54 commits_hash = md5_safe(':'.join(x.get('raw_id', '') for x in commits))
52
55
53 data.update({
56 data.update({
54 'pullrequest': {
57 'pullrequest': {
55 'title': self.pullrequest.title,
58 'title': self.pullrequest.title,
56 'issues': issues,
59 'issues': issues,
57 'pull_request_id': self.pullrequest.pull_request_id,
60 'pull_request_id': self.pullrequest.pull_request_id,
58 'url': PullRequestModel().get_url(
61 'url': PullRequestModel().get_url(
59 self.pullrequest, request=self.request),
62 self.pullrequest, request=self.request),
60 'permalink_url': PullRequestModel().get_url(
63 'permalink_url': PullRequestModel().get_url(
61 self.pullrequest, request=self.request, permalink=True),
64 self.pullrequest, request=self.request, permalink=True),
62 'shadow_url': PullRequestModel().get_shadow_clone_url(
65 'shadow_url': PullRequestModel().get_shadow_clone_url(
63 self.pullrequest, request=self.request),
66 self.pullrequest, request=self.request),
64 'status': self.pullrequest.calculated_review_status(),
67 'status': self.pullrequest.calculated_review_status(),
65 'commits_uid': commits_hash,
68 'commits_uid': commits_hash,
66 'commits': commits,
69 'commits': commits,
67 }
70 }
68 })
71 })
69 return data
72 return data
70
73
71
74
72 class PullRequestCreateEvent(PullRequestEvent):
75 class PullRequestCreateEvent(PullRequestEvent):
73 """
76 """
74 An instance of this class is emitted as an :term:`event` after a pull
77 An instance of this class is emitted as an :term:`event` after a pull
75 request is created.
78 request is created.
76 """
79 """
77 name = 'pullrequest-create'
80 name = 'pullrequest-create'
78 display_name = lazy_ugettext('pullrequest created')
81 display_name = lazy_ugettext('pullrequest created')
79 description = lazy_ugettext('Event triggered after pull request was created')
82 description = lazy_ugettext('Event triggered after pull request was created')
80
83
81
84
82 class PullRequestCloseEvent(PullRequestEvent):
85 class PullRequestCloseEvent(PullRequestEvent):
83 """
86 """
84 An instance of this class is emitted as an :term:`event` after a pull
87 An instance of this class is emitted as an :term:`event` after a pull
85 request is closed.
88 request is closed.
86 """
89 """
87 name = 'pullrequest-close'
90 name = 'pullrequest-close'
88 display_name = lazy_ugettext('pullrequest closed')
91 display_name = lazy_ugettext('pullrequest closed')
89 description = lazy_ugettext('Event triggered after pull request was closed')
92 description = lazy_ugettext('Event triggered after pull request was closed')
90
93
91
94
92 class PullRequestUpdateEvent(PullRequestEvent):
95 class PullRequestUpdateEvent(PullRequestEvent):
93 """
96 """
94 An instance of this class is emitted as an :term:`event` after a pull
97 An instance of this class is emitted as an :term:`event` after a pull
95 request's commits have been updated.
98 request's commits have been updated.
96 """
99 """
97 name = 'pullrequest-update'
100 name = 'pullrequest-update'
98 display_name = lazy_ugettext('pullrequest commits updated')
101 display_name = lazy_ugettext('pullrequest commits updated')
99 description = lazy_ugettext('Event triggered after pull requests was updated')
102 description = lazy_ugettext('Event triggered after pull requests was updated')
100
103
101
104
102 class PullRequestReviewEvent(PullRequestEvent):
105 class PullRequestReviewEvent(PullRequestEvent):
103 """
106 """
104 An instance of this class is emitted as an :term:`event` after a pull
107 An instance of this class is emitted as an :term:`event` after a pull
105 request review has changed. A status defines new status of review.
108 request review has changed. A status defines new status of review.
106 """
109 """
107 name = 'pullrequest-review'
110 name = 'pullrequest-review'
108 display_name = lazy_ugettext('pullrequest review changed')
111 display_name = lazy_ugettext('pullrequest review changed')
109 description = lazy_ugettext('Event triggered after a review status of a '
112 description = lazy_ugettext('Event triggered after a review status of a '
110 'pull requests has changed to other.')
113 'pull requests has changed to other.')
111
114
112 def __init__(self, pullrequest, status):
115 def __init__(self, pullrequest, status):
113 super().__init__(pullrequest)
116 super().__init__(pullrequest)
114 self.status = status
117 self.status = status
115
118
116
119
117 class PullRequestMergeEvent(PullRequestEvent):
120 class PullRequestMergeEvent(PullRequestEvent):
118 """
121 """
119 An instance of this class is emitted as an :term:`event` after a pull
122 An instance of this class is emitted as an :term:`event` after a pull
120 request is merged.
123 request is merged.
121 """
124 """
122 name = 'pullrequest-merge'
125 name = 'pullrequest-merge'
123 display_name = lazy_ugettext('pullrequest merged')
126 display_name = lazy_ugettext('pullrequest merged')
124 description = lazy_ugettext('Event triggered after a successful merge operation '
127 description = lazy_ugettext('Event triggered after a successful merge operation '
125 'was executed on a pull request')
128 'was executed on a pull request')
126
129
127
130
128 class PullRequestCommentEvent(PullRequestEvent):
131 class PullRequestCommentEvent(PullRequestEvent):
129 """
132 """
130 An instance of this class is emitted as an :term:`event` after a pull
133 An instance of this class is emitted as an :term:`event` after a pull
131 request comment is created.
134 request comment is created.
132 """
135 """
133 name = 'pullrequest-comment'
136 name = 'pullrequest-comment'
134 display_name = lazy_ugettext('pullrequest commented')
137 display_name = lazy_ugettext('pullrequest commented')
135 description = lazy_ugettext('Event triggered after a comment was made on a code '
138 description = lazy_ugettext('Event triggered after a comment was made on a code '
136 'in the pull request')
139 'in the pull request')
137
140
138 def __init__(self, pullrequest, comment):
141 def __init__(self, pullrequest, comment):
139 super().__init__(pullrequest)
142 super().__init__(pullrequest)
140 self.comment = comment
143 self.comment = comment
141
144
142 def as_dict(self):
145 def as_dict(self):
143 from rhodecode.model.comment import CommentsModel
146 from rhodecode.model.comment import CommentsModel
144 data = super().as_dict()
147 data = super().as_dict()
145
148
146 status = None
149 status = None
147 if self.comment.status_change:
150 if self.comment.status_change:
148 status = self.comment.review_status
151 status = self.comment.review_status
149
152
150 data.update({
153 data.update({
151 'comment': {
154 'comment': {
152 'status': status,
155 'status': status,
153 'text': self.comment.text,
156 'text': self.comment.text,
154 'type': self.comment.comment_type,
157 'type': self.comment.comment_type,
155 'file': self.comment.f_path,
158 'file': self.comment.f_path,
156 'line': self.comment.line_no,
159 'line': self.comment.line_no,
157 'version': self.comment.last_version,
160 'version': self.comment.last_version,
158 'url': CommentsModel().get_url(
161 'url': CommentsModel().get_url(
159 self.comment, request=self.request),
162 self.comment, request=self.request),
160 'permalink_url': CommentsModel().get_url(
163 'permalink_url': CommentsModel().get_url(
161 self.comment, request=self.request, permalink=True),
164 self.comment, request=self.request, permalink=True),
162 }
165 }
163 })
166 })
164 return data
167 return data
165
168
166
169
167 class PullRequestCommentEditEvent(PullRequestEvent):
170 class PullRequestCommentEditEvent(PullRequestEvent):
168 """
171 """
169 An instance of this class is emitted as an :term:`event` after a pull
172 An instance of this class is emitted as an :term:`event` after a pull
170 request comment is edited.
173 request comment is edited.
171 """
174 """
172 name = 'pullrequest-comment-edit'
175 name = 'pullrequest-comment-edit'
173 display_name = lazy_ugettext('pullrequest comment edited')
176 display_name = lazy_ugettext('pullrequest comment edited')
174 description = lazy_ugettext('Event triggered after a comment was edited on a code '
177 description = lazy_ugettext('Event triggered after a comment was edited on a code '
175 'in the pull request')
178 'in the pull request')
176
179
177 def __init__(self, pullrequest, comment):
180 def __init__(self, pullrequest, comment):
178 super().__init__(pullrequest)
181 super().__init__(pullrequest)
179 self.comment = comment
182 self.comment = comment
180
183
181 def as_dict(self):
184 def as_dict(self):
182 from rhodecode.model.comment import CommentsModel
185 from rhodecode.model.comment import CommentsModel
183 data = super().as_dict()
186 data = super().as_dict()
184
187
185 status = None
188 status = None
186 if self.comment.status_change:
189 if self.comment.status_change:
187 status = self.comment.review_status
190 status = self.comment.review_status
188
191
189 data.update({
192 data.update({
190 'comment': {
193 'comment': {
191 'status': status,
194 'status': status,
192 'text': self.comment.text,
195 'text': self.comment.text,
193 'type': self.comment.comment_type,
196 'type': self.comment.comment_type,
194 'file': self.comment.f_path,
197 'file': self.comment.f_path,
195 'line': self.comment.line_no,
198 'line': self.comment.line_no,
196 'version': self.comment.last_version,
199 'version': self.comment.last_version,
197 'url': CommentsModel().get_url(
200 'url': CommentsModel().get_url(
198 self.comment, request=self.request),
201 self.comment, request=self.request),
199 'permalink_url': CommentsModel().get_url(
202 'permalink_url': CommentsModel().get_url(
200 self.comment, request=self.request, permalink=True),
203 self.comment, request=self.request, permalink=True),
201 }
204 }
202 })
205 })
203 return data
206 return data
@@ -1,440 +1,444 b''
1 # Copyright (C) 2016-2023 RhodeCode GmbH
1 # Copyright (C) 2016-2023 RhodeCode GmbH
2 #
2 #
3 # This program is free software: you can redistribute it and/or modify
3 # This program is free software: you can redistribute it and/or modify
4 # it under the terms of the GNU Affero General Public License, version 3
4 # it under the terms of the GNU Affero General Public License, version 3
5 # (only), as published by the Free Software Foundation.
5 # (only), as published by the Free Software Foundation.
6 #
6 #
7 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # GNU General Public License for more details.
10 # GNU General Public License for more details.
11 #
11 #
12 # You should have received a copy of the GNU Affero General Public License
12 # You should have received a copy of the GNU Affero General Public License
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 #
14 #
15 # This program is dual-licensed. If you wish to learn more about the
15 # This program is dual-licensed. If you wish to learn more about the
16 # RhodeCode Enterprise Edition, including its added features, Support services,
16 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
18
18
19 import collections
19 import collections
20 import logging
20 import logging
21 import datetime
21 import datetime
22
22
23 from rhodecode.translation import lazy_ugettext
23 from rhodecode.translation import lazy_ugettext
24 from rhodecode.model.db import User, Repository, Session
24 from rhodecode.model.db import User, Repository
25 from rhodecode.events.base import RhodeCodeIntegrationEvent
25 from rhodecode.events.base import RhodeCodeIntegrationEvent
26 from rhodecode.lib.vcs.exceptions import CommitDoesNotExistError
26 from rhodecode.lib.vcs.exceptions import CommitDoesNotExistError
27
27
28 log = logging.getLogger(__name__)
28 log = logging.getLogger(__name__)
29
29
30
30
31 def _commits_as_dict(event, commit_ids, repos):
31 def _commits_as_dict(event, commit_ids, repos):
32 """
32 """
33 Helper function to serialize commit_ids
33 Helper function to serialize commit_ids
34
34
35 :param event: class calling this method
35 :param event: class calling this method
36 :param commit_ids: commits to get
36 :param commit_ids: commits to get
37 :param repos: list of repos to check
37 :param repos: a list of repos to check
38 """
38 """
39 from rhodecode.lib.utils2 import extract_mentioned_users
39 from rhodecode.lib.utils2 import extract_mentioned_users
40 from rhodecode.lib.helpers import (
40 from rhodecode.lib.helpers import (
41 urlify_commit_message, process_patterns, chop_at_smart)
41 urlify_commit_message, process_patterns, chop_at_smart)
42 from rhodecode.model.repo import RepoModel
42 from rhodecode.model.repo import RepoModel
43
43
44 if not repos:
44 if not repos:
45 raise Exception('no repo defined')
45 raise Exception('no repo defined')
46
46
47 if not isinstance(repos, (tuple, list)):
47 if not isinstance(repos, (tuple, list)):
48 repos = [repos]
48 repos = [repos]
49
49
50 if not commit_ids:
50 if not commit_ids:
51 return []
51 return []
52
52
53 needed_commits = list(commit_ids)
53 needed_commits = list(commit_ids)
54
54
55 commits = []
55 commits = []
56 reviewers = []
56 reviewers = []
57 for repo in repos:
57 for repo in repos:
58 if not needed_commits:
58 if not needed_commits:
59 return commits # return early if we have the commits we need
59 return commits # return early if we have the commits we need
60
60
61 vcs_repo = repo.scm_instance(cache=False)
61 vcs_repo = repo.scm_instance(cache=False)
62
62
63 try:
63 try:
64 # use copy of needed_commits since we modify it while iterating
64 # use copy of needed_commits since we modify it while iterating
65 for commit_id in list(needed_commits):
65 for commit_id in list(needed_commits):
66 if commit_id.startswith('tag=>'):
66 if commit_id.startswith('tag=>'):
67 raw_id = commit_id[5:]
67 raw_id = commit_id[5:]
68 cs_data = {
68 cs_data = {
69 'raw_id': commit_id, 'short_id': commit_id,
69 'raw_id': commit_id, 'short_id': commit_id,
70 'branch': None,
70 'branch': None,
71 'git_ref_change': 'tag_add',
71 'git_ref_change': 'tag_add',
72 'message': f'Added new tag {raw_id}',
72 'message': f'Added new tag {raw_id}',
73 'author': event.actor.full_contact,
73 'author': event.actor.full_contact,
74 'date': datetime.datetime.now(),
74 'date': datetime.datetime.now(),
75 'refs': {
75 'refs': {
76 'branches': [],
76 'branches': [],
77 'bookmarks': [],
77 'bookmarks': [],
78 'tags': []
78 'tags': []
79 }
79 }
80 }
80 }
81 commits.append(cs_data)
81 commits.append(cs_data)
82
82
83 elif commit_id.startswith('delete_branch=>'):
83 elif commit_id.startswith('delete_branch=>'):
84 raw_id = commit_id[15:]
84 raw_id = commit_id[15:]
85 cs_data = {
85 cs_data = {
86 'raw_id': commit_id, 'short_id': commit_id,
86 'raw_id': commit_id, 'short_id': commit_id,
87 'branch': None,
87 'branch': None,
88 'git_ref_change': 'branch_delete',
88 'git_ref_change': 'branch_delete',
89 'message': f'Deleted branch {raw_id}',
89 'message': f'Deleted branch {raw_id}',
90 'author': event.actor.full_contact,
90 'author': event.actor.full_contact,
91 'date': datetime.datetime.now(),
91 'date': datetime.datetime.now(),
92 'refs': {
92 'refs': {
93 'branches': [],
93 'branches': [],
94 'bookmarks': [],
94 'bookmarks': [],
95 'tags': []
95 'tags': []
96 }
96 }
97 }
97 }
98 commits.append(cs_data)
98 commits.append(cs_data)
99
99
100 else:
100 else:
101 try:
101 try:
102 cs = vcs_repo.get_commit(commit_id)
102 cs = vcs_repo.get_commit(commit_id)
103 except CommitDoesNotExistError:
103 except CommitDoesNotExistError:
104 continue # maybe its in next repo
104 continue # maybe its in next repo
105
105
106 cs_data = cs.__json__()
106 cs_data = cs.__json__()
107 cs_data['refs'] = cs._get_refs()
107 cs_data['refs'] = cs._get_refs()
108
108
109 cs_data['mentions'] = extract_mentioned_users(cs_data['message'])
109 cs_data['mentions'] = extract_mentioned_users(cs_data['message'])
110 cs_data['reviewers'] = reviewers
110 cs_data['reviewers'] = reviewers
111 cs_data['url'] = RepoModel().get_commit_url(
111 cs_data['url'] = RepoModel().get_commit_url(
112 repo, cs_data['raw_id'], request=event.request)
112 repo, cs_data['raw_id'], request=event.request)
113 cs_data['permalink_url'] = RepoModel().get_commit_url(
113 cs_data['permalink_url'] = RepoModel().get_commit_url(
114 repo, cs_data['raw_id'], request=event.request,
114 repo, cs_data['raw_id'], request=event.request,
115 permalink=True)
115 permalink=True)
116 urlified_message, issues_data, errors = process_patterns(
116 urlified_message, issues_data, errors = process_patterns(
117 cs_data['message'], repo.repo_name)
117 cs_data['message'], repo.repo_name)
118 cs_data['issues'] = issues_data
118 cs_data['issues'] = issues_data
119 cs_data['message_html'] = urlify_commit_message(
119 cs_data['message_html'] = urlify_commit_message(
120 cs_data['message'], repo.repo_name)
120 cs_data['message'], repo.repo_name)
121 cs_data['message_html_title'] = chop_at_smart(
121 cs_data['message_html_title'] = chop_at_smart(
122 cs_data['message'], '\n', suffix_if_chopped='...')
122 cs_data['message'], '\n', suffix_if_chopped='...')
123 commits.append(cs_data)
123 commits.append(cs_data)
124
124
125 needed_commits.remove(commit_id)
125 needed_commits.remove(commit_id)
126
126
127 except Exception:
127 except Exception:
128 log.exception('Failed to extract commits data')
128 log.exception('Failed to extract commits data')
129 # we don't send any commits when crash happens, only full list
129 # we don't send any commits when crash happens, only full list
130 # matters we short circuit then.
130 # matters we short circuit then.
131 return []
131 return []
132
132
133 # we failed to remove all needed_commits from all repositories
133 # we failed to remove all needed_commits from all repositories
134 if needed_commits:
134 if needed_commits:
135 raise ValueError(f'Unexpectedly not found {needed_commits} in all available repos {repos}')
135 raise ValueError(f'Unexpectedly not found {needed_commits} in all available repos {repos}')
136
136
137 missing_commits = set(commit_ids) - set(c['raw_id'] for c in commits)
137 missing_commits = set(commit_ids) - set(c['raw_id'] for c in commits)
138 if missing_commits:
138 if missing_commits:
139 log.error('Inconsistent repository state. '
139 log.error('Inconsistent repository state. '
140 'Missing commits: %s', ', '.join(missing_commits))
140 'Missing commits: %s', ', '.join(missing_commits))
141
141
142 return commits
142 return commits
143
143
144
144
145 def _issues_as_dict(commits):
145 def _issues_as_dict(commits):
146 """ Helper function to serialize issues from commits """
146 """ Helper function to serialize issues from commits """
147 issues = {}
147 issues = {}
148 for commit in commits:
148 for commit in commits:
149 for issue in commit['issues']:
149 for issue in commit['issues']:
150 issues[issue['id']] = issue
150 issues[issue['id']] = issue
151 return issues
151 return issues
152
152
153
153
154 class RepoEvent(RhodeCodeIntegrationEvent):
154 class RepoEvent(RhodeCodeIntegrationEvent):
155 """
155 """
156 Base class for events acting on a repository.
156 Base class for events acting on a repository.
157
158 :param repo: a :class:`Repository` instance
159 """
157 """
160
158
161 def __init__(self, repo):
159 def __init__(self, repo):
160 """
161 :param repo: a :class:`Repository` instance
162 """
162 super().__init__()
163 super().__init__()
163 self.repo = repo
164 self.repo = repo
164
165
165 def as_dict(self):
166 def as_dict(self):
166 from rhodecode.model.repo import RepoModel
167 from rhodecode.model.repo import RepoModel
167 data = super().as_dict()
168 data = super().as_dict()
168
169
169 extra_fields = collections.OrderedDict()
170 extra_fields = collections.OrderedDict()
170 for field in self.repo.extra_fields:
171 for field in self.repo.extra_fields:
171 extra_fields[field.field_key] = field.field_value
172 extra_fields[field.field_key] = field.field_value
172
173
173 data.update({
174 data.update({
174 'repo': {
175 'repo': {
175 'repo_id': self.repo.repo_id,
176 'repo_id': self.repo.repo_id,
176 'repo_name': self.repo.repo_name,
177 'repo_name': self.repo.repo_name,
177 'repo_type': self.repo.repo_type,
178 'repo_type': self.repo.repo_type,
178 'url': RepoModel().get_url(
179 'url': RepoModel().get_url(
179 self.repo, request=self.request),
180 self.repo, request=self.request),
180 'permalink_url': RepoModel().get_url(
181 'permalink_url': RepoModel().get_url(
181 self.repo, request=self.request, permalink=True),
182 self.repo, request=self.request, permalink=True),
182 'extra_fields': extra_fields
183 'extra_fields': extra_fields
183 }
184 }
184 })
185 })
185 return data
186 return data
186
187
187
188
188 class RepoCommitCommentEvent(RepoEvent):
189 class RepoCommitCommentEvent(RepoEvent):
189 """
190 """
190 An instance of this class is emitted as an :term:`event` after a comment is made
191 An instance of this class is emitted as an :term:`event` after a comment is made
191 on repository commit.
192 on repository commit.
192 """
193 """
193
194
194 name = 'repo-commit-comment'
195 name = 'repo-commit-comment'
195 display_name = lazy_ugettext('repository commit comment')
196 display_name = lazy_ugettext('repository commit comment')
196 description = lazy_ugettext('Event triggered after a comment was made '
197 description = lazy_ugettext('Event triggered after a comment was made '
197 'on commit inside a repository')
198 'on commit inside a repository')
198
199
199 def __init__(self, repo, commit, comment):
200 def __init__(self, repo, commit, comment):
200 super().__init__(repo)
201 super().__init__(repo)
201 self.commit = commit
202 self.commit = commit
202 self.comment = comment
203 self.comment = comment
203
204
204 def as_dict(self):
205 def as_dict(self):
205 data = super().as_dict()
206 data = super().as_dict()
206 data['commit'] = {
207 data['commit'] = {
207 'commit_id': self.commit.raw_id,
208 'commit_id': self.commit.raw_id,
208 'commit_message': self.commit.message,
209 'commit_message': self.commit.message,
209 'commit_branch': self.commit.branch,
210 'commit_branch': self.commit.branch,
210 }
211 }
211
212
212 data['comment'] = {
213 data['comment'] = {
213 'comment_id': self.comment.comment_id,
214 'comment_id': self.comment.comment_id,
214 'comment_text': self.comment.text,
215 'comment_text': self.comment.text,
215 'comment_type': self.comment.comment_type,
216 'comment_type': self.comment.comment_type,
216 'comment_f_path': self.comment.f_path,
217 'comment_f_path': self.comment.f_path,
217 'comment_line_no': self.comment.line_no,
218 'comment_line_no': self.comment.line_no,
218 'comment_version': self.comment.last_version,
219 'comment_version': self.comment.last_version,
219 }
220 }
220 return data
221 return data
221
222
222
223
223 class RepoCommitCommentEditEvent(RepoEvent):
224 class RepoCommitCommentEditEvent(RepoEvent):
224 """
225 """
225 An instance of this class is emitted as an :term:`event` after a comment is edited
226 An instance of this class is emitted as an :term:`event` after a comment is edited
226 on repository commit.
227 on repository commit.
227 """
228 """
228
229
229 name = 'repo-commit-edit-comment'
230 name = 'repo-commit-edit-comment'
230 display_name = lazy_ugettext('repository commit edit comment')
231 display_name = lazy_ugettext('repository commit edit comment')
231 description = lazy_ugettext('Event triggered after a comment was edited '
232 description = lazy_ugettext('Event triggered after a comment was edited '
232 'on commit inside a repository')
233 'on commit inside a repository')
233
234
234 def __init__(self, repo, commit, comment):
235 def __init__(self, repo, commit, comment):
235 super().__init__(repo)
236 super().__init__(repo)
236 self.commit = commit
237 self.commit = commit
237 self.comment = comment
238 self.comment = comment
238
239
239 def as_dict(self):
240 def as_dict(self):
240 data = super().as_dict()
241 data = super().as_dict()
241 data['commit'] = {
242 data['commit'] = {
242 'commit_id': self.commit.raw_id,
243 'commit_id': self.commit.raw_id,
243 'commit_message': self.commit.message,
244 'commit_message': self.commit.message,
244 'commit_branch': self.commit.branch,
245 'commit_branch': self.commit.branch,
245 }
246 }
246
247
247 data['comment'] = {
248 data['comment'] = {
248 'comment_id': self.comment.comment_id,
249 'comment_id': self.comment.comment_id,
249 'comment_text': self.comment.text,
250 'comment_text': self.comment.text,
250 'comment_type': self.comment.comment_type,
251 'comment_type': self.comment.comment_type,
251 'comment_f_path': self.comment.f_path,
252 'comment_f_path': self.comment.f_path,
252 'comment_line_no': self.comment.line_no,
253 'comment_line_no': self.comment.line_no,
253 'comment_version': self.comment.last_version,
254 'comment_version': self.comment.last_version,
254 }
255 }
255 return data
256 return data
256
257
257
258
258 class RepoPreCreateEvent(RepoEvent):
259 class RepoPreCreateEvent(RepoEvent):
259 """
260 """
260 An instance of this class is emitted as an :term:`event` before a repo is
261 An instance of this class is emitted as an :term:`event` before a repo is
261 created.
262 created.
262 """
263 """
263 name = 'repo-pre-create'
264 name = 'repo-pre-create'
264 display_name = lazy_ugettext('repository pre create')
265 display_name = lazy_ugettext('repository pre create')
265 description = lazy_ugettext('Event triggered before repository is created')
266 description = lazy_ugettext('Event triggered before repository is created')
266
267
267
268
268 class RepoCreateEvent(RepoEvent):
269 class RepoCreateEvent(RepoEvent):
269 """
270 """
270 An instance of this class is emitted as an :term:`event` whenever a repo is
271 An instance of this class is emitted as an :term:`event` whenever a repo is
271 created.
272 created.
272 """
273 """
273 name = 'repo-create'
274 name = 'repo-create'
274 display_name = lazy_ugettext('repository created')
275 display_name = lazy_ugettext('repository created')
275 description = lazy_ugettext('Event triggered after repository was created')
276 description = lazy_ugettext('Event triggered after repository was created')
276
277
277
278
278 class RepoPreDeleteEvent(RepoEvent):
279 class RepoPreDeleteEvent(RepoEvent):
279 """
280 """
280 An instance of this class is emitted as an :term:`event` whenever a repo is
281 An instance of this class is emitted as an :term:`event` whenever a repo is
281 created.
282 created.
282 """
283 """
283 name = 'repo-pre-delete'
284 name = 'repo-pre-delete'
284 display_name = lazy_ugettext('repository pre delete')
285 display_name = lazy_ugettext('repository pre delete')
285 description = lazy_ugettext('Event triggered before a repository is deleted')
286 description = lazy_ugettext('Event triggered before a repository is deleted')
286
287
287
288
288 class RepoDeleteEvent(RepoEvent):
289 class RepoDeleteEvent(RepoEvent):
289 """
290 """
290 An instance of this class is emitted as an :term:`event` whenever a repo is
291 An instance of this class is emitted as an :term:`event` whenever a repo is
291 created.
292 created.
292 """
293 """
293 name = 'repo-delete'
294 name = 'repo-delete'
294 display_name = lazy_ugettext('repository deleted')
295 display_name = lazy_ugettext('repository deleted')
295 description = lazy_ugettext('Event triggered after repository was deleted')
296 description = lazy_ugettext('Event triggered after repository was deleted')
296
297
297
298
298 class RepoVCSEvent(RepoEvent):
299 class RepoVCSEvent(RepoEvent):
299 """
300 """
300 Base class for events triggered by the VCS
301 Base class for events triggered by the VCS
301 """
302 """
303 name = ''
304 display_name = 'generic_vcs_event'
305
302 def __init__(self, repo_name, extras):
306 def __init__(self, repo_name, extras):
303 self.repo = Repository.get_by_repo_name(repo_name)
307 self.repo = Repository.get_by_repo_name(repo_name)
304 if not self.repo:
308 if not self.repo:
305 raise Exception(f'repo by this name {repo_name} does not exist')
309 raise Exception(f'repo by this name {repo_name} does not exist')
306 self.extras = extras
310 self.extras = extras
307 super().__init__(self.repo)
311 super().__init__(self.repo)
308
312
309 @property
313 @property
310 def actor(self):
314 def actor(self):
311 if self.extras.get('username'):
315 if self.extras.get('username'):
312 return User.get_by_username(self.extras['username'])
316 return User.get_by_username(self.extras['username'])
313
317
314 @property
318 @property
315 def actor_ip(self):
319 def actor_ip(self):
316 if self.extras.get('ip'):
320 if self.extras.get('ip'):
317 return self.extras['ip']
321 return self.extras['ip']
318
322
319 @property
323 @property
320 def server_url(self):
324 def server_url(self):
321 if self.extras.get('server_url'):
325 if self.extras.get('server_url'):
322 return self.extras['server_url']
326 return self.extras['server_url']
323
327
324 @property
328 @property
325 def request(self):
329 def request(self):
326 return self.extras.get('request') or self.get_request()
330 return self.extras.get('request') or self.get_request()
327
331
328
332
329 class RepoPrePullEvent(RepoVCSEvent):
333 class RepoPrePullEvent(RepoVCSEvent):
330 """
334 """
331 An instance of this class is emitted as an :term:`event` before commits
335 An instance of this class is emitted as an :term:`event` before commits
332 are pulled from a repo.
336 are pulled from a repo.
333 """
337 """
334 name = 'repo-pre-pull'
338 name = 'repo-pre-pull'
335 display_name = lazy_ugettext('repository pre pull')
339 display_name = lazy_ugettext('repository pre pull')
336 description = lazy_ugettext('Event triggered before repository code is pulled')
340 description = lazy_ugettext('Event triggered before repository code is pulled')
337
341
338
342
339 class RepoPullEvent(RepoVCSEvent):
343 class RepoPullEvent(RepoVCSEvent):
340 """
344 """
341 An instance of this class is emitted as an :term:`event` after commits
345 An instance of this class is emitted as an :term:`event` after commits
342 are pulled from a repo.
346 are pulled from a repo.
343 """
347 """
344 name = 'repo-pull'
348 name = 'repo-pull'
345 display_name = lazy_ugettext('repository pull')
349 display_name = lazy_ugettext('repository pull')
346 description = lazy_ugettext('Event triggered after repository code was pulled')
350 description = lazy_ugettext('Event triggered after repository code was pulled')
347
351
348
352
349 class RepoPrePushEvent(RepoVCSEvent):
353 class RepoPrePushEvent(RepoVCSEvent):
350 """
354 """
351 An instance of this class is emitted as an :term:`event` before commits
355 An instance of this class is emitted as an :term:`event` before commits
352 are pushed to a repo.
356 are pushed to a repo.
353 """
357 """
354 name = 'repo-pre-push'
358 name = 'repo-pre-push'
355 display_name = lazy_ugettext('repository pre push')
359 display_name = lazy_ugettext('repository pre push')
356 description = lazy_ugettext('Event triggered before the code is '
360 description = lazy_ugettext('Event triggered before the code is '
357 'pushed to a repository')
361 'pushed to a repository')
358
362
359
363
360 class RepoPushEvent(RepoVCSEvent):
364 class RepoPushEvent(RepoVCSEvent):
361 """
365 """
362 An instance of this class is emitted as an :term:`event` after commits
366 An instance of this class is emitted as an :term:`event` after commits
363 are pushed to a repo.
367 are pushed to a repo.
364
368
365 :param extras: (optional) dict of data from proxied VCS actions
369 :param extras: (optional) dict of data from proxied VCS actions
366 """
370 """
367 name = 'repo-push'
371 name = 'repo-push'
368 display_name = lazy_ugettext('repository push')
372 display_name = lazy_ugettext('repository push')
369 description = lazy_ugettext('Event triggered after the code was '
373 description = lazy_ugettext('Event triggered after the code was '
370 'pushed to a repository')
374 'pushed to a repository')
371
375
372 def __init__(self, repo_name, pushed_commit_ids, extras):
376 def __init__(self, repo_name, pushed_commit_ids, extras):
373 super().__init__(repo_name, extras)
377 super().__init__(repo_name, extras)
374 self.pushed_commit_ids = pushed_commit_ids
378 self.pushed_commit_ids = pushed_commit_ids
375 self.new_refs = extras.new_refs
379 self.new_refs = extras.new_refs
376
380
377 def as_dict(self):
381 def as_dict(self):
378 data = super().as_dict()
382 data = super().as_dict()
379
383
380 def branch_url(branch_name):
384 def branch_url(branch_name):
381 return '{}/changelog?branch={}'.format(
385 return '{}/changelog?branch={}'.format(
382 data['repo']['url'], branch_name)
386 data['repo']['url'], branch_name)
383
387
384 def tag_url(tag_name):
388 def tag_url(tag_name):
385 return '{}/files/{}/'.format(
389 return '{}/files/{}/'.format(
386 data['repo']['url'], tag_name)
390 data['repo']['url'], tag_name)
387
391
388 commits = _commits_as_dict(
392 commits = _commits_as_dict(
389 self, commit_ids=self.pushed_commit_ids, repos=[self.repo])
393 self, commit_ids=self.pushed_commit_ids, repos=[self.repo])
390
394
391 last_branch = None
395 last_branch = None
392 for commit in reversed(commits):
396 for commit in reversed(commits):
393 commit['branch'] = commit['branch'] or last_branch
397 commit['branch'] = commit['branch'] or last_branch
394 last_branch = commit['branch']
398 last_branch = commit['branch']
395 issues = _issues_as_dict(commits)
399 issues = _issues_as_dict(commits)
396
400
397 branches = set()
401 branches = set()
398 tags = set()
402 tags = set()
399 for commit in commits:
403 for commit in commits:
400 if commit['refs']['tags']:
404 if commit['refs']['tags']:
401 for tag in commit['refs']['tags']:
405 for tag in commit['refs']['tags']:
402 tags.add(tag)
406 tags.add(tag)
403 if commit['branch']:
407 if commit['branch']:
404 branches.add(commit['branch'])
408 branches.add(commit['branch'])
405
409
406 # maybe we have branches in new_refs ?
410 # maybe we have branches in new_refs ?
407 try:
411 try:
408 branches = branches.union(set(self.new_refs['branches']))
412 branches = branches.union(set(self.new_refs['branches']))
409 except Exception:
413 except Exception:
410 pass
414 pass
411
415
412 branches = [
416 branches = [
413 {
417 {
414 'name': branch,
418 'name': branch,
415 'url': branch_url(branch)
419 'url': branch_url(branch)
416 }
420 }
417 for branch in branches
421 for branch in branches
418 ]
422 ]
419
423
420 # maybe we have branches in new_refs ?
424 # maybe we have branches in new_refs ?
421 try:
425 try:
422 tags = tags.union(set(self.new_refs['tags']))
426 tags = tags.union(set(self.new_refs['tags']))
423 except Exception:
427 except Exception:
424 pass
428 pass
425
429
426 tags = [
430 tags = [
427 {
431 {
428 'name': tag,
432 'name': tag,
429 'url': tag_url(tag)
433 'url': tag_url(tag)
430 }
434 }
431 for tag in tags
435 for tag in tags
432 ]
436 ]
433
437
434 data['push'] = {
438 data['push'] = {
435 'commits': commits,
439 'commits': commits,
436 'issues': issues,
440 'issues': issues,
437 'branches': branches,
441 'branches': branches,
438 'tags': tags,
442 'tags': tags,
439 }
443 }
440 return data
444 return data
@@ -1,93 +1,93 b''
1 # Copyright (C) 2010-2023 RhodeCode GmbH
1 # Copyright (C) 2010-2023 RhodeCode GmbH
2 #
2 #
3 # This program is free software: you can redistribute it and/or modify
3 # This program is free software: you can redistribute it and/or modify
4 # it under the terms of the GNU Affero General Public License, version 3
4 # it under the terms of the GNU Affero General Public License, version 3
5 # (only), as published by the Free Software Foundation.
5 # (only), as published by the Free Software Foundation.
6 #
6 #
7 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # GNU General Public License for more details.
10 # GNU General Public License for more details.
11 #
11 #
12 # You should have received a copy of the GNU Affero General Public License
12 # You should have received a copy of the GNU Affero General Public License
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 #
14 #
15 # This program is dual-licensed. If you wish to learn more about the
15 # This program is dual-licensed. If you wish to learn more about the
16 # RhodeCode Enterprise Edition, including its added features, Support services,
16 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
18
18
19 import socket
19 import socket
20 import logging
20 import logging
21
21
22 import rhodecode
22 import rhodecode
23 from zope.cachedescriptors.property import Lazy as LazyProperty
23 from zope.cachedescriptors.property import Lazy as LazyProperty
24 from rhodecode.lib.celerylib.loader import (
24 from rhodecode.lib.celerylib.loader import (
25 celery_app, RequestContextTask, get_logger)
25 celery_app, RequestContextTask, get_logger)
26 from rhodecode.lib.statsd_client import StatsdClient
26 from rhodecode.lib.statsd_client import StatsdClient
27
27
28 async_task = celery_app.task
28 async_task = celery_app.task
29
29
30
30
31 log = logging.getLogger(__name__)
31 log = logging.getLogger(__name__)
32
32
33
33
34 class ResultWrapper(object):
34 class ResultWrapper(object):
35 def __init__(self, task):
35 def __init__(self, task):
36 self.task = task
36 self.task = task
37
37
38 @LazyProperty
38 @LazyProperty
39 def result(self):
39 def result(self):
40 return self.task
40 return self.task
41
41
42
42
43 def run_task(task, *args, **kwargs):
43 def run_task(task, *args, **kwargs):
44 import celery
44 import celery
45 log.debug('Got task `%s` for execution, celery mode enabled:%s', task, rhodecode.CELERY_ENABLED)
45 log.debug('Got task `%s` for execution, celery mode enabled:%s', task, rhodecode.CELERY_ENABLED)
46 if task is None:
46 if task is None:
47 raise ValueError('Got non-existing task for execution')
47 raise ValueError(f'Got non-existing task: {task} for execution')
48
48
49 exec_mode = 'sync'
49 exec_mode = 'sync'
50 allow_async = True
50 allow_async = True
51
51
52 # if we're already in a celery task, don't allow async execution again
52 # if we're already in a celery task, don't allow async execution again
53 # e.g task within task
53 # e.g task within task
54 in_task = celery.current_task
54 in_task = celery.current_task
55 if in_task:
55 if in_task:
56 log.debug('This task in in context of another task: %s, not allowing another async execution', in_task)
56 log.debug('This task in in context of another task: %s, not allowing another async execution', in_task)
57 allow_async = False
57 allow_async = False
58 if kwargs.pop('allow_subtask', False):
58 if kwargs.pop('allow_subtask', False):
59 log.debug('Forced async by allow_async=True flag')
59 log.debug('Forced async by allow_async=True flag')
60 allow_async = True
60 allow_async = True
61
61
62 t = None
62 t = None
63 if rhodecode.CELERY_ENABLED and allow_async:
63 if rhodecode.CELERY_ENABLED and allow_async:
64
64
65 try:
65 try:
66 t = task.apply_async(args=args, kwargs=kwargs)
66 t = task.apply_async(args=args, kwargs=kwargs)
67 log.debug('executing task %s:%s in async mode', t.task_id, task)
67 log.debug('executing task %s:%s in async mode', t.task_id, task)
68 except socket.error as e:
68 except socket.error as e:
69 if isinstance(e, IOError) and e.errno == 111:
69 if isinstance(e, IOError) and e.errno == 111:
70 log.error('Unable to connect to celeryd `%s`. Sync execution', e)
70 log.error('Unable to connect to celeryd `%s`. Sync execution', e)
71 else:
71 else:
72 log.exception("Exception while connecting to celeryd.")
72 log.exception("Exception while connecting to celeryd.")
73 except KeyError as e:
73 except KeyError as e:
74 log.error('Unable to connect to celeryd `%s`. Sync execution', e)
74 log.error('Unable to connect to celeryd `%s`. Sync execution', e)
75 except Exception as e:
75 except Exception as e:
76 log.exception(
76 log.exception(
77 "Exception while trying to run task asynchronous. "
77 "Exception while trying to run task asynchronous. "
78 "Fallback to sync execution.")
78 "Fallback to sync execution.")
79
79
80 else:
80 else:
81 log.debug('executing task %s:%s in sync mode', 'TASK', task)
81 log.debug('executing task %s:%s in sync mode', 'TASK', task)
82 statsd = StatsdClient.statsd
82 statsd = StatsdClient.statsd
83 if statsd:
83 if statsd:
84 task_repr = getattr(task, 'name', task)
84 task_repr = getattr(task, 'name', task)
85 statsd.incr('rhodecode_celery_task_total', tags=[
85 statsd.incr('rhodecode_celery_task_total', tags=[
86 f'task:{task_repr}',
86 f'task:{task_repr}',
87 'mode:sync'
87 'mode:sync'
88 ])
88 ])
89
89
90 # we got async task, return it after statsd call
90 # we got async task, return it after statsd call
91 if t:
91 if t:
92 return t
92 return t
93 return ResultWrapper(task(*args, **kwargs))
93 return ResultWrapper(task(*args, **kwargs))
@@ -1,173 +1,181 b''
1
1
2 # Copyright (C) 2010-2023 RhodeCode GmbH
2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 #
3 #
4 # This program is free software: you can redistribute it and/or modify
4 # This program is free software: you can redistribute it and/or modify
5 # it under the terms of the GNU Affero General Public License, version 3
5 # it under the terms of the GNU Affero General Public License, version 3
6 # (only), as published by the Free Software Foundation.
6 # (only), as published by the Free Software Foundation.
7 #
7 #
8 # This program is distributed in the hope that it will be useful,
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
11 # GNU General Public License for more details.
12 #
12 #
13 # You should have received a copy of the GNU Affero General Public License
13 # You should have received a copy of the GNU Affero General Public License
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 #
15 #
16 # This program is dual-licensed. If you wish to learn more about the
16 # This program is dual-licensed. If you wish to learn more about the
17 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19
19
20 import sqlalchemy
20 import sqlalchemy
21 from sqlalchemy import UnicodeText
21 from sqlalchemy import UnicodeText
22 from sqlalchemy.ext.mutable import Mutable, \
22 from sqlalchemy.ext.mutable import Mutable, MutableList, MutableDict
23 MutableList as MutationList, \
24 MutableDict as MutationDict
25
23
26 from rhodecode.lib import ext_json
24 from rhodecode.lib import ext_json
27
25
28
26
29 class JsonRaw(str):
27 class JsonRaw(str):
30 """
28 """
31 Allows interacting with a JSON types field using a raw string.
29 Allows interacting with a JSON types field using a raw string.
32
30
33 For example::
31 For example:
34 db_instance = JsonTable()
32 db_instance = JsonTable()
35 db_instance.enabled = True
33 db_instance.enabled = True
36 db_instance.json_data = JsonRaw('{"a": 4}')
34 db_instance.json_data = JsonRaw('{"a": 4}')
37
35
38 This will bypass serialization/checks, and allow storing
36 This will bypass serialization/checks, and allow storing
39 raw values
37 raw values
40 """
38 """
41 pass
39 pass
42
40
43
41
44 class JSONEncodedObj(sqlalchemy.types.TypeDecorator):
42 class JSONEncodedObj(sqlalchemy.types.TypeDecorator):
45 """
43 """
46 Represents an immutable structure as a json-encoded string.
44 Represents an immutable structure as a json-encoded string.
47
45
48 If default is, for example, a dict, then a NULL value in the
46 If default is, for example, a dict, then a NULL value in the
49 database will be exposed as an empty dict.
47 database will be exposed as an empty dict.
50 """
48 """
51
49
52 impl = UnicodeText
50 impl = UnicodeText
53 safe = True
51 safe = True
54 enforce_str = True
52 enforce_str = True
55
53
56 def __init__(self, *args, **kwargs):
54 def __init__(self, *args, **kwargs):
57 self.default = kwargs.pop('default', None)
55 self.default = kwargs.pop('default', None)
58 self.safe = kwargs.pop('safe_json', self.safe)
56 self.safe = kwargs.pop('safe_json', self.safe)
59 self.enforce_str = kwargs.pop('enforce_str', self.enforce_str)
57 self.enforce_str = kwargs.pop('enforce_str', self.enforce_str)
60 self.dialect_map = kwargs.pop('dialect_map', {})
58 self.dialect_map = kwargs.pop('dialect_map', {})
61 super(JSONEncodedObj, self).__init__(*args, **kwargs)
59 super(JSONEncodedObj, self).__init__(*args, **kwargs)
62
60
63 def load_dialect_impl(self, dialect):
61 def load_dialect_impl(self, dialect):
64 if dialect.name in self.dialect_map:
62 if dialect.name in self.dialect_map:
65 return dialect.type_descriptor(self.dialect_map[dialect.name])
63 return dialect.type_descriptor(self.dialect_map[dialect.name])
66 return dialect.type_descriptor(self.impl)
64 return dialect.type_descriptor(self.impl)
67
65
68 def process_bind_param(self, value, dialect):
66 def process_bind_param(self, value, dialect):
69 if isinstance(value, JsonRaw):
67 if isinstance(value, JsonRaw):
70 value = value
68 value = value
71 elif value is not None:
69 elif value is not None:
72 if self.enforce_str:
70 if self.enforce_str:
73 value = ext_json.str_json(value)
71 value = ext_json.str_json(value)
74 else:
72 else:
75 value = ext_json.json.dumps(value)
73 value = ext_json.json.dumps(value)
76 return value
74 return value
77
75
78 def process_result_value(self, value, dialect):
76 def process_result_value(self, value, dialect):
79 if self.default is not None and (not value or value == '""'):
77 if self.default is not None and (not value or value == '""'):
80 return self.default()
78 return self.default()
81
79
82 if value is not None:
80 if value is not None:
83 try:
81 try:
84 value = ext_json.json.loads(value)
82 value = ext_json.json.loads(value)
85 except Exception:
83 except Exception:
86 if self.safe and self.default is not None:
84 if self.safe and self.default is not None:
87 return self.default()
85 return self.default()
88 else:
86 else:
89 raise
87 raise
90 return value
88 return value
91
89
92
90
93 class MutationObj(Mutable):
91 class MutationObj(Mutable):
94
92
95 @classmethod
93 @classmethod
96 def coerce(cls, key, value):
94 def coerce(cls, key, value):
97 if isinstance(value, dict) and not isinstance(value, MutationDict):
95 if isinstance(value, dict) and not isinstance(value, MutationDict):
98 return MutationDict.coerce(key, value)
96 return MutationDict.coerce(key, value)
99 if isinstance(value, list) and not isinstance(value, MutationList):
97 if isinstance(value, list) and not isinstance(value, MutationList):
100 return MutationList.coerce(key, value)
98 return MutationList.coerce(key, value)
101 return value
99 return value
102
100
103 def de_coerce(self):
101 def de_coerce(self) -> "MutationObj":
104 return self
102 return self
105
103
106 @classmethod
104 @classmethod
107 def _listen_on_attribute(cls, attribute, coerce, parent_cls):
105 def _listen_on_attribute(cls, attribute, coerce, parent_cls):
108 key = attribute.key
106 key = attribute.key
109 if parent_cls is not attribute.class_:
107 if parent_cls is not attribute.class_:
110 return
108 return
111
109
112 # rely on "propagate" here
110 # rely on "propagate" here
113 parent_cls = attribute.class_
111 parent_cls = attribute.class_
114
112
115 def load(state, *args):
113 def load(state, *args):
116 val = state.dict.get(key, None)
114 val = state.dict.get(key, None)
117 if coerce:
115 if coerce:
118 val = cls.coerce(key, val)
116 val = cls.coerce(key, val)
119 state.dict[key] = val
117 state.dict[key] = val
120 if isinstance(val, cls):
118 if isinstance(val, cls):
121 val._parents[state.obj()] = key
119 val._parents[state.obj()] = key
122
120
123 def set(target, value, oldvalue, initiator):
121 def set(target, value, oldvalue, initiator):
124 if not isinstance(value, cls):
122 if not isinstance(value, cls):
125 value = cls.coerce(key, value)
123 value = cls.coerce(key, value)
126 if isinstance(value, cls):
124 if isinstance(value, cls):
127 value._parents[target.obj()] = key
125 value._parents[target.obj()] = key
128 if isinstance(oldvalue, cls):
126 if isinstance(oldvalue, cls):
129 oldvalue._parents.pop(target.obj(), None)
127 oldvalue._parents.pop(target.obj(), None)
130 return value
128 return value
131
129
132 def pickle(state, state_dict):
130 def pickle(state, state_dict):
133 val = state.dict.get(key, None)
131 val = state.dict.get(key, None)
134 if isinstance(val, cls):
132 if isinstance(val, cls):
135 if 'ext.mutable.values' not in state_dict:
133 if 'ext.mutable.values' not in state_dict:
136 state_dict['ext.mutable.values'] = []
134 state_dict['ext.mutable.values'] = []
137 state_dict['ext.mutable.values'].append(val)
135 state_dict['ext.mutable.values'].append(val)
138
136
139 def unpickle(state, state_dict):
137 def unpickle(state, state_dict):
140 if 'ext.mutable.values' in state_dict:
138 if 'ext.mutable.values' in state_dict:
141 for val in state_dict['ext.mutable.values']:
139 for val in state_dict['ext.mutable.values']:
142 val._parents[state.obj()] = key
140 val._parents[state.obj()] = key
143
141
144 sqlalchemy.event.listen(parent_cls, 'load', load, raw=True,
142 sqlalchemy.event.listen(parent_cls, 'load', load, raw=True,
145 propagate=True)
143 propagate=True)
146 sqlalchemy.event.listen(parent_cls, 'refresh', load, raw=True,
144 sqlalchemy.event.listen(parent_cls, 'refresh', load, raw=True,
147 propagate=True)
145 propagate=True)
148 sqlalchemy.event.listen(parent_cls, 'pickle', pickle, raw=True,
146 sqlalchemy.event.listen(parent_cls, 'pickle', pickle, raw=True,
149 propagate=True)
147 propagate=True)
150 sqlalchemy.event.listen(attribute, 'set', set, raw=True, retval=True,
148 sqlalchemy.event.listen(attribute, 'set', set, raw=True, retval=True,
151 propagate=True)
149 propagate=True)
152 sqlalchemy.event.listen(parent_cls, 'unpickle', unpickle, raw=True,
150 sqlalchemy.event.listen(parent_cls, 'unpickle', unpickle, raw=True,
153 propagate=True)
151 propagate=True)
154
152
155
153
154 class MutationList(MutableList):
155 def de_coerce(self):
156 return list(self)
157
158
159 class MutationDict(MutableDict):
160 def de_coerce(self):
161 return dict(self)
162
163
156 def JsonType(impl=None, **kwargs):
164 def JsonType(impl=None, **kwargs):
157 """
165 """
158 Helper for using a mutation obj, it allows to use .with_variant easily.
166 Helper for using a mutation obj, it allows to use .with_variant easily.
159 example::
167 example::
160
168
161 settings = Column('settings_json',
169 settings = Column('settings_json',
162 MutationObj.as_mutable(
170 MutationObj.as_mutable(
163 JsonType(dialect_map=dict(mysql=UnicodeText(16384))))
171 JsonType(dialect_map=dict(mysql=UnicodeText(16384))))
164 """
172 """
165
173
166 if impl == 'list':
174 if impl == 'list':
167 return JSONEncodedObj(default=list, **kwargs)
175 return JSONEncodedObj(default=list, **kwargs)
168 elif impl == 'dict':
176 elif impl == 'dict':
169 return JSONEncodedObj(default=dict, **kwargs)
177 return JSONEncodedObj(default=dict, **kwargs)
170 else:
178 else:
171 return JSONEncodedObj(**kwargs)
179 return JSONEncodedObj(**kwargs)
172
180
173
181
@@ -1,5860 +1,5873 b''
1 # Copyright (C) 2010-2023 RhodeCode GmbH
1 # Copyright (C) 2010-2023 RhodeCode GmbH
2 #
2 #
3 # This program is free software: you can redistribute it and/or modify
3 # This program is free software: you can redistribute it and/or modify
4 # it under the terms of the GNU Affero General Public License, version 3
4 # it under the terms of the GNU Affero General Public License, version 3
5 # (only), as published by the Free Software Foundation.
5 # (only), as published by the Free Software Foundation.
6 #
6 #
7 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # GNU General Public License for more details.
10 # GNU General Public License for more details.
11 #
11 #
12 # You should have received a copy of the GNU Affero General Public License
12 # You should have received a copy of the GNU Affero General Public License
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 #
14 #
15 # This program is dual-licensed. If you wish to learn more about the
15 # This program is dual-licensed. If you wish to learn more about the
16 # RhodeCode Enterprise Edition, including its added features, Support services,
16 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
18
18
19 """
19 """
20 Database Models for RhodeCode Enterprise
20 Database Models for RhodeCode Enterprise
21 """
21 """
22
22
23 import re
23 import re
24 import os
24 import os
25 import time
25 import time
26 import string
26 import string
27 import logging
27 import logging
28 import datetime
28 import datetime
29 import uuid
29 import uuid
30 import warnings
30 import warnings
31 import ipaddress
31 import ipaddress
32 import functools
32 import functools
33 import traceback
33 import traceback
34 import collections
34 import collections
35
35
36 from sqlalchemy import (
36 from sqlalchemy import (
37 or_, and_, not_, func, cast, TypeDecorator, event, select,
37 or_, and_, not_, func, cast, TypeDecorator, event, select,
38 true, false, null,
38 true, false, null,
39 Index, Sequence, UniqueConstraint, ForeignKey, CheckConstraint, Column,
39 Index, Sequence, UniqueConstraint, ForeignKey, CheckConstraint, Column,
40 Boolean, String, Unicode, UnicodeText, DateTime, Integer, LargeBinary,
40 Boolean, String, Unicode, UnicodeText, DateTime, Integer, LargeBinary,
41 Text, Float, PickleType, BigInteger)
41 Text, Float, PickleType, BigInteger)
42 from sqlalchemy.sql.expression import case
42 from sqlalchemy.sql.expression import case
43 from sqlalchemy.sql.functions import coalesce, count # pragma: no cover
43 from sqlalchemy.sql.functions import coalesce, count # pragma: no cover
44 from sqlalchemy.orm import (
44 from sqlalchemy.orm import (
45 relationship, lazyload, joinedload, class_mapper, validates, aliased, load_only)
45 relationship, lazyload, joinedload, class_mapper, validates, aliased, load_only)
46 from sqlalchemy.ext.declarative import declared_attr
46 from sqlalchemy.ext.declarative import declared_attr
47 from sqlalchemy.ext.hybrid import hybrid_property
47 from sqlalchemy.ext.hybrid import hybrid_property
48 from sqlalchemy.exc import IntegrityError # pragma: no cover
48 from sqlalchemy.exc import IntegrityError # pragma: no cover
49 from sqlalchemy.dialects.mysql import LONGTEXT
49 from sqlalchemy.dialects.mysql import LONGTEXT
50 from zope.cachedescriptors.property import Lazy as LazyProperty
50 from zope.cachedescriptors.property import Lazy as LazyProperty
51 from pyramid.threadlocal import get_current_request
51 from pyramid.threadlocal import get_current_request
52 from webhelpers2.text import remove_formatting
52 from webhelpers2.text import remove_formatting
53
53
54 from rhodecode.lib.str_utils import safe_bytes
54 from rhodecode.lib.str_utils import safe_bytes
55 from rhodecode.translation import _
55 from rhodecode.translation import _
56 from rhodecode.lib.vcs import get_vcs_instance, VCSError
56 from rhodecode.lib.vcs import get_vcs_instance, VCSError
57 from rhodecode.lib.vcs.backends.base import (
57 from rhodecode.lib.vcs.backends.base import (
58 EmptyCommit, Reference, unicode_to_reference, reference_to_unicode)
58 EmptyCommit, Reference, unicode_to_reference, reference_to_unicode)
59 from rhodecode.lib.utils2 import (
59 from rhodecode.lib.utils2 import (
60 str2bool, safe_str, get_commit_safe, sha1_safe,
60 str2bool, safe_str, get_commit_safe, sha1_safe,
61 time_to_datetime, aslist, Optional, safe_int, get_clone_url, AttributeDict,
61 time_to_datetime, aslist, Optional, safe_int, get_clone_url, AttributeDict,
62 glob2re, StrictAttributeDict, cleaned_uri, datetime_to_time)
62 glob2re, StrictAttributeDict, cleaned_uri, datetime_to_time)
63 from rhodecode.lib.jsonalchemy import (
63 from rhodecode.lib.jsonalchemy import (
64 MutationObj, MutationList, JsonType, JsonRaw)
64 MutationObj, MutationList, JsonType, JsonRaw)
65 from rhodecode.lib.hash_utils import sha1
65 from rhodecode.lib.hash_utils import sha1
66 from rhodecode.lib import ext_json
66 from rhodecode.lib import ext_json
67 from rhodecode.lib import enc_utils
67 from rhodecode.lib import enc_utils
68 from rhodecode.lib.ext_json import json
68 from rhodecode.lib.ext_json import json
69 from rhodecode.lib.caching_query import FromCache
69 from rhodecode.lib.caching_query import FromCache
70 from rhodecode.lib.exceptions import (
70 from rhodecode.lib.exceptions import (
71 ArtifactMetadataDuplicate, ArtifactMetadataBadValueType)
71 ArtifactMetadataDuplicate, ArtifactMetadataBadValueType)
72 from rhodecode.model.meta import Base, Session
72 from rhodecode.model.meta import Base, Session
73
73
74 URL_SEP = '/'
74 URL_SEP = '/'
75 log = logging.getLogger(__name__)
75 log = logging.getLogger(__name__)
76
76
77 # =============================================================================
77 # =============================================================================
78 # BASE CLASSES
78 # BASE CLASSES
79 # =============================================================================
79 # =============================================================================
80
80
81 # this is propagated from .ini file rhodecode.encrypted_values.secret or
81 # this is propagated from .ini file rhodecode.encrypted_values.secret or
82 # beaker.session.secret if first is not set.
82 # beaker.session.secret if first is not set.
83 # and initialized at environment.py
83 # and initialized at environment.py
84 ENCRYPTION_KEY: bytes = b''
84 ENCRYPTION_KEY: bytes = b''
85
85
86 # used to sort permissions by types, '#' used here is not allowed to be in
86 # used to sort permissions by types, '#' used here is not allowed to be in
87 # usernames, and it's very early in sorted string.printable table.
87 # usernames, and it's very early in sorted string.printable table.
88 PERMISSION_TYPE_SORT = {
88 PERMISSION_TYPE_SORT = {
89 'admin': '####',
89 'admin': '####',
90 'write': '###',
90 'write': '###',
91 'read': '##',
91 'read': '##',
92 'none': '#',
92 'none': '#',
93 }
93 }
94
94
95
95
96 def display_user_sort(obj):
96 def display_user_sort(obj):
97 """
97 """
98 Sort function used to sort permissions in .permissions() function of
98 Sort function used to sort permissions in .permissions() function of
99 Repository, RepoGroup, UserGroup. Also it put the default user in front
99 Repository, RepoGroup, UserGroup. Also it put the default user in front
100 of all other resources
100 of all other resources
101 """
101 """
102
102
103 if obj.username == User.DEFAULT_USER:
103 if obj.username == User.DEFAULT_USER:
104 return '#####'
104 return '#####'
105 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
105 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
106 extra_sort_num = '1' # default
106 extra_sort_num = '1' # default
107
107
108 # NOTE(dan): inactive duplicates goes last
108 # NOTE(dan): inactive duplicates goes last
109 if getattr(obj, 'duplicate_perm', None):
109 if getattr(obj, 'duplicate_perm', None):
110 extra_sort_num = '9'
110 extra_sort_num = '9'
111 return prefix + extra_sort_num + obj.username
111 return prefix + extra_sort_num + obj.username
112
112
113
113
114 def display_user_group_sort(obj):
114 def display_user_group_sort(obj):
115 """
115 """
116 Sort function used to sort permissions in .permissions() function of
116 Sort function used to sort permissions in .permissions() function of
117 Repository, RepoGroup, UserGroup. Also it put the default user in front
117 Repository, RepoGroup, UserGroup. Also it put the default user in front
118 of all other resources
118 of all other resources
119 """
119 """
120
120
121 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
121 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
122 return prefix + obj.users_group_name
122 return prefix + obj.users_group_name
123
123
124
124
125 def _hash_key(k):
125 def _hash_key(k):
126 return sha1_safe(k)
126 return sha1_safe(k)
127
127
128
128
129 def in_filter_generator(qry, items, limit=500):
129 def in_filter_generator(qry, items, limit=500):
130 """
130 """
131 Splits IN() into multiple with OR
131 Splits IN() into multiple with OR
132 e.g.::
132 e.g.::
133 cnt = Repository.query().filter(
133 cnt = Repository.query().filter(
134 or_(
134 or_(
135 *in_filter_generator(Repository.repo_id, range(100000))
135 *in_filter_generator(Repository.repo_id, range(100000))
136 )).count()
136 )).count()
137 """
137 """
138 if not items:
138 if not items:
139 # empty list will cause empty query which might cause security issues
139 # empty list will cause empty query which might cause security issues
140 # this can lead to hidden unpleasant results
140 # this can lead to hidden unpleasant results
141 items = [-1]
141 items = [-1]
142
142
143 parts = []
143 parts = []
144 for chunk in range(0, len(items), limit):
144 for chunk in range(0, len(items), limit):
145 parts.append(
145 parts.append(
146 qry.in_(items[chunk: chunk + limit])
146 qry.in_(items[chunk: chunk + limit])
147 )
147 )
148
148
149 return parts
149 return parts
150
150
151
151
152 base_table_args = {
152 base_table_args = {
153 'extend_existing': True,
153 'extend_existing': True,
154 'mysql_engine': 'InnoDB',
154 'mysql_engine': 'InnoDB',
155 'mysql_charset': 'utf8',
155 'mysql_charset': 'utf8',
156 'sqlite_autoincrement': True
156 'sqlite_autoincrement': True
157 }
157 }
158
158
159
159
160 class EncryptedTextValue(TypeDecorator):
160 class EncryptedTextValue(TypeDecorator):
161 """
161 """
162 Special column for encrypted long text data, use like::
162 Special column for encrypted long text data, use like::
163
163
164 value = Column("encrypted_value", EncryptedValue(), nullable=False)
164 value = Column("encrypted_value", EncryptedValue(), nullable=False)
165
165
166 This column is intelligent so if value is in unencrypted form it return
166 This column is intelligent so if value is in unencrypted form it return
167 unencrypted form, but on save it always encrypts
167 unencrypted form, but on save it always encrypts
168 """
168 """
169 cache_ok = True
169 cache_ok = True
170 impl = Text
170 impl = Text
171
171
172 def process_bind_param(self, value, dialect):
172 def process_bind_param(self, value, dialect):
173 """
173 """
174 Setter for storing value
174 Setter for storing value
175 """
175 """
176 import rhodecode
176 import rhodecode
177 if not value:
177 if not value:
178 return value
178 return value
179
179
180 # protect against double encrypting if values is already encrypted
180 # protect against double encrypting if values is already encrypted
181 if value.startswith('enc$aes$') \
181 if value.startswith('enc$aes$') \
182 or value.startswith('enc$aes_hmac$') \
182 or value.startswith('enc$aes_hmac$') \
183 or value.startswith('enc2$'):
183 or value.startswith('enc2$'):
184 raise ValueError('value needs to be in unencrypted format, '
184 raise ValueError('value needs to be in unencrypted format, '
185 'ie. not starting with enc$ or enc2$')
185 'ie. not starting with enc$ or enc2$')
186
186
187 algo = rhodecode.CONFIG.get('rhodecode.encrypted_values.algorithm') or 'aes'
187 algo = rhodecode.CONFIG.get('rhodecode.encrypted_values.algorithm') or 'aes'
188 bytes_val = enc_utils.encrypt_value(value, enc_key=ENCRYPTION_KEY, algo=algo)
188 bytes_val = enc_utils.encrypt_value(value, enc_key=ENCRYPTION_KEY, algo=algo)
189 return safe_str(bytes_val)
189 return safe_str(bytes_val)
190
190
191 def process_result_value(self, value, dialect):
191 def process_result_value(self, value, dialect):
192 """
192 """
193 Getter for retrieving value
193 Getter for retrieving value
194 """
194 """
195
195
196 import rhodecode
196 import rhodecode
197 if not value:
197 if not value:
198 return value
198 return value
199
199
200 enc_strict_mode = rhodecode.ConfigGet().get_bool('rhodecode.encrypted_values.strict', missing=True)
200 enc_strict_mode = rhodecode.ConfigGet().get_bool('rhodecode.encrypted_values.strict', missing=True)
201
201
202 bytes_val = enc_utils.decrypt_value(value, enc_key=ENCRYPTION_KEY, strict_mode=enc_strict_mode)
202 bytes_val = enc_utils.decrypt_value(value, enc_key=ENCRYPTION_KEY, strict_mode=enc_strict_mode)
203
203
204 return safe_str(bytes_val)
204 return safe_str(bytes_val)
205
205
206
206
207 class BaseModel(object):
207 class BaseModel(object):
208 """
208 """
209 Base Model for all classes
209 Base Model for all classes
210 """
210 """
211
211
212 @classmethod
212 @classmethod
213 def _get_keys(cls):
213 def _get_keys(cls):
214 """return column names for this model """
214 """return column names for this model """
215 return class_mapper(cls).c.keys()
215 return class_mapper(cls).c.keys()
216
216
217 def get_dict(self):
217 def get_dict(self):
218 """
218 """
219 return dict with keys and values corresponding
219 return dict with keys and values corresponding
220 to this model data """
220 to this model data """
221
221
222 d = {}
222 d = {}
223 for k in self._get_keys():
223 for k in self._get_keys():
224 d[k] = getattr(self, k)
224 d[k] = getattr(self, k)
225
225
226 # also use __json__() if present to get additional fields
226 # also use __json__() if present to get additional fields
227 _json_attr = getattr(self, '__json__', None)
227 _json_attr = getattr(self, '__json__', None)
228 if _json_attr:
228 if _json_attr:
229 # update with attributes from __json__
229 # update with attributes from __json__
230 if callable(_json_attr):
230 if callable(_json_attr):
231 _json_attr = _json_attr()
231 _json_attr = _json_attr()
232 for k, val in _json_attr.items():
232 for k, val in _json_attr.items():
233 d[k] = val
233 d[k] = val
234 return d
234 return d
235
235
236 def get_appstruct(self):
236 def get_appstruct(self):
237 """return list with keys and values tuples corresponding
237 """return list with keys and values tuples corresponding
238 to this model data """
238 to this model data """
239
239
240 lst = []
240 lst = []
241 for k in self._get_keys():
241 for k in self._get_keys():
242 lst.append((k, getattr(self, k),))
242 lst.append((k, getattr(self, k),))
243 return lst
243 return lst
244
244
245 def populate_obj(self, populate_dict):
245 def populate_obj(self, populate_dict):
246 """populate model with data from given populate_dict"""
246 """populate model with data from given populate_dict"""
247
247
248 for k in self._get_keys():
248 for k in self._get_keys():
249 if k in populate_dict:
249 if k in populate_dict:
250 setattr(self, k, populate_dict[k])
250 setattr(self, k, populate_dict[k])
251
251
252 @classmethod
252 @classmethod
253 def query(cls):
253 def query(cls):
254 return Session().query(cls)
254 return Session().query(cls)
255
255
256 @classmethod
256 @classmethod
257 def select(cls, custom_cls=None):
257 def select(cls, custom_cls=None):
258 """
258 """
259 stmt = cls.select().where(cls.user_id==1)
259 stmt = cls.select().where(cls.user_id==1)
260 # optionally
260 # optionally
261 stmt = cls.select(User.user_id).where(cls.user_id==1)
261 stmt = cls.select(User.user_id).where(cls.user_id==1)
262 result = cls.execute(stmt) | cls.scalars(stmt)
262 result = cls.execute(stmt) | cls.scalars(stmt)
263 """
263 """
264
264
265 if custom_cls:
265 if custom_cls:
266 stmt = select(custom_cls)
266 stmt = select(custom_cls)
267 else:
267 else:
268 stmt = select(cls)
268 stmt = select(cls)
269 return stmt
269 return stmt
270
270
271 @classmethod
271 @classmethod
272 def execute(cls, stmt):
272 def execute(cls, stmt):
273 return Session().execute(stmt)
273 return Session().execute(stmt)
274
274
275 @classmethod
275 @classmethod
276 def scalars(cls, stmt):
276 def scalars(cls, stmt):
277 return Session().scalars(stmt)
277 return Session().scalars(stmt)
278
278
279 @classmethod
279 @classmethod
280 def get(cls, id_):
280 def get(cls, id_):
281 if id_:
281 if id_:
282 return cls.query().get(id_)
282 return cls.query().get(id_)
283
283
284 @classmethod
284 @classmethod
285 def get_or_404(cls, id_):
285 def get_or_404(cls, id_):
286 from pyramid.httpexceptions import HTTPNotFound
286 from pyramid.httpexceptions import HTTPNotFound
287
287
288 try:
288 try:
289 id_ = int(id_)
289 id_ = int(id_)
290 except (TypeError, ValueError):
290 except (TypeError, ValueError):
291 raise HTTPNotFound()
291 raise HTTPNotFound()
292
292
293 res = cls.query().get(id_)
293 res = cls.query().get(id_)
294 if not res:
294 if not res:
295 raise HTTPNotFound()
295 raise HTTPNotFound()
296 return res
296 return res
297
297
298 @classmethod
298 @classmethod
299 def getAll(cls):
299 def getAll(cls):
300 # deprecated and left for backward compatibility
300 # deprecated and left for backward compatibility
301 return cls.get_all()
301 return cls.get_all()
302
302
303 @classmethod
303 @classmethod
304 def get_all(cls):
304 def get_all(cls):
305 return cls.query().all()
305 return cls.query().all()
306
306
307 @classmethod
307 @classmethod
308 def delete(cls, id_):
308 def delete(cls, id_):
309 obj = cls.query().get(id_)
309 obj = cls.query().get(id_)
310 Session().delete(obj)
310 Session().delete(obj)
311
311
312 @classmethod
312 @classmethod
313 def identity_cache(cls, session, attr_name, value):
313 def identity_cache(cls, session, attr_name, value):
314 exist_in_session = []
314 exist_in_session = []
315 for (item_cls, pkey), instance in session.identity_map.items():
315 for (item_cls, pkey), instance in session.identity_map.items():
316 if cls == item_cls and getattr(instance, attr_name) == value:
316 if cls == item_cls and getattr(instance, attr_name) == value:
317 exist_in_session.append(instance)
317 exist_in_session.append(instance)
318 if exist_in_session:
318 if exist_in_session:
319 if len(exist_in_session) == 1:
319 if len(exist_in_session) == 1:
320 return exist_in_session[0]
320 return exist_in_session[0]
321 log.exception(
321 log.exception(
322 'multiple objects with attr %s and '
322 'multiple objects with attr %s and '
323 'value %s found with same name: %r',
323 'value %s found with same name: %r',
324 attr_name, value, exist_in_session)
324 attr_name, value, exist_in_session)
325
325
326 @property
326 @property
327 def cls_name(self):
327 def cls_name(self):
328 return self.__class__.__name__
328 return self.__class__.__name__
329
329
330 def __repr__(self):
330 def __repr__(self):
331 return f'<DB:{self.cls_name}>'
331 return f'<DB:{self.cls_name}>'
332
332
333
333
334 class RhodeCodeSetting(Base, BaseModel):
334 class RhodeCodeSetting(Base, BaseModel):
335 __tablename__ = 'rhodecode_settings'
335 __tablename__ = 'rhodecode_settings'
336 __table_args__ = (
336 __table_args__ = (
337 UniqueConstraint('app_settings_name'),
337 UniqueConstraint('app_settings_name'),
338 base_table_args
338 base_table_args
339 )
339 )
340
340
341 SETTINGS_TYPES = {
341 SETTINGS_TYPES = {
342 'str': safe_str,
342 'str': safe_str,
343 'int': safe_int,
343 'int': safe_int,
344 'unicode': safe_str,
344 'unicode': safe_str,
345 'bool': str2bool,
345 'bool': str2bool,
346 'list': functools.partial(aslist, sep=',')
346 'list': functools.partial(aslist, sep=',')
347 }
347 }
348 DEFAULT_UPDATE_URL = 'https://rhodecode.com/api/v1/info/versions'
348 DEFAULT_UPDATE_URL = 'https://rhodecode.com/api/v1/info/versions'
349 GLOBAL_CONF_KEY = 'app_settings'
349 GLOBAL_CONF_KEY = 'app_settings'
350
350
351 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
351 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
352 app_settings_name = Column("app_settings_name", String(255), nullable=True, unique=None, default=None)
352 app_settings_name = Column("app_settings_name", String(255), nullable=True, unique=None, default=None)
353 _app_settings_value = Column("app_settings_value", String(4096), nullable=True, unique=None, default=None)
353 _app_settings_value = Column("app_settings_value", String(4096), nullable=True, unique=None, default=None)
354 _app_settings_type = Column("app_settings_type", String(255), nullable=True, unique=None, default=None)
354 _app_settings_type = Column("app_settings_type", String(255), nullable=True, unique=None, default=None)
355
355
356 def __init__(self, key='', val='', type='unicode'):
356 def __init__(self, key='', val='', type='unicode'):
357 self.app_settings_name = key
357 self.app_settings_name = key
358 self.app_settings_type = type
358 self.app_settings_type = type
359 self.app_settings_value = val
359 self.app_settings_value = val
360
360
361 @validates('_app_settings_value')
361 @validates('_app_settings_value')
362 def validate_settings_value(self, key, val):
362 def validate_settings_value(self, key, val):
363 assert type(val) == str
363 assert type(val) == str
364 return val
364 return val
365
365
366 @hybrid_property
366 @hybrid_property
367 def app_settings_value(self):
367 def app_settings_value(self):
368 v = self._app_settings_value
368 v = self._app_settings_value
369 _type = self.app_settings_type
369 _type = self.app_settings_type
370 if _type:
370 if _type:
371 _type = self.app_settings_type.split('.')[0]
371 _type = self.app_settings_type.split('.')[0]
372 # decode the encrypted value
372 # decode the encrypted value
373 if 'encrypted' in self.app_settings_type:
373 if 'encrypted' in self.app_settings_type:
374 cipher = EncryptedTextValue()
374 cipher = EncryptedTextValue()
375 v = safe_str(cipher.process_result_value(v, None))
375 v = safe_str(cipher.process_result_value(v, None))
376
376
377 converter = self.SETTINGS_TYPES.get(_type) or \
377 converter = self.SETTINGS_TYPES.get(_type) or \
378 self.SETTINGS_TYPES['unicode']
378 self.SETTINGS_TYPES['unicode']
379 return converter(v)
379 return converter(v)
380
380
381 @app_settings_value.setter
381 @app_settings_value.setter
382 def app_settings_value(self, val):
382 def app_settings_value(self, val):
383 """
383 """
384 Setter that will always make sure we use unicode in app_settings_value
384 Setter that will always make sure we use unicode in app_settings_value
385
385
386 :param val:
386 :param val:
387 """
387 """
388 val = safe_str(val)
388 val = safe_str(val)
389 # encode the encrypted value
389 # encode the encrypted value
390 if 'encrypted' in self.app_settings_type:
390 if 'encrypted' in self.app_settings_type:
391 cipher = EncryptedTextValue()
391 cipher = EncryptedTextValue()
392 val = safe_str(cipher.process_bind_param(val, None))
392 val = safe_str(cipher.process_bind_param(val, None))
393 self._app_settings_value = val
393 self._app_settings_value = val
394
394
395 @hybrid_property
395 @hybrid_property
396 def app_settings_type(self):
396 def app_settings_type(self):
397 return self._app_settings_type
397 return self._app_settings_type
398
398
399 @app_settings_type.setter
399 @app_settings_type.setter
400 def app_settings_type(self, val):
400 def app_settings_type(self, val):
401 if val.split('.')[0] not in self.SETTINGS_TYPES:
401 if val.split('.')[0] not in self.SETTINGS_TYPES:
402 raise Exception('type must be one of %s got %s'
402 raise Exception('type must be one of %s got %s'
403 % (self.SETTINGS_TYPES.keys(), val))
403 % (self.SETTINGS_TYPES.keys(), val))
404 self._app_settings_type = val
404 self._app_settings_type = val
405
405
406 @classmethod
406 @classmethod
407 def get_by_prefix(cls, prefix):
407 def get_by_prefix(cls, prefix):
408 return RhodeCodeSetting.query()\
408 return RhodeCodeSetting.query()\
409 .filter(RhodeCodeSetting.app_settings_name.startswith(prefix))\
409 .filter(RhodeCodeSetting.app_settings_name.startswith(prefix))\
410 .all()
410 .all()
411
411
412 def __repr__(self):
412 def __repr__(self):
413 return "<%s('%s:%s[%s]')>" % (
413 return "<%s('%s:%s[%s]')>" % (
414 self.cls_name,
414 self.cls_name,
415 self.app_settings_name, self.app_settings_value,
415 self.app_settings_name, self.app_settings_value,
416 self.app_settings_type
416 self.app_settings_type
417 )
417 )
418
418
419
419
420 class RhodeCodeUi(Base, BaseModel):
420 class RhodeCodeUi(Base, BaseModel):
421 __tablename__ = 'rhodecode_ui'
421 __tablename__ = 'rhodecode_ui'
422 __table_args__ = (
422 __table_args__ = (
423 UniqueConstraint('ui_key'),
423 UniqueConstraint('ui_key'),
424 base_table_args
424 base_table_args
425 )
425 )
426 # Sync those values with vcsserver.config.hooks
426 # Sync those values with vcsserver.config.hooks
427
427
428 HOOK_REPO_SIZE = 'changegroup.repo_size'
428 HOOK_REPO_SIZE = 'changegroup.repo_size'
429 # HG
429 # HG
430 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
430 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
431 HOOK_PULL = 'outgoing.pull_logger'
431 HOOK_PULL = 'outgoing.pull_logger'
432 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
432 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
433 HOOK_PRETX_PUSH = 'pretxnchangegroup.pre_push'
433 HOOK_PRETX_PUSH = 'pretxnchangegroup.pre_push'
434 HOOK_PUSH = 'changegroup.push_logger'
434 HOOK_PUSH = 'changegroup.push_logger'
435 HOOK_PUSH_KEY = 'pushkey.key_push'
435 HOOK_PUSH_KEY = 'pushkey.key_push'
436
436
437 HOOKS_BUILTIN = [
437 HOOKS_BUILTIN = [
438 HOOK_PRE_PULL,
438 HOOK_PRE_PULL,
439 HOOK_PULL,
439 HOOK_PULL,
440 HOOK_PRE_PUSH,
440 HOOK_PRE_PUSH,
441 HOOK_PRETX_PUSH,
441 HOOK_PRETX_PUSH,
442 HOOK_PUSH,
442 HOOK_PUSH,
443 HOOK_PUSH_KEY,
443 HOOK_PUSH_KEY,
444 ]
444 ]
445
445
446 # TODO: johbo: Unify way how hooks are configured for git and hg,
446 # TODO: johbo: Unify way how hooks are configured for git and hg,
447 # git part is currently hardcoded.
447 # git part is currently hardcoded.
448
448
449 # SVN PATTERNS
449 # SVN PATTERNS
450 SVN_BRANCH_ID = 'vcs_svn_branch'
450 SVN_BRANCH_ID = 'vcs_svn_branch'
451 SVN_TAG_ID = 'vcs_svn_tag'
451 SVN_TAG_ID = 'vcs_svn_tag'
452
452
453 ui_id = Column(
453 ui_id = Column(
454 "ui_id", Integer(), nullable=False, unique=True, default=None,
454 "ui_id", Integer(), nullable=False, unique=True, default=None,
455 primary_key=True)
455 primary_key=True)
456 ui_section = Column(
456 ui_section = Column(
457 "ui_section", String(255), nullable=True, unique=None, default=None)
457 "ui_section", String(255), nullable=True, unique=None, default=None)
458 ui_key = Column(
458 ui_key = Column(
459 "ui_key", String(255), nullable=True, unique=None, default=None)
459 "ui_key", String(255), nullable=True, unique=None, default=None)
460 ui_value = Column(
460 ui_value = Column(
461 "ui_value", String(255), nullable=True, unique=None, default=None)
461 "ui_value", String(255), nullable=True, unique=None, default=None)
462 ui_active = Column(
462 ui_active = Column(
463 "ui_active", Boolean(), nullable=True, unique=None, default=True)
463 "ui_active", Boolean(), nullable=True, unique=None, default=True)
464
464
465 def __repr__(self):
465 def __repr__(self):
466 return '<%s[%s]%s=>%s]>' % (self.cls_name, self.ui_section,
466 return '<%s[%s]%s=>%s]>' % (self.cls_name, self.ui_section,
467 self.ui_key, self.ui_value)
467 self.ui_key, self.ui_value)
468
468
469
469
470 class RepoRhodeCodeSetting(Base, BaseModel):
470 class RepoRhodeCodeSetting(Base, BaseModel):
471 __tablename__ = 'repo_rhodecode_settings'
471 __tablename__ = 'repo_rhodecode_settings'
472 __table_args__ = (
472 __table_args__ = (
473 UniqueConstraint(
473 UniqueConstraint(
474 'app_settings_name', 'repository_id',
474 'app_settings_name', 'repository_id',
475 name='uq_repo_rhodecode_setting_name_repo_id'),
475 name='uq_repo_rhodecode_setting_name_repo_id'),
476 base_table_args
476 base_table_args
477 )
477 )
478
478
479 repository_id = Column(
479 repository_id = Column(
480 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
480 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
481 nullable=False)
481 nullable=False)
482 app_settings_id = Column(
482 app_settings_id = Column(
483 "app_settings_id", Integer(), nullable=False, unique=True,
483 "app_settings_id", Integer(), nullable=False, unique=True,
484 default=None, primary_key=True)
484 default=None, primary_key=True)
485 app_settings_name = Column(
485 app_settings_name = Column(
486 "app_settings_name", String(255), nullable=True, unique=None,
486 "app_settings_name", String(255), nullable=True, unique=None,
487 default=None)
487 default=None)
488 _app_settings_value = Column(
488 _app_settings_value = Column(
489 "app_settings_value", String(4096), nullable=True, unique=None,
489 "app_settings_value", String(4096), nullable=True, unique=None,
490 default=None)
490 default=None)
491 _app_settings_type = Column(
491 _app_settings_type = Column(
492 "app_settings_type", String(255), nullable=True, unique=None,
492 "app_settings_type", String(255), nullable=True, unique=None,
493 default=None)
493 default=None)
494
494
495 repository = relationship('Repository', viewonly=True)
495 repository = relationship('Repository', viewonly=True)
496
496
497 def __init__(self, repository_id, key='', val='', type='unicode'):
497 def __init__(self, repository_id, key='', val='', type='unicode'):
498 self.repository_id = repository_id
498 self.repository_id = repository_id
499 self.app_settings_name = key
499 self.app_settings_name = key
500 self.app_settings_type = type
500 self.app_settings_type = type
501 self.app_settings_value = val
501 self.app_settings_value = val
502
502
503 @validates('_app_settings_value')
503 @validates('_app_settings_value')
504 def validate_settings_value(self, key, val):
504 def validate_settings_value(self, key, val):
505 assert type(val) == str
505 assert type(val) == str
506 return val
506 return val
507
507
508 @hybrid_property
508 @hybrid_property
509 def app_settings_value(self):
509 def app_settings_value(self):
510 v = self._app_settings_value
510 v = self._app_settings_value
511 type_ = self.app_settings_type
511 type_ = self.app_settings_type
512 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
512 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
513 converter = SETTINGS_TYPES.get(type_) or SETTINGS_TYPES['unicode']
513 converter = SETTINGS_TYPES.get(type_) or SETTINGS_TYPES['unicode']
514 return converter(v)
514 return converter(v)
515
515
516 @app_settings_value.setter
516 @app_settings_value.setter
517 def app_settings_value(self, val):
517 def app_settings_value(self, val):
518 """
518 """
519 Setter that will always make sure we use unicode in app_settings_value
519 Setter that will always make sure we use unicode in app_settings_value
520
520
521 :param val:
521 :param val:
522 """
522 """
523 self._app_settings_value = safe_str(val)
523 self._app_settings_value = safe_str(val)
524
524
525 @hybrid_property
525 @hybrid_property
526 def app_settings_type(self):
526 def app_settings_type(self):
527 return self._app_settings_type
527 return self._app_settings_type
528
528
529 @app_settings_type.setter
529 @app_settings_type.setter
530 def app_settings_type(self, val):
530 def app_settings_type(self, val):
531 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
531 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
532 if val not in SETTINGS_TYPES:
532 if val not in SETTINGS_TYPES:
533 raise Exception('type must be one of %s got %s'
533 raise Exception('type must be one of %s got %s'
534 % (SETTINGS_TYPES.keys(), val))
534 % (SETTINGS_TYPES.keys(), val))
535 self._app_settings_type = val
535 self._app_settings_type = val
536
536
537 def __repr__(self):
537 def __repr__(self):
538 return "<%s('%s:%s:%s[%s]')>" % (
538 return "<%s('%s:%s:%s[%s]')>" % (
539 self.cls_name, self.repository.repo_name,
539 self.cls_name, self.repository.repo_name,
540 self.app_settings_name, self.app_settings_value,
540 self.app_settings_name, self.app_settings_value,
541 self.app_settings_type
541 self.app_settings_type
542 )
542 )
543
543
544
544
545 class RepoRhodeCodeUi(Base, BaseModel):
545 class RepoRhodeCodeUi(Base, BaseModel):
546 __tablename__ = 'repo_rhodecode_ui'
546 __tablename__ = 'repo_rhodecode_ui'
547 __table_args__ = (
547 __table_args__ = (
548 UniqueConstraint(
548 UniqueConstraint(
549 'repository_id', 'ui_section', 'ui_key',
549 'repository_id', 'ui_section', 'ui_key',
550 name='uq_repo_rhodecode_ui_repository_id_section_key'),
550 name='uq_repo_rhodecode_ui_repository_id_section_key'),
551 base_table_args
551 base_table_args
552 )
552 )
553
553
554 repository_id = Column(
554 repository_id = Column(
555 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
555 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
556 nullable=False)
556 nullable=False)
557 ui_id = Column(
557 ui_id = Column(
558 "ui_id", Integer(), nullable=False, unique=True, default=None,
558 "ui_id", Integer(), nullable=False, unique=True, default=None,
559 primary_key=True)
559 primary_key=True)
560 ui_section = Column(
560 ui_section = Column(
561 "ui_section", String(255), nullable=True, unique=None, default=None)
561 "ui_section", String(255), nullable=True, unique=None, default=None)
562 ui_key = Column(
562 ui_key = Column(
563 "ui_key", String(255), nullable=True, unique=None, default=None)
563 "ui_key", String(255), nullable=True, unique=None, default=None)
564 ui_value = Column(
564 ui_value = Column(
565 "ui_value", String(255), nullable=True, unique=None, default=None)
565 "ui_value", String(255), nullable=True, unique=None, default=None)
566 ui_active = Column(
566 ui_active = Column(
567 "ui_active", Boolean(), nullable=True, unique=None, default=True)
567 "ui_active", Boolean(), nullable=True, unique=None, default=True)
568
568
569 repository = relationship('Repository', viewonly=True)
569 repository = relationship('Repository', viewonly=True)
570
570
571 def __repr__(self):
571 def __repr__(self):
572 return '<%s[%s:%s]%s=>%s]>' % (
572 return '<%s[%s:%s]%s=>%s]>' % (
573 self.cls_name, self.repository.repo_name,
573 self.cls_name, self.repository.repo_name,
574 self.ui_section, self.ui_key, self.ui_value)
574 self.ui_section, self.ui_key, self.ui_value)
575
575
576
576
577 class User(Base, BaseModel):
577 class User(Base, BaseModel):
578 __tablename__ = 'users'
578 __tablename__ = 'users'
579 __table_args__ = (
579 __table_args__ = (
580 UniqueConstraint('username'), UniqueConstraint('email'),
580 UniqueConstraint('username'), UniqueConstraint('email'),
581 Index('u_username_idx', 'username'),
581 Index('u_username_idx', 'username'),
582 Index('u_email_idx', 'email'),
582 Index('u_email_idx', 'email'),
583 base_table_args
583 base_table_args
584 )
584 )
585
585
586 DEFAULT_USER = 'default'
586 DEFAULT_USER = 'default'
587 DEFAULT_USER_EMAIL = 'anonymous@rhodecode.org'
587 DEFAULT_USER_EMAIL = 'anonymous@rhodecode.org'
588 DEFAULT_GRAVATAR_URL = 'https://secure.gravatar.com/avatar/{md5email}?d=identicon&s={size}'
588 DEFAULT_GRAVATAR_URL = 'https://secure.gravatar.com/avatar/{md5email}?d=identicon&s={size}'
589
589
590 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
590 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
591 username = Column("username", String(255), nullable=True, unique=None, default=None)
591 username = Column("username", String(255), nullable=True, unique=None, default=None)
592 password = Column("password", String(255), nullable=True, unique=None, default=None)
592 password = Column("password", String(255), nullable=True, unique=None, default=None)
593 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
593 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
594 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
594 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
595 name = Column("firstname", String(255), nullable=True, unique=None, default=None)
595 name = Column("firstname", String(255), nullable=True, unique=None, default=None)
596 lastname = Column("lastname", String(255), nullable=True, unique=None, default=None)
596 lastname = Column("lastname", String(255), nullable=True, unique=None, default=None)
597 _email = Column("email", String(255), nullable=True, unique=None, default=None)
597 _email = Column("email", String(255), nullable=True, unique=None, default=None)
598 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
598 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
599 last_activity = Column('last_activity', DateTime(timezone=False), nullable=True, unique=None, default=None)
599 last_activity = Column('last_activity', DateTime(timezone=False), nullable=True, unique=None, default=None)
600 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
600 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
601
601
602 extern_type = Column("extern_type", String(255), nullable=True, unique=None, default=None)
602 extern_type = Column("extern_type", String(255), nullable=True, unique=None, default=None)
603 extern_name = Column("extern_name", String(255), nullable=True, unique=None, default=None)
603 extern_name = Column("extern_name", String(255), nullable=True, unique=None, default=None)
604 _api_key = Column("api_key", String(255), nullable=True, unique=None, default=None)
604 _api_key = Column("api_key", String(255), nullable=True, unique=None, default=None)
605 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
605 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
606 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
606 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
607 _user_data = Column("user_data", LargeBinary(), nullable=True) # JSON data
607 _user_data = Column("user_data", LargeBinary(), nullable=True) # JSON data
608
608
609 user_log = relationship('UserLog', back_populates='user')
609 user_log = relationship('UserLog', back_populates='user')
610 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all, delete-orphan')
610 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all, delete-orphan')
611
611
612 repositories = relationship('Repository', back_populates='user')
612 repositories = relationship('Repository', back_populates='user')
613 repository_groups = relationship('RepoGroup', back_populates='user')
613 repository_groups = relationship('RepoGroup', back_populates='user')
614 user_groups = relationship('UserGroup', back_populates='user')
614 user_groups = relationship('UserGroup', back_populates='user')
615
615
616 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all', back_populates='follows_user')
616 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all', back_populates='follows_user')
617 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all', back_populates='user')
617 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all', back_populates='user')
618
618
619 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all, delete-orphan')
619 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all, delete-orphan')
620 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all, delete-orphan', back_populates='user')
620 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all, delete-orphan', back_populates='user')
621 user_group_to_perm = relationship('UserUserGroupToPerm', primaryjoin='UserUserGroupToPerm.user_id==User.user_id', cascade='all, delete-orphan', back_populates='user')
621 user_group_to_perm = relationship('UserUserGroupToPerm', primaryjoin='UserUserGroupToPerm.user_id==User.user_id', cascade='all, delete-orphan', back_populates='user')
622
622
623 group_member = relationship('UserGroupMember', cascade='all', back_populates='user')
623 group_member = relationship('UserGroupMember', cascade='all', back_populates='user')
624
624
625 notifications = relationship('UserNotification', cascade='all', back_populates='user')
625 notifications = relationship('UserNotification', cascade='all', back_populates='user')
626 # notifications assigned to this user
626 # notifications assigned to this user
627 user_created_notifications = relationship('Notification', cascade='all', back_populates='created_by_user')
627 user_created_notifications = relationship('Notification', cascade='all', back_populates='created_by_user')
628 # comments created by this user
628 # comments created by this user
629 user_comments = relationship('ChangesetComment', cascade='all', back_populates='author')
629 user_comments = relationship('ChangesetComment', cascade='all', back_populates='author')
630 # user profile extra info
630 # user profile extra info
631 user_emails = relationship('UserEmailMap', cascade='all', back_populates='user')
631 user_emails = relationship('UserEmailMap', cascade='all', back_populates='user')
632 user_ip_map = relationship('UserIpMap', cascade='all', back_populates='user')
632 user_ip_map = relationship('UserIpMap', cascade='all', back_populates='user')
633 user_auth_tokens = relationship('UserApiKeys', cascade='all', back_populates='user')
633 user_auth_tokens = relationship('UserApiKeys', cascade='all', back_populates='user')
634 user_ssh_keys = relationship('UserSshKeys', cascade='all', back_populates='user')
634 user_ssh_keys = relationship('UserSshKeys', cascade='all', back_populates='user')
635
635
636 # gists
636 # gists
637 user_gists = relationship('Gist', cascade='all', back_populates='owner')
637 user_gists = relationship('Gist', cascade='all', back_populates='owner')
638 # user pull requests
638 # user pull requests
639 user_pull_requests = relationship('PullRequest', cascade='all', back_populates='author')
639 user_pull_requests = relationship('PullRequest', cascade='all', back_populates='author')
640
640
641 # external identities
641 # external identities
642 external_identities = relationship('ExternalIdentity', primaryjoin="User.user_id==ExternalIdentity.local_user_id", cascade='all')
642 external_identities = relationship('ExternalIdentity', primaryjoin="User.user_id==ExternalIdentity.local_user_id", cascade='all')
643 # review rules
643 # review rules
644 user_review_rules = relationship('RepoReviewRuleUser', cascade='all', back_populates='user')
644 user_review_rules = relationship('RepoReviewRuleUser', cascade='all', back_populates='user')
645
645
646 # artifacts owned
646 # artifacts owned
647 artifacts = relationship('FileStore', primaryjoin='FileStore.user_id==User.user_id', back_populates='upload_user')
647 artifacts = relationship('FileStore', primaryjoin='FileStore.user_id==User.user_id', back_populates='upload_user')
648
648
649 # no cascade, set NULL
649 # no cascade, set NULL
650 scope_artifacts = relationship('FileStore', primaryjoin='FileStore.scope_user_id==User.user_id', cascade='', back_populates='user')
650 scope_artifacts = relationship('FileStore', primaryjoin='FileStore.scope_user_id==User.user_id', cascade='', back_populates='user')
651
651
652 def __repr__(self):
652 def __repr__(self):
653 return f"<{self.cls_name}('id={self.user_id}, username={self.username}')>"
653 return f"<{self.cls_name}('id={self.user_id}, username={self.username}')>"
654
654
655 @hybrid_property
655 @hybrid_property
656 def email(self):
656 def email(self):
657 return self._email
657 return self._email
658
658
659 @email.setter
659 @email.setter
660 def email(self, val):
660 def email(self, val):
661 self._email = val.lower() if val else None
661 self._email = val.lower() if val else None
662
662
663 @hybrid_property
663 @hybrid_property
664 def first_name(self):
664 def first_name(self):
665 from rhodecode.lib import helpers as h
665 from rhodecode.lib import helpers as h
666 if self.name:
666 if self.name:
667 return h.escape(self.name)
667 return h.escape(self.name)
668 return self.name
668 return self.name
669
669
670 @hybrid_property
670 @hybrid_property
671 def last_name(self):
671 def last_name(self):
672 from rhodecode.lib import helpers as h
672 from rhodecode.lib import helpers as h
673 if self.lastname:
673 if self.lastname:
674 return h.escape(self.lastname)
674 return h.escape(self.lastname)
675 return self.lastname
675 return self.lastname
676
676
677 @hybrid_property
677 @hybrid_property
678 def api_key(self):
678 def api_key(self):
679 """
679 """
680 Fetch if exist an auth-token with role ALL connected to this user
680 Fetch if exist an auth-token with role ALL connected to this user
681 """
681 """
682 user_auth_token = UserApiKeys.query()\
682 user_auth_token = UserApiKeys.query()\
683 .filter(UserApiKeys.user_id == self.user_id)\
683 .filter(UserApiKeys.user_id == self.user_id)\
684 .filter(or_(UserApiKeys.expires == -1,
684 .filter(or_(UserApiKeys.expires == -1,
685 UserApiKeys.expires >= time.time()))\
685 UserApiKeys.expires >= time.time()))\
686 .filter(UserApiKeys.role == UserApiKeys.ROLE_ALL).first()
686 .filter(UserApiKeys.role == UserApiKeys.ROLE_ALL).first()
687 if user_auth_token:
687 if user_auth_token:
688 user_auth_token = user_auth_token.api_key
688 user_auth_token = user_auth_token.api_key
689
689
690 return user_auth_token
690 return user_auth_token
691
691
692 @api_key.setter
692 @api_key.setter
693 def api_key(self, val):
693 def api_key(self, val):
694 # don't allow to set API key this is deprecated for now
694 # don't allow to set API key this is deprecated for now
695 self._api_key = None
695 self._api_key = None
696
696
697 @property
697 @property
698 def reviewer_pull_requests(self):
698 def reviewer_pull_requests(self):
699 return PullRequestReviewers.query() \
699 return PullRequestReviewers.query() \
700 .options(joinedload(PullRequestReviewers.pull_request)) \
700 .options(joinedload(PullRequestReviewers.pull_request)) \
701 .filter(PullRequestReviewers.user_id == self.user_id) \
701 .filter(PullRequestReviewers.user_id == self.user_id) \
702 .all()
702 .all()
703
703
704 @property
704 @property
705 def firstname(self):
705 def firstname(self):
706 # alias for future
706 # alias for future
707 return self.name
707 return self.name
708
708
709 @property
709 @property
710 def emails(self):
710 def emails(self):
711 other = UserEmailMap.query()\
711 other = UserEmailMap.query()\
712 .filter(UserEmailMap.user == self) \
712 .filter(UserEmailMap.user == self) \
713 .order_by(UserEmailMap.email_id.asc()) \
713 .order_by(UserEmailMap.email_id.asc()) \
714 .all()
714 .all()
715 return [self.email] + [x.email for x in other]
715 return [self.email] + [x.email for x in other]
716
716
717 def emails_cached(self):
717 def emails_cached(self):
718 emails = []
718 emails = []
719 if self.user_id != self.get_default_user_id():
719 if self.user_id != self.get_default_user_id():
720 emails = UserEmailMap.query()\
720 emails = UserEmailMap.query()\
721 .filter(UserEmailMap.user == self) \
721 .filter(UserEmailMap.user == self) \
722 .order_by(UserEmailMap.email_id.asc())
722 .order_by(UserEmailMap.email_id.asc())
723
723
724 emails = emails.options(
724 emails = emails.options(
725 FromCache("sql_cache_short", f"get_user_{self.user_id}_emails")
725 FromCache("sql_cache_short", f"get_user_{self.user_id}_emails")
726 )
726 )
727
727
728 return [self.email] + [x.email for x in emails]
728 return [self.email] + [x.email for x in emails]
729
729
730 @property
730 @property
731 def auth_tokens(self):
731 def auth_tokens(self):
732 auth_tokens = self.get_auth_tokens()
732 auth_tokens = self.get_auth_tokens()
733 return [x.api_key for x in auth_tokens]
733 return [x.api_key for x in auth_tokens]
734
734
735 def get_auth_tokens(self):
735 def get_auth_tokens(self):
736 return UserApiKeys.query()\
736 return UserApiKeys.query()\
737 .filter(UserApiKeys.user == self)\
737 .filter(UserApiKeys.user == self)\
738 .order_by(UserApiKeys.user_api_key_id.asc())\
738 .order_by(UserApiKeys.user_api_key_id.asc())\
739 .all()
739 .all()
740
740
741 @LazyProperty
741 @LazyProperty
742 def feed_token(self):
742 def feed_token(self):
743 return self.get_feed_token()
743 return self.get_feed_token()
744
744
745 def get_feed_token(self, cache=True):
745 def get_feed_token(self, cache=True):
746 feed_tokens = UserApiKeys.query()\
746 feed_tokens = UserApiKeys.query()\
747 .filter(UserApiKeys.user == self)\
747 .filter(UserApiKeys.user == self)\
748 .filter(UserApiKeys.role == UserApiKeys.ROLE_FEED)
748 .filter(UserApiKeys.role == UserApiKeys.ROLE_FEED)
749 if cache:
749 if cache:
750 feed_tokens = feed_tokens.options(
750 feed_tokens = feed_tokens.options(
751 FromCache("sql_cache_short", f"get_user_feed_token_{self.user_id}"))
751 FromCache("sql_cache_short", f"get_user_feed_token_{self.user_id}"))
752
752
753 feed_tokens = feed_tokens.all()
753 feed_tokens = feed_tokens.all()
754 if feed_tokens:
754 if feed_tokens:
755 return feed_tokens[0].api_key
755 return feed_tokens[0].api_key
756 return 'NO_FEED_TOKEN_AVAILABLE'
756 return 'NO_FEED_TOKEN_AVAILABLE'
757
757
758 @LazyProperty
758 @LazyProperty
759 def artifact_token(self):
759 def artifact_token(self):
760 return self.get_artifact_token()
760 return self.get_artifact_token()
761
761
762 def get_artifact_token(self, cache=True):
762 def get_artifact_token(self, cache=True):
763 artifacts_tokens = UserApiKeys.query()\
763 artifacts_tokens = UserApiKeys.query()\
764 .filter(UserApiKeys.user == self) \
764 .filter(UserApiKeys.user == self) \
765 .filter(or_(UserApiKeys.expires == -1,
765 .filter(or_(UserApiKeys.expires == -1,
766 UserApiKeys.expires >= time.time())) \
766 UserApiKeys.expires >= time.time())) \
767 .filter(UserApiKeys.role == UserApiKeys.ROLE_ARTIFACT_DOWNLOAD)
767 .filter(UserApiKeys.role == UserApiKeys.ROLE_ARTIFACT_DOWNLOAD)
768
768
769 if cache:
769 if cache:
770 artifacts_tokens = artifacts_tokens.options(
770 artifacts_tokens = artifacts_tokens.options(
771 FromCache("sql_cache_short", f"get_user_artifact_token_{self.user_id}"))
771 FromCache("sql_cache_short", f"get_user_artifact_token_{self.user_id}"))
772
772
773 artifacts_tokens = artifacts_tokens.all()
773 artifacts_tokens = artifacts_tokens.all()
774 if artifacts_tokens:
774 if artifacts_tokens:
775 return artifacts_tokens[0].api_key
775 return artifacts_tokens[0].api_key
776 return 'NO_ARTIFACT_TOKEN_AVAILABLE'
776 return 'NO_ARTIFACT_TOKEN_AVAILABLE'
777
777
778 def get_or_create_artifact_token(self):
778 def get_or_create_artifact_token(self):
779 artifacts_tokens = UserApiKeys.query()\
779 artifacts_tokens = UserApiKeys.query()\
780 .filter(UserApiKeys.user == self) \
780 .filter(UserApiKeys.user == self) \
781 .filter(or_(UserApiKeys.expires == -1,
781 .filter(or_(UserApiKeys.expires == -1,
782 UserApiKeys.expires >= time.time())) \
782 UserApiKeys.expires >= time.time())) \
783 .filter(UserApiKeys.role == UserApiKeys.ROLE_ARTIFACT_DOWNLOAD)
783 .filter(UserApiKeys.role == UserApiKeys.ROLE_ARTIFACT_DOWNLOAD)
784
784
785 artifacts_tokens = artifacts_tokens.all()
785 artifacts_tokens = artifacts_tokens.all()
786 if artifacts_tokens:
786 if artifacts_tokens:
787 return artifacts_tokens[0].api_key
787 return artifacts_tokens[0].api_key
788 else:
788 else:
789 from rhodecode.model.auth_token import AuthTokenModel
789 from rhodecode.model.auth_token import AuthTokenModel
790 artifact_token = AuthTokenModel().create(
790 artifact_token = AuthTokenModel().create(
791 self, 'auto-generated-artifact-token',
791 self, 'auto-generated-artifact-token',
792 lifetime=-1, role=UserApiKeys.ROLE_ARTIFACT_DOWNLOAD)
792 lifetime=-1, role=UserApiKeys.ROLE_ARTIFACT_DOWNLOAD)
793 Session.commit()
793 Session.commit()
794 return artifact_token.api_key
794 return artifact_token.api_key
795
795
796 @classmethod
796 @classmethod
797 def get(cls, user_id, cache=False):
797 def get(cls, user_id, cache=False):
798 if not user_id:
798 if not user_id:
799 return
799 return
800
800
801 user = cls.query()
801 user = cls.query()
802 if cache:
802 if cache:
803 user = user.options(
803 user = user.options(
804 FromCache("sql_cache_short", f"get_users_{user_id}"))
804 FromCache("sql_cache_short", f"get_users_{user_id}"))
805 return user.get(user_id)
805 return user.get(user_id)
806
806
807 @classmethod
807 @classmethod
808 def extra_valid_auth_tokens(cls, user, role=None):
808 def extra_valid_auth_tokens(cls, user, role=None):
809 tokens = UserApiKeys.query().filter(UserApiKeys.user == user)\
809 tokens = UserApiKeys.query().filter(UserApiKeys.user == user)\
810 .filter(or_(UserApiKeys.expires == -1,
810 .filter(or_(UserApiKeys.expires == -1,
811 UserApiKeys.expires >= time.time()))
811 UserApiKeys.expires >= time.time()))
812 if role:
812 if role:
813 tokens = tokens.filter(or_(UserApiKeys.role == role,
813 tokens = tokens.filter(or_(UserApiKeys.role == role,
814 UserApiKeys.role == UserApiKeys.ROLE_ALL))
814 UserApiKeys.role == UserApiKeys.ROLE_ALL))
815 return tokens.all()
815 return tokens.all()
816
816
817 def authenticate_by_token(self, auth_token, roles=None, scope_repo_id=None):
817 def authenticate_by_token(self, auth_token, roles=None, scope_repo_id=None):
818 from rhodecode.lib import auth
818 from rhodecode.lib import auth
819
819
820 log.debug('Trying to authenticate user: %s via auth-token, '
820 log.debug('Trying to authenticate user: %s via auth-token, '
821 'and roles: %s', self, roles)
821 'and roles: %s', self, roles)
822
822
823 if not auth_token:
823 if not auth_token:
824 return False
824 return False
825
825
826 roles = (roles or []) + [UserApiKeys.ROLE_ALL]
826 roles = (roles or []) + [UserApiKeys.ROLE_ALL]
827 tokens_q = UserApiKeys.query()\
827 tokens_q = UserApiKeys.query()\
828 .filter(UserApiKeys.user_id == self.user_id)\
828 .filter(UserApiKeys.user_id == self.user_id)\
829 .filter(or_(UserApiKeys.expires == -1,
829 .filter(or_(UserApiKeys.expires == -1,
830 UserApiKeys.expires >= time.time()))
830 UserApiKeys.expires >= time.time()))
831
831
832 tokens_q = tokens_q.filter(UserApiKeys.role.in_(roles))
832 tokens_q = tokens_q.filter(UserApiKeys.role.in_(roles))
833
833
834 crypto_backend = auth.crypto_backend()
834 crypto_backend = auth.crypto_backend()
835 enc_token_map = {}
835 enc_token_map = {}
836 plain_token_map = {}
836 plain_token_map = {}
837 for token in tokens_q:
837 for token in tokens_q:
838 if token.api_key.startswith(crypto_backend.ENC_PREF):
838 if token.api_key.startswith(crypto_backend.ENC_PREF):
839 enc_token_map[token.api_key] = token
839 enc_token_map[token.api_key] = token
840 else:
840 else:
841 plain_token_map[token.api_key] = token
841 plain_token_map[token.api_key] = token
842 log.debug(
842 log.debug(
843 'Found %s plain and %s encrypted tokens to check for authentication for this user',
843 'Found %s plain and %s encrypted tokens to check for authentication for this user',
844 len(plain_token_map), len(enc_token_map))
844 len(plain_token_map), len(enc_token_map))
845
845
846 # plain token match comes first
846 # plain token match comes first
847 match = plain_token_map.get(auth_token)
847 match = plain_token_map.get(auth_token)
848
848
849 # check encrypted tokens now
849 # check encrypted tokens now
850 if not match:
850 if not match:
851 for token_hash, token in enc_token_map.items():
851 for token_hash, token in enc_token_map.items():
852 # NOTE(marcink): this is expensive to calculate, but most secure
852 # NOTE(marcink): this is expensive to calculate, but most secure
853 if crypto_backend.hash_check(auth_token, token_hash):
853 if crypto_backend.hash_check(auth_token, token_hash):
854 match = token
854 match = token
855 break
855 break
856
856
857 if match:
857 if match:
858 log.debug('Found matching token %s', match)
858 log.debug('Found matching token %s', match)
859 if match.repo_id:
859 if match.repo_id:
860 log.debug('Found scope, checking for scope match of token %s', match)
860 log.debug('Found scope, checking for scope match of token %s', match)
861 if match.repo_id == scope_repo_id:
861 if match.repo_id == scope_repo_id:
862 return True
862 return True
863 else:
863 else:
864 log.debug(
864 log.debug(
865 'AUTH_TOKEN: scope mismatch, token has a set repo scope: %s, '
865 'AUTH_TOKEN: scope mismatch, token has a set repo scope: %s, '
866 'and calling scope is:%s, skipping further checks',
866 'and calling scope is:%s, skipping further checks',
867 match.repo, scope_repo_id)
867 match.repo, scope_repo_id)
868 return False
868 return False
869 else:
869 else:
870 return True
870 return True
871
871
872 return False
872 return False
873
873
874 @property
874 @property
875 def ip_addresses(self):
875 def ip_addresses(self):
876 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
876 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
877 return [x.ip_addr for x in ret]
877 return [x.ip_addr for x in ret]
878
878
879 @property
879 @property
880 def username_and_name(self):
880 def username_and_name(self):
881 return f'{self.username} ({self.first_name} {self.last_name})'
881 return f'{self.username} ({self.first_name} {self.last_name})'
882
882
883 @property
883 @property
884 def username_or_name_or_email(self):
884 def username_or_name_or_email(self):
885 full_name = self.full_name if self.full_name != ' ' else None
885 full_name = self.full_name if self.full_name != ' ' else None
886 return self.username or full_name or self.email
886 return self.username or full_name or self.email
887
887
888 @property
888 @property
889 def full_name(self):
889 def full_name(self):
890 return f'{self.first_name} {self.last_name}'
890 return f'{self.first_name} {self.last_name}'
891
891
892 @property
892 @property
893 def full_name_or_username(self):
893 def full_name_or_username(self):
894 return (f'{self.first_name} {self.last_name}'
894 return (f'{self.first_name} {self.last_name}'
895 if (self.first_name and self.last_name) else self.username)
895 if (self.first_name and self.last_name) else self.username)
896
896
897 @property
897 @property
898 def full_contact(self):
898 def full_contact(self):
899 return f'{self.first_name} {self.last_name} <{self.email}>'
899 return f'{self.first_name} {self.last_name} <{self.email}>'
900
900
901 @property
901 @property
902 def short_contact(self):
902 def short_contact(self):
903 return f'{self.first_name} {self.last_name}'
903 return f'{self.first_name} {self.last_name}'
904
904
905 @property
905 @property
906 def is_admin(self):
906 def is_admin(self):
907 return self.admin
907 return self.admin
908
908
909 @property
909 @property
910 def language(self):
910 def language(self):
911 return self.user_data.get('language')
911 return self.user_data.get('language')
912
912
913 def AuthUser(self, **kwargs):
913 def AuthUser(self, **kwargs):
914 """
914 """
915 Returns instance of AuthUser for this user
915 Returns instance of AuthUser for this user
916 """
916 """
917 from rhodecode.lib.auth import AuthUser
917 from rhodecode.lib.auth import AuthUser
918 return AuthUser(user_id=self.user_id, username=self.username, **kwargs)
918 return AuthUser(user_id=self.user_id, username=self.username, **kwargs)
919
919
920 @hybrid_property
920 @hybrid_property
921 def user_data(self):
921 def user_data(self):
922 if not self._user_data:
922 if not self._user_data:
923 return {}
923 return {}
924
924
925 try:
925 try:
926 return json.loads(self._user_data) or {}
926 return json.loads(self._user_data) or {}
927 except TypeError:
927 except TypeError:
928 return {}
928 return {}
929
929
930 @user_data.setter
930 @user_data.setter
931 def user_data(self, val):
931 def user_data(self, val):
932 if not isinstance(val, dict):
932 if not isinstance(val, dict):
933 raise Exception('user_data must be dict, got %s' % type(val))
933 raise Exception('user_data must be dict, got %s' % type(val))
934 try:
934 try:
935 self._user_data = safe_bytes(json.dumps(val))
935 self._user_data = safe_bytes(json.dumps(val))
936 except Exception:
936 except Exception:
937 log.error(traceback.format_exc())
937 log.error(traceback.format_exc())
938
938
939 @classmethod
939 @classmethod
940 def get_by_username(cls, username, case_insensitive=False,
940 def get_by_username(cls, username, case_insensitive=False,
941 cache=False):
941 cache=False):
942
942
943 if case_insensitive:
943 if case_insensitive:
944 q = cls.select().where(
944 q = cls.select().where(
945 func.lower(cls.username) == func.lower(username))
945 func.lower(cls.username) == func.lower(username))
946 else:
946 else:
947 q = cls.select().where(cls.username == username)
947 q = cls.select().where(cls.username == username)
948
948
949 if cache:
949 if cache:
950 hash_key = _hash_key(username)
950 hash_key = _hash_key(username)
951 q = q.options(
951 q = q.options(
952 FromCache("sql_cache_short", f"get_user_by_name_{hash_key}"))
952 FromCache("sql_cache_short", f"get_user_by_name_{hash_key}"))
953
953
954 return cls.execute(q).scalar_one_or_none()
954 return cls.execute(q).scalar_one_or_none()
955
955
956 @classmethod
956 @classmethod
957 def get_by_auth_token(cls, auth_token, cache=False):
957 def get_by_auth_token(cls, auth_token, cache=False):
958
958
959 q = cls.select(User)\
959 q = cls.select(User)\
960 .join(UserApiKeys)\
960 .join(UserApiKeys)\
961 .where(UserApiKeys.api_key == auth_token)\
961 .where(UserApiKeys.api_key == auth_token)\
962 .where(or_(UserApiKeys.expires == -1,
962 .where(or_(UserApiKeys.expires == -1,
963 UserApiKeys.expires >= time.time()))
963 UserApiKeys.expires >= time.time()))
964
964
965 if cache:
965 if cache:
966 q = q.options(
966 q = q.options(
967 FromCache("sql_cache_short", f"get_auth_token_{auth_token}"))
967 FromCache("sql_cache_short", f"get_auth_token_{auth_token}"))
968
968
969 matched_user = cls.execute(q).scalar_one_or_none()
969 matched_user = cls.execute(q).scalar_one_or_none()
970
970
971 return matched_user
971 return matched_user
972
972
973 @classmethod
973 @classmethod
974 def get_by_email(cls, email, case_insensitive=False, cache=False):
974 def get_by_email(cls, email, case_insensitive=False, cache=False):
975
975
976 if case_insensitive:
976 if case_insensitive:
977 q = cls.select().where(func.lower(cls.email) == func.lower(email))
977 q = cls.select().where(func.lower(cls.email) == func.lower(email))
978 else:
978 else:
979 q = cls.select().where(cls.email == email)
979 q = cls.select().where(cls.email == email)
980
980
981 if cache:
981 if cache:
982 email_key = _hash_key(email)
982 email_key = _hash_key(email)
983 q = q.options(
983 q = q.options(
984 FromCache("sql_cache_short", f"get_email_key_{email_key}"))
984 FromCache("sql_cache_short", f"get_email_key_{email_key}"))
985
985
986 ret = cls.execute(q).scalar_one_or_none()
986 ret = cls.execute(q).scalar_one_or_none()
987
987
988 if ret is None:
988 if ret is None:
989 q = cls.select(UserEmailMap)
989 q = cls.select(UserEmailMap)
990 # try fetching in alternate email map
990 # try fetching in alternate email map
991 if case_insensitive:
991 if case_insensitive:
992 q = q.where(func.lower(UserEmailMap.email) == func.lower(email))
992 q = q.where(func.lower(UserEmailMap.email) == func.lower(email))
993 else:
993 else:
994 q = q.where(UserEmailMap.email == email)
994 q = q.where(UserEmailMap.email == email)
995 q = q.options(joinedload(UserEmailMap.user))
995 q = q.options(joinedload(UserEmailMap.user))
996 if cache:
996 if cache:
997 q = q.options(
997 q = q.options(
998 FromCache("sql_cache_short", f"get_email_map_key_{email_key}"))
998 FromCache("sql_cache_short", f"get_email_map_key_{email_key}"))
999
999
1000 result = cls.execute(q).scalar_one_or_none()
1000 result = cls.execute(q).scalar_one_or_none()
1001 ret = getattr(result, 'user', None)
1001 ret = getattr(result, 'user', None)
1002
1002
1003 return ret
1003 return ret
1004
1004
1005 @classmethod
1005 @classmethod
1006 def get_from_cs_author(cls, author):
1006 def get_from_cs_author(cls, author):
1007 """
1007 """
1008 Tries to get User objects out of commit author string
1008 Tries to get User objects out of commit author string
1009
1009
1010 :param author:
1010 :param author:
1011 """
1011 """
1012 from rhodecode.lib.helpers import email, author_name
1012 from rhodecode.lib.helpers import email, author_name
1013 # Valid email in the attribute passed, see if they're in the system
1013 # Valid email in the attribute passed, see if they're in the system
1014 _email = email(author)
1014 _email = email(author)
1015 if _email:
1015 if _email:
1016 user = cls.get_by_email(_email, case_insensitive=True)
1016 user = cls.get_by_email(_email, case_insensitive=True)
1017 if user:
1017 if user:
1018 return user
1018 return user
1019 # Maybe we can match by username?
1019 # Maybe we can match by username?
1020 _author = author_name(author)
1020 _author = author_name(author)
1021 user = cls.get_by_username(_author, case_insensitive=True)
1021 user = cls.get_by_username(_author, case_insensitive=True)
1022 if user:
1022 if user:
1023 return user
1023 return user
1024
1024
1025 def update_userdata(self, **kwargs):
1025 def update_userdata(self, **kwargs):
1026 usr = self
1026 usr = self
1027 old = usr.user_data
1027 old = usr.user_data
1028 old.update(**kwargs)
1028 old.update(**kwargs)
1029 usr.user_data = old
1029 usr.user_data = old
1030 Session().add(usr)
1030 Session().add(usr)
1031 log.debug('updated userdata with %s', kwargs)
1031 log.debug('updated userdata with %s', kwargs)
1032
1032
1033 def update_lastlogin(self):
1033 def update_lastlogin(self):
1034 """Update user lastlogin"""
1034 """Update user lastlogin"""
1035 self.last_login = datetime.datetime.now()
1035 self.last_login = datetime.datetime.now()
1036 Session().add(self)
1036 Session().add(self)
1037 log.debug('updated user %s lastlogin', self.username)
1037 log.debug('updated user %s lastlogin', self.username)
1038
1038
1039 def update_password(self, new_password):
1039 def update_password(self, new_password):
1040 from rhodecode.lib.auth import get_crypt_password
1040 from rhodecode.lib.auth import get_crypt_password
1041
1041
1042 self.password = get_crypt_password(new_password)
1042 self.password = get_crypt_password(new_password)
1043 Session().add(self)
1043 Session().add(self)
1044
1044
1045 @classmethod
1045 @classmethod
1046 def get_first_super_admin(cls):
1046 def get_first_super_admin(cls):
1047 stmt = cls.select().where(User.admin == true()).order_by(User.user_id.asc())
1047 stmt = cls.select().where(User.admin == true()).order_by(User.user_id.asc())
1048 user = cls.scalars(stmt).first()
1048 user = cls.scalars(stmt).first()
1049
1049
1050 if user is None:
1050 if user is None:
1051 raise Exception('FATAL: Missing administrative account!')
1051 raise Exception('FATAL: Missing administrative account!')
1052 return user
1052 return user
1053
1053
1054 @classmethod
1054 @classmethod
1055 def get_all_super_admins(cls, only_active=False):
1055 def get_all_super_admins(cls, only_active=False):
1056 """
1056 """
1057 Returns all admin accounts sorted by username
1057 Returns all admin accounts sorted by username
1058 """
1058 """
1059 qry = User.query().filter(User.admin == true()).order_by(User.username.asc())
1059 qry = User.query().filter(User.admin == true()).order_by(User.username.asc())
1060 if only_active:
1060 if only_active:
1061 qry = qry.filter(User.active == true())
1061 qry = qry.filter(User.active == true())
1062 return qry.all()
1062 return qry.all()
1063
1063
1064 @classmethod
1064 @classmethod
1065 def get_all_user_ids(cls, only_active=True):
1065 def get_all_user_ids(cls, only_active=True):
1066 """
1066 """
1067 Returns all users IDs
1067 Returns all users IDs
1068 """
1068 """
1069 qry = Session().query(User.user_id)
1069 qry = Session().query(User.user_id)
1070
1070
1071 if only_active:
1071 if only_active:
1072 qry = qry.filter(User.active == true())
1072 qry = qry.filter(User.active == true())
1073 return [x.user_id for x in qry]
1073 return [x.user_id for x in qry]
1074
1074
1075 @classmethod
1075 @classmethod
1076 def get_default_user(cls, cache=False, refresh=False):
1076 def get_default_user(cls, cache=False, refresh=False):
1077 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
1077 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
1078 if user is None:
1078 if user is None:
1079 raise Exception('FATAL: Missing default account!')
1079 raise Exception('FATAL: Missing default account!')
1080 if refresh:
1080 if refresh:
1081 # The default user might be based on outdated state which
1081 # The default user might be based on outdated state which
1082 # has been loaded from the cache.
1082 # has been loaded from the cache.
1083 # A call to refresh() ensures that the
1083 # A call to refresh() ensures that the
1084 # latest state from the database is used.
1084 # latest state from the database is used.
1085 Session().refresh(user)
1085 Session().refresh(user)
1086
1086
1087 return user
1087 return user
1088
1088
1089 @classmethod
1089 @classmethod
1090 def get_default_user_id(cls):
1090 def get_default_user_id(cls):
1091 import rhodecode
1091 import rhodecode
1092 return rhodecode.CONFIG['default_user_id']
1092 return rhodecode.CONFIG['default_user_id']
1093
1093
1094 def _get_default_perms(self, user, suffix=''):
1094 def _get_default_perms(self, user, suffix=''):
1095 from rhodecode.model.permission import PermissionModel
1095 from rhodecode.model.permission import PermissionModel
1096 return PermissionModel().get_default_perms(user.user_perms, suffix)
1096 return PermissionModel().get_default_perms(user.user_perms, suffix)
1097
1097
1098 def get_default_perms(self, suffix=''):
1098 def get_default_perms(self, suffix=''):
1099 return self._get_default_perms(self, suffix)
1099 return self._get_default_perms(self, suffix)
1100
1100
1101 def get_api_data(self, include_secrets=False, details='full'):
1101 def get_api_data(self, include_secrets=False, details='full'):
1102 """
1102 """
1103 Common function for generating user related data for API
1103 Common function for generating user related data for API
1104
1104
1105 :param include_secrets: By default secrets in the API data will be replaced
1105 :param include_secrets: By default secrets in the API data will be replaced
1106 by a placeholder value to prevent exposing this data by accident. In case
1106 by a placeholder value to prevent exposing this data by accident. In case
1107 this data shall be exposed, set this flag to ``True``.
1107 this data shall be exposed, set this flag to ``True``.
1108
1108
1109 :param details: details can be 'basic|full' basic gives only a subset of
1109 :param details: details can be 'basic|full' basic gives only a subset of
1110 the available user information that includes user_id, name and emails.
1110 the available user information that includes user_id, name and emails.
1111 """
1111 """
1112 user = self
1112 user = self
1113 user_data = self.user_data
1113 user_data = self.user_data
1114 data = {
1114 data = {
1115 'user_id': user.user_id,
1115 'user_id': user.user_id,
1116 'username': user.username,
1116 'username': user.username,
1117 'firstname': user.name,
1117 'firstname': user.name,
1118 'lastname': user.lastname,
1118 'lastname': user.lastname,
1119 'description': user.description,
1119 'description': user.description,
1120 'email': user.email,
1120 'email': user.email,
1121 'emails': user.emails,
1121 'emails': user.emails,
1122 }
1122 }
1123 if details == 'basic':
1123 if details == 'basic':
1124 return data
1124 return data
1125
1125
1126 auth_token_length = 40
1126 auth_token_length = 40
1127 auth_token_replacement = '*' * auth_token_length
1127 auth_token_replacement = '*' * auth_token_length
1128
1128
1129 extras = {
1129 extras = {
1130 'auth_tokens': [auth_token_replacement],
1130 'auth_tokens': [auth_token_replacement],
1131 'active': user.active,
1131 'active': user.active,
1132 'admin': user.admin,
1132 'admin': user.admin,
1133 'extern_type': user.extern_type,
1133 'extern_type': user.extern_type,
1134 'extern_name': user.extern_name,
1134 'extern_name': user.extern_name,
1135 'last_login': user.last_login,
1135 'last_login': user.last_login,
1136 'last_activity': user.last_activity,
1136 'last_activity': user.last_activity,
1137 'ip_addresses': user.ip_addresses,
1137 'ip_addresses': user.ip_addresses,
1138 'language': user_data.get('language')
1138 'language': user_data.get('language')
1139 }
1139 }
1140 data.update(extras)
1140 data.update(extras)
1141
1141
1142 if include_secrets:
1142 if include_secrets:
1143 data['auth_tokens'] = user.auth_tokens
1143 data['auth_tokens'] = user.auth_tokens
1144 return data
1144 return data
1145
1145
1146 def __json__(self):
1146 def __json__(self):
1147 data = {
1147 data = {
1148 'full_name': self.full_name,
1148 'full_name': self.full_name,
1149 'full_name_or_username': self.full_name_or_username,
1149 'full_name_or_username': self.full_name_or_username,
1150 'short_contact': self.short_contact,
1150 'short_contact': self.short_contact,
1151 'full_contact': self.full_contact,
1151 'full_contact': self.full_contact,
1152 }
1152 }
1153 data.update(self.get_api_data())
1153 data.update(self.get_api_data())
1154 return data
1154 return data
1155
1155
1156
1156
1157 class UserApiKeys(Base, BaseModel):
1157 class UserApiKeys(Base, BaseModel):
1158 __tablename__ = 'user_api_keys'
1158 __tablename__ = 'user_api_keys'
1159 __table_args__ = (
1159 __table_args__ = (
1160 Index('uak_api_key_idx', 'api_key'),
1160 Index('uak_api_key_idx', 'api_key'),
1161 Index('uak_api_key_expires_idx', 'api_key', 'expires'),
1161 Index('uak_api_key_expires_idx', 'api_key', 'expires'),
1162 base_table_args
1162 base_table_args
1163 )
1163 )
1164 __mapper_args__ = {}
1164 __mapper_args__ = {}
1165
1165
1166 # ApiKey role
1166 # ApiKey role
1167 ROLE_ALL = 'token_role_all'
1167 ROLE_ALL = 'token_role_all'
1168 ROLE_VCS = 'token_role_vcs'
1168 ROLE_VCS = 'token_role_vcs'
1169 ROLE_API = 'token_role_api'
1169 ROLE_API = 'token_role_api'
1170 ROLE_HTTP = 'token_role_http'
1170 ROLE_HTTP = 'token_role_http'
1171 ROLE_FEED = 'token_role_feed'
1171 ROLE_FEED = 'token_role_feed'
1172 ROLE_ARTIFACT_DOWNLOAD = 'role_artifact_download'
1172 ROLE_ARTIFACT_DOWNLOAD = 'role_artifact_download'
1173 # The last one is ignored in the list as we only
1173 # The last one is ignored in the list as we only
1174 # use it for one action, and cannot be created by users
1174 # use it for one action, and cannot be created by users
1175 ROLE_PASSWORD_RESET = 'token_password_reset'
1175 ROLE_PASSWORD_RESET = 'token_password_reset'
1176
1176
1177 ROLES = [ROLE_ALL, ROLE_VCS, ROLE_API, ROLE_HTTP, ROLE_FEED, ROLE_ARTIFACT_DOWNLOAD]
1177 ROLES = [ROLE_ALL, ROLE_VCS, ROLE_API, ROLE_HTTP, ROLE_FEED, ROLE_ARTIFACT_DOWNLOAD]
1178
1178
1179 user_api_key_id = Column("user_api_key_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1179 user_api_key_id = Column("user_api_key_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1180 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1180 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1181 api_key = Column("api_key", String(255), nullable=False, unique=True)
1181 api_key = Column("api_key", String(255), nullable=False, unique=True)
1182 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1182 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1183 expires = Column('expires', Float(53), nullable=False)
1183 expires = Column('expires', Float(53), nullable=False)
1184 role = Column('role', String(255), nullable=True)
1184 role = Column('role', String(255), nullable=True)
1185 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1185 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1186
1186
1187 # scope columns
1187 # scope columns
1188 repo_id = Column(
1188 repo_id = Column(
1189 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
1189 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
1190 nullable=True, unique=None, default=None)
1190 nullable=True, unique=None, default=None)
1191 repo = relationship('Repository', lazy='joined', back_populates='scoped_tokens')
1191 repo = relationship('Repository', lazy='joined', back_populates='scoped_tokens')
1192
1192
1193 repo_group_id = Column(
1193 repo_group_id = Column(
1194 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
1194 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
1195 nullable=True, unique=None, default=None)
1195 nullable=True, unique=None, default=None)
1196 repo_group = relationship('RepoGroup', lazy='joined')
1196 repo_group = relationship('RepoGroup', lazy='joined')
1197
1197
1198 user = relationship('User', lazy='joined', back_populates='user_auth_tokens')
1198 user = relationship('User', lazy='joined', back_populates='user_auth_tokens')
1199
1199
1200 def __repr__(self):
1200 def __repr__(self):
1201 return f"<{self.cls_name}('{self.role}')>"
1201 return f"<{self.cls_name}('{self.role}')>"
1202
1202
1203 def __json__(self):
1203 def __json__(self):
1204 data = {
1204 data = {
1205 'auth_token': self.api_key,
1205 'auth_token': self.api_key,
1206 'role': self.role,
1206 'role': self.role,
1207 'scope': self.scope_humanized,
1207 'scope': self.scope_humanized,
1208 'expired': self.expired
1208 'expired': self.expired
1209 }
1209 }
1210 return data
1210 return data
1211
1211
1212 def get_api_data(self, include_secrets=False):
1212 def get_api_data(self, include_secrets=False):
1213 data = self.__json__()
1213 data = self.__json__()
1214 if include_secrets:
1214 if include_secrets:
1215 return data
1215 return data
1216 else:
1216 else:
1217 data['auth_token'] = self.token_obfuscated
1217 data['auth_token'] = self.token_obfuscated
1218 return data
1218 return data
1219
1219
1220 @hybrid_property
1220 @hybrid_property
1221 def description_safe(self):
1221 def description_safe(self):
1222 from rhodecode.lib import helpers as h
1222 from rhodecode.lib import helpers as h
1223 return h.escape(self.description)
1223 return h.escape(self.description)
1224
1224
1225 @property
1225 @property
1226 def expired(self):
1226 def expired(self):
1227 if self.expires == -1:
1227 if self.expires == -1:
1228 return False
1228 return False
1229 return time.time() > self.expires
1229 return time.time() > self.expires
1230
1230
1231 @classmethod
1231 @classmethod
1232 def _get_role_name(cls, role):
1232 def _get_role_name(cls, role):
1233 return {
1233 return {
1234 cls.ROLE_ALL: _('all'),
1234 cls.ROLE_ALL: _('all'),
1235 cls.ROLE_HTTP: _('http/web interface'),
1235 cls.ROLE_HTTP: _('http/web interface'),
1236 cls.ROLE_VCS: _('vcs (git/hg/svn protocol)'),
1236 cls.ROLE_VCS: _('vcs (git/hg/svn protocol)'),
1237 cls.ROLE_API: _('api calls'),
1237 cls.ROLE_API: _('api calls'),
1238 cls.ROLE_FEED: _('feed access'),
1238 cls.ROLE_FEED: _('feed access'),
1239 cls.ROLE_ARTIFACT_DOWNLOAD: _('artifacts downloads'),
1239 cls.ROLE_ARTIFACT_DOWNLOAD: _('artifacts downloads'),
1240 }.get(role, role)
1240 }.get(role, role)
1241
1241
1242 @classmethod
1242 @classmethod
1243 def _get_role_description(cls, role):
1243 def _get_role_description(cls, role):
1244 return {
1244 return {
1245 cls.ROLE_ALL: _('Token for all actions.'),
1245 cls.ROLE_ALL: _('Token for all actions.'),
1246 cls.ROLE_HTTP: _('Token to access RhodeCode pages via web interface without '
1246 cls.ROLE_HTTP: _('Token to access RhodeCode pages via web interface without '
1247 'login using `api_access_controllers_whitelist` functionality.'),
1247 'login using `api_access_controllers_whitelist` functionality.'),
1248 cls.ROLE_VCS: _('Token to interact over git/hg/svn protocols. '
1248 cls.ROLE_VCS: _('Token to interact over git/hg/svn protocols. '
1249 'Requires auth_token authentication plugin to be active. <br/>'
1249 'Requires auth_token authentication plugin to be active. <br/>'
1250 'Such Token should be used then instead of a password to '
1250 'Such Token should be used then instead of a password to '
1251 'interact with a repository, and additionally can be '
1251 'interact with a repository, and additionally can be '
1252 'limited to single repository using repo scope.'),
1252 'limited to single repository using repo scope.'),
1253 cls.ROLE_API: _('Token limited to api calls.'),
1253 cls.ROLE_API: _('Token limited to api calls.'),
1254 cls.ROLE_FEED: _('Token to read RSS/ATOM feed.'),
1254 cls.ROLE_FEED: _('Token to read RSS/ATOM feed.'),
1255 cls.ROLE_ARTIFACT_DOWNLOAD: _('Token for artifacts downloads.'),
1255 cls.ROLE_ARTIFACT_DOWNLOAD: _('Token for artifacts downloads.'),
1256 }.get(role, role)
1256 }.get(role, role)
1257
1257
1258 @property
1258 @property
1259 def role_humanized(self):
1259 def role_humanized(self):
1260 return self._get_role_name(self.role)
1260 return self._get_role_name(self.role)
1261
1261
1262 def _get_scope(self):
1262 def _get_scope(self):
1263 if self.repo:
1263 if self.repo:
1264 return 'Repository: {}'.format(self.repo.repo_name)
1264 return 'Repository: {}'.format(self.repo.repo_name)
1265 if self.repo_group:
1265 if self.repo_group:
1266 return 'RepositoryGroup: {} (recursive)'.format(self.repo_group.group_name)
1266 return 'RepositoryGroup: {} (recursive)'.format(self.repo_group.group_name)
1267 return 'Global'
1267 return 'Global'
1268
1268
1269 @property
1269 @property
1270 def scope_humanized(self):
1270 def scope_humanized(self):
1271 return self._get_scope()
1271 return self._get_scope()
1272
1272
1273 @property
1273 @property
1274 def token_obfuscated(self):
1274 def token_obfuscated(self):
1275 if self.api_key:
1275 if self.api_key:
1276 return self.api_key[:4] + "****"
1276 return self.api_key[:4] + "****"
1277
1277
1278
1278
1279 class UserEmailMap(Base, BaseModel):
1279 class UserEmailMap(Base, BaseModel):
1280 __tablename__ = 'user_email_map'
1280 __tablename__ = 'user_email_map'
1281 __table_args__ = (
1281 __table_args__ = (
1282 Index('uem_email_idx', 'email'),
1282 Index('uem_email_idx', 'email'),
1283 Index('uem_user_id_idx', 'user_id'),
1283 Index('uem_user_id_idx', 'user_id'),
1284 UniqueConstraint('email'),
1284 UniqueConstraint('email'),
1285 base_table_args
1285 base_table_args
1286 )
1286 )
1287 __mapper_args__ = {}
1287 __mapper_args__ = {}
1288
1288
1289 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1289 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1290 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1290 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1291 _email = Column("email", String(255), nullable=True, unique=False, default=None)
1291 _email = Column("email", String(255), nullable=True, unique=False, default=None)
1292 user = relationship('User', lazy='joined', back_populates='user_emails')
1292 user = relationship('User', lazy='joined', back_populates='user_emails')
1293
1293
1294 @validates('_email')
1294 @validates('_email')
1295 def validate_email(self, key, email):
1295 def validate_email(self, key, email):
1296 # check if this email is not main one
1296 # check if this email is not main one
1297 main_email = Session().query(User).filter(User.email == email).scalar()
1297 main_email = Session().query(User).filter(User.email == email).scalar()
1298 if main_email is not None:
1298 if main_email is not None:
1299 raise AttributeError('email %s is present is user table' % email)
1299 raise AttributeError('email %s is present is user table' % email)
1300 return email
1300 return email
1301
1301
1302 @hybrid_property
1302 @hybrid_property
1303 def email(self):
1303 def email(self):
1304 return self._email
1304 return self._email
1305
1305
1306 @email.setter
1306 @email.setter
1307 def email(self, val):
1307 def email(self, val):
1308 self._email = val.lower() if val else None
1308 self._email = val.lower() if val else None
1309
1309
1310
1310
1311 class UserIpMap(Base, BaseModel):
1311 class UserIpMap(Base, BaseModel):
1312 __tablename__ = 'user_ip_map'
1312 __tablename__ = 'user_ip_map'
1313 __table_args__ = (
1313 __table_args__ = (
1314 UniqueConstraint('user_id', 'ip_addr'),
1314 UniqueConstraint('user_id', 'ip_addr'),
1315 base_table_args
1315 base_table_args
1316 )
1316 )
1317 __mapper_args__ = {}
1317 __mapper_args__ = {}
1318
1318
1319 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1319 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1320 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1320 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1321 ip_addr = Column("ip_addr", String(255), nullable=True, unique=False, default=None)
1321 ip_addr = Column("ip_addr", String(255), nullable=True, unique=False, default=None)
1322 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
1322 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
1323 description = Column("description", String(10000), nullable=True, unique=None, default=None)
1323 description = Column("description", String(10000), nullable=True, unique=None, default=None)
1324 user = relationship('User', lazy='joined', back_populates='user_ip_map')
1324 user = relationship('User', lazy='joined', back_populates='user_ip_map')
1325
1325
1326 @hybrid_property
1326 @hybrid_property
1327 def description_safe(self):
1327 def description_safe(self):
1328 from rhodecode.lib import helpers as h
1328 from rhodecode.lib import helpers as h
1329 return h.escape(self.description)
1329 return h.escape(self.description)
1330
1330
1331 @classmethod
1331 @classmethod
1332 def _get_ip_range(cls, ip_addr):
1332 def _get_ip_range(cls, ip_addr):
1333 net = ipaddress.ip_network(safe_str(ip_addr), strict=False)
1333 net = ipaddress.ip_network(safe_str(ip_addr), strict=False)
1334 return [str(net.network_address), str(net.broadcast_address)]
1334 return [str(net.network_address), str(net.broadcast_address)]
1335
1335
1336 def __json__(self):
1336 def __json__(self):
1337 return {
1337 return {
1338 'ip_addr': self.ip_addr,
1338 'ip_addr': self.ip_addr,
1339 'ip_range': self._get_ip_range(self.ip_addr),
1339 'ip_range': self._get_ip_range(self.ip_addr),
1340 }
1340 }
1341
1341
1342 def __repr__(self):
1342 def __repr__(self):
1343 return f"<{self.cls_name}('user_id={self.user_id} => ip={self.ip_addr}')>"
1343 return f"<{self.cls_name}('user_id={self.user_id} => ip={self.ip_addr}')>"
1344
1344
1345
1345
1346 class UserSshKeys(Base, BaseModel):
1346 class UserSshKeys(Base, BaseModel):
1347 __tablename__ = 'user_ssh_keys'
1347 __tablename__ = 'user_ssh_keys'
1348 __table_args__ = (
1348 __table_args__ = (
1349 Index('usk_ssh_key_fingerprint_idx', 'ssh_key_fingerprint'),
1349 Index('usk_ssh_key_fingerprint_idx', 'ssh_key_fingerprint'),
1350
1350
1351 UniqueConstraint('ssh_key_fingerprint'),
1351 UniqueConstraint('ssh_key_fingerprint'),
1352
1352
1353 base_table_args
1353 base_table_args
1354 )
1354 )
1355 __mapper_args__ = {}
1355 __mapper_args__ = {}
1356
1356
1357 ssh_key_id = Column('ssh_key_id', Integer(), nullable=False, unique=True, default=None, primary_key=True)
1357 ssh_key_id = Column('ssh_key_id', Integer(), nullable=False, unique=True, default=None, primary_key=True)
1358 ssh_key_data = Column('ssh_key_data', String(10240), nullable=False, unique=None, default=None)
1358 ssh_key_data = Column('ssh_key_data', String(10240), nullable=False, unique=None, default=None)
1359 ssh_key_fingerprint = Column('ssh_key_fingerprint', String(255), nullable=False, unique=None, default=None)
1359 ssh_key_fingerprint = Column('ssh_key_fingerprint', String(255), nullable=False, unique=None, default=None)
1360
1360
1361 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1361 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1362
1362
1363 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1363 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1364 accessed_on = Column('accessed_on', DateTime(timezone=False), nullable=True, default=None)
1364 accessed_on = Column('accessed_on', DateTime(timezone=False), nullable=True, default=None)
1365 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1365 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1366
1366
1367 user = relationship('User', lazy='joined', back_populates='user_ssh_keys')
1367 user = relationship('User', lazy='joined', back_populates='user_ssh_keys')
1368
1368
1369 def __json__(self):
1369 def __json__(self):
1370 data = {
1370 data = {
1371 'ssh_fingerprint': self.ssh_key_fingerprint,
1371 'ssh_fingerprint': self.ssh_key_fingerprint,
1372 'description': self.description,
1372 'description': self.description,
1373 'created_on': self.created_on
1373 'created_on': self.created_on
1374 }
1374 }
1375 return data
1375 return data
1376
1376
1377 def get_api_data(self):
1377 def get_api_data(self):
1378 data = self.__json__()
1378 data = self.__json__()
1379 return data
1379 return data
1380
1380
1381
1381
1382 class UserLog(Base, BaseModel):
1382 class UserLog(Base, BaseModel):
1383 __tablename__ = 'user_logs'
1383 __tablename__ = 'user_logs'
1384 __table_args__ = (
1384 __table_args__ = (
1385 base_table_args,
1385 base_table_args,
1386 )
1386 )
1387
1387
1388 VERSION_1 = 'v1'
1388 VERSION_1 = 'v1'
1389 VERSION_2 = 'v2'
1389 VERSION_2 = 'v2'
1390 VERSIONS = [VERSION_1, VERSION_2]
1390 VERSIONS = [VERSION_1, VERSION_2]
1391
1391
1392 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1392 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1393 user_id = Column("user_id", Integer(), ForeignKey('users.user_id',ondelete='SET NULL'), nullable=True, unique=None, default=None)
1393 user_id = Column("user_id", Integer(), ForeignKey('users.user_id',ondelete='SET NULL'), nullable=True, unique=None, default=None)
1394 username = Column("username", String(255), nullable=True, unique=None, default=None)
1394 username = Column("username", String(255), nullable=True, unique=None, default=None)
1395 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id', ondelete='SET NULL'), nullable=True, unique=None, default=None)
1395 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id', ondelete='SET NULL'), nullable=True, unique=None, default=None)
1396 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
1396 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
1397 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
1397 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
1398 action = Column("action", Text().with_variant(Text(1200000), 'mysql'), nullable=True, unique=None, default=None)
1398 action = Column("action", Text().with_variant(Text(1200000), 'mysql'), nullable=True, unique=None, default=None)
1399 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
1399 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
1400
1400
1401 version = Column("version", String(255), nullable=True, default=VERSION_1)
1401 version = Column("version", String(255), nullable=True, default=VERSION_1)
1402 user_data = Column('user_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=LONGTEXT()))))
1402 user_data = Column('user_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=LONGTEXT()))))
1403 action_data = Column('action_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=LONGTEXT()))))
1403 action_data = Column('action_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=LONGTEXT()))))
1404 user = relationship('User', cascade='', back_populates='user_log')
1404 user = relationship('User', cascade='', back_populates='user_log')
1405 repository = relationship('Repository', cascade='', back_populates='logs')
1405 repository = relationship('Repository', cascade='', back_populates='logs')
1406
1406
1407 def __repr__(self):
1407 def __repr__(self):
1408 return f"<{self.cls_name}('id:{self.repository_name}:{self.action}')>"
1408 return f"<{self.cls_name}('id:{self.repository_name}:{self.action}')>"
1409
1409
1410 def __json__(self):
1410 def __json__(self):
1411 return {
1411 return {
1412 'user_id': self.user_id,
1412 'user_id': self.user_id,
1413 'username': self.username,
1413 'username': self.username,
1414 'repository_id': self.repository_id,
1414 'repository_id': self.repository_id,
1415 'repository_name': self.repository_name,
1415 'repository_name': self.repository_name,
1416 'user_ip': self.user_ip,
1416 'user_ip': self.user_ip,
1417 'action_date': self.action_date,
1417 'action_date': self.action_date,
1418 'action': self.action,
1418 'action': self.action,
1419 }
1419 }
1420
1420
1421 @hybrid_property
1421 @hybrid_property
1422 def entry_id(self):
1422 def entry_id(self):
1423 return self.user_log_id
1423 return self.user_log_id
1424
1424
1425 @property
1425 @property
1426 def action_as_day(self):
1426 def action_as_day(self):
1427 return datetime.date(*self.action_date.timetuple()[:3])
1427 return datetime.date(*self.action_date.timetuple()[:3])
1428
1428
1429
1429
1430 class UserGroup(Base, BaseModel):
1430 class UserGroup(Base, BaseModel):
1431 __tablename__ = 'users_groups'
1431 __tablename__ = 'users_groups'
1432 __table_args__ = (
1432 __table_args__ = (
1433 base_table_args,
1433 base_table_args,
1434 )
1434 )
1435
1435
1436 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1436 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1437 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
1437 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
1438 user_group_description = Column("user_group_description", String(10000), nullable=True, unique=None, default=None)
1438 user_group_description = Column("user_group_description", String(10000), nullable=True, unique=None, default=None)
1439 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
1439 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
1440 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
1440 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
1441 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
1441 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
1442 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1442 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1443 _group_data = Column("group_data", LargeBinary(), nullable=True) # JSON data
1443 _group_data = Column("group_data", LargeBinary(), nullable=True) # JSON data
1444
1444
1445 members = relationship('UserGroupMember', cascade="all, delete-orphan", lazy="joined", back_populates='users_group')
1445 members = relationship('UserGroupMember', cascade="all, delete-orphan", lazy="joined", back_populates='users_group')
1446 users_group_to_perm = relationship('UserGroupToPerm', cascade='all', back_populates='users_group')
1446 users_group_to_perm = relationship('UserGroupToPerm', cascade='all', back_populates='users_group')
1447 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all', back_populates='users_group')
1447 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all', back_populates='users_group')
1448 users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all', back_populates='users_group')
1448 users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all', back_populates='users_group')
1449 user_user_group_to_perm = relationship('UserUserGroupToPerm', cascade='all', back_populates='user_group')
1449 user_user_group_to_perm = relationship('UserUserGroupToPerm', cascade='all', back_populates='user_group')
1450
1450
1451 user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all', back_populates='target_user_group')
1451 user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all', back_populates='target_user_group')
1452
1452
1453 user_group_review_rules = relationship('RepoReviewRuleUserGroup', cascade='all', back_populates='users_group')
1453 user_group_review_rules = relationship('RepoReviewRuleUserGroup', cascade='all', back_populates='users_group')
1454 user = relationship('User', primaryjoin="User.user_id==UserGroup.user_id", back_populates='user_groups')
1454 user = relationship('User', primaryjoin="User.user_id==UserGroup.user_id", back_populates='user_groups')
1455
1455
1456 @classmethod
1456 @classmethod
1457 def _load_group_data(cls, column):
1457 def _load_group_data(cls, column):
1458 if not column:
1458 if not column:
1459 return {}
1459 return {}
1460
1460
1461 try:
1461 try:
1462 return json.loads(column) or {}
1462 return json.loads(column) or {}
1463 except TypeError:
1463 except TypeError:
1464 return {}
1464 return {}
1465
1465
1466 @hybrid_property
1466 @hybrid_property
1467 def description_safe(self):
1467 def description_safe(self):
1468 from rhodecode.lib import helpers as h
1468 from rhodecode.lib import helpers as h
1469 return h.escape(self.user_group_description)
1469 return h.escape(self.user_group_description)
1470
1470
1471 @hybrid_property
1471 @hybrid_property
1472 def group_data(self):
1472 def group_data(self):
1473 return self._load_group_data(self._group_data)
1473 return self._load_group_data(self._group_data)
1474
1474
1475 @group_data.expression
1475 @group_data.expression
1476 def group_data(self, **kwargs):
1476 def group_data(self, **kwargs):
1477 return self._group_data
1477 return self._group_data
1478
1478
1479 @group_data.setter
1479 @group_data.setter
1480 def group_data(self, val):
1480 def group_data(self, val):
1481 try:
1481 try:
1482 self._group_data = json.dumps(val)
1482 self._group_data = json.dumps(val)
1483 except Exception:
1483 except Exception:
1484 log.error(traceback.format_exc())
1484 log.error(traceback.format_exc())
1485
1485
1486 @classmethod
1486 @classmethod
1487 def _load_sync(cls, group_data):
1487 def _load_sync(cls, group_data):
1488 if group_data:
1488 if group_data:
1489 return group_data.get('extern_type')
1489 return group_data.get('extern_type')
1490
1490
1491 @property
1491 @property
1492 def sync(self):
1492 def sync(self):
1493 return self._load_sync(self.group_data)
1493 return self._load_sync(self.group_data)
1494
1494
1495 def __repr__(self):
1495 def __repr__(self):
1496 return f"<{self.cls_name}('id:{self.users_group_id}:{self.users_group_name}')>"
1496 return f"<{self.cls_name}('id:{self.users_group_id}:{self.users_group_name}')>"
1497
1497
1498 @classmethod
1498 @classmethod
1499 def get_by_group_name(cls, group_name, cache=False,
1499 def get_by_group_name(cls, group_name, cache=False,
1500 case_insensitive=False):
1500 case_insensitive=False):
1501 if case_insensitive:
1501 if case_insensitive:
1502 q = cls.query().filter(func.lower(cls.users_group_name) ==
1502 q = cls.query().filter(func.lower(cls.users_group_name) ==
1503 func.lower(group_name))
1503 func.lower(group_name))
1504
1504
1505 else:
1505 else:
1506 q = cls.query().filter(cls.users_group_name == group_name)
1506 q = cls.query().filter(cls.users_group_name == group_name)
1507 if cache:
1507 if cache:
1508 name_key = _hash_key(group_name)
1508 name_key = _hash_key(group_name)
1509 q = q.options(
1509 q = q.options(
1510 FromCache("sql_cache_short", f"get_group_{name_key}"))
1510 FromCache("sql_cache_short", f"get_group_{name_key}"))
1511 return q.scalar()
1511 return q.scalar()
1512
1512
1513 @classmethod
1513 @classmethod
1514 def get(cls, user_group_id, cache=False):
1514 def get(cls, user_group_id, cache=False):
1515 if not user_group_id:
1515 if not user_group_id:
1516 return
1516 return
1517
1517
1518 user_group = cls.query()
1518 user_group = cls.query()
1519 if cache:
1519 if cache:
1520 user_group = user_group.options(
1520 user_group = user_group.options(
1521 FromCache("sql_cache_short", "get_users_group_%s" % user_group_id))
1521 FromCache("sql_cache_short", "get_users_group_%s" % user_group_id))
1522 return user_group.get(user_group_id)
1522 return user_group.get(user_group_id)
1523
1523
1524 def permissions(self, with_admins=True, with_owner=True,
1524 def permissions(self, with_admins=True, with_owner=True,
1525 expand_from_user_groups=False):
1525 expand_from_user_groups=False):
1526 """
1526 """
1527 Permissions for user groups
1527 Permissions for user groups
1528 """
1528 """
1529 _admin_perm = 'usergroup.admin'
1529 _admin_perm = 'usergroup.admin'
1530
1530
1531 owner_row = []
1531 owner_row = []
1532 if with_owner:
1532 if with_owner:
1533 usr = AttributeDict(self.user.get_dict())
1533 usr = AttributeDict(self.user.get_dict())
1534 usr.owner_row = True
1534 usr.owner_row = True
1535 usr.permission = _admin_perm
1535 usr.permission = _admin_perm
1536 owner_row.append(usr)
1536 owner_row.append(usr)
1537
1537
1538 super_admin_ids = []
1538 super_admin_ids = []
1539 super_admin_rows = []
1539 super_admin_rows = []
1540 if with_admins:
1540 if with_admins:
1541 for usr in User.get_all_super_admins():
1541 for usr in User.get_all_super_admins():
1542 super_admin_ids.append(usr.user_id)
1542 super_admin_ids.append(usr.user_id)
1543 # if this admin is also owner, don't double the record
1543 # if this admin is also owner, don't double the record
1544 if usr.user_id == owner_row[0].user_id:
1544 if usr.user_id == owner_row[0].user_id:
1545 owner_row[0].admin_row = True
1545 owner_row[0].admin_row = True
1546 else:
1546 else:
1547 usr = AttributeDict(usr.get_dict())
1547 usr = AttributeDict(usr.get_dict())
1548 usr.admin_row = True
1548 usr.admin_row = True
1549 usr.permission = _admin_perm
1549 usr.permission = _admin_perm
1550 super_admin_rows.append(usr)
1550 super_admin_rows.append(usr)
1551
1551
1552 q = UserUserGroupToPerm.query().filter(UserUserGroupToPerm.user_group == self)
1552 q = UserUserGroupToPerm.query().filter(UserUserGroupToPerm.user_group == self)
1553 q = q.options(joinedload(UserUserGroupToPerm.user_group),
1553 q = q.options(joinedload(UserUserGroupToPerm.user_group),
1554 joinedload(UserUserGroupToPerm.user),
1554 joinedload(UserUserGroupToPerm.user),
1555 joinedload(UserUserGroupToPerm.permission),)
1555 joinedload(UserUserGroupToPerm.permission),)
1556
1556
1557 # get owners and admins and permissions. We do a trick of re-writing
1557 # get owners and admins and permissions. We do a trick of re-writing
1558 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1558 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1559 # has a global reference and changing one object propagates to all
1559 # has a global reference and changing one object propagates to all
1560 # others. This means if admin is also an owner admin_row that change
1560 # others. This means if admin is also an owner admin_row that change
1561 # would propagate to both objects
1561 # would propagate to both objects
1562 perm_rows = []
1562 perm_rows = []
1563 for _usr in q.all():
1563 for _usr in q.all():
1564 usr = AttributeDict(_usr.user.get_dict())
1564 usr = AttributeDict(_usr.user.get_dict())
1565 # if this user is also owner/admin, mark as duplicate record
1565 # if this user is also owner/admin, mark as duplicate record
1566 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
1566 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
1567 usr.duplicate_perm = True
1567 usr.duplicate_perm = True
1568 usr.permission = _usr.permission.permission_name
1568 usr.permission = _usr.permission.permission_name
1569 perm_rows.append(usr)
1569 perm_rows.append(usr)
1570
1570
1571 # filter the perm rows by 'default' first and then sort them by
1571 # filter the perm rows by 'default' first and then sort them by
1572 # admin,write,read,none permissions sorted again alphabetically in
1572 # admin,write,read,none permissions sorted again alphabetically in
1573 # each group
1573 # each group
1574 perm_rows = sorted(perm_rows, key=display_user_sort)
1574 perm_rows = sorted(perm_rows, key=display_user_sort)
1575
1575
1576 user_groups_rows = []
1576 user_groups_rows = []
1577 if expand_from_user_groups:
1577 if expand_from_user_groups:
1578 for ug in self.permission_user_groups(with_members=True):
1578 for ug in self.permission_user_groups(with_members=True):
1579 for user_data in ug.members:
1579 for user_data in ug.members:
1580 user_groups_rows.append(user_data)
1580 user_groups_rows.append(user_data)
1581
1581
1582 return super_admin_rows + owner_row + perm_rows + user_groups_rows
1582 return super_admin_rows + owner_row + perm_rows + user_groups_rows
1583
1583
1584 def permission_user_groups(self, with_members=False):
1584 def permission_user_groups(self, with_members=False):
1585 q = UserGroupUserGroupToPerm.query()\
1585 q = UserGroupUserGroupToPerm.query()\
1586 .filter(UserGroupUserGroupToPerm.target_user_group == self)
1586 .filter(UserGroupUserGroupToPerm.target_user_group == self)
1587 q = q.options(joinedload(UserGroupUserGroupToPerm.user_group),
1587 q = q.options(joinedload(UserGroupUserGroupToPerm.user_group),
1588 joinedload(UserGroupUserGroupToPerm.target_user_group),
1588 joinedload(UserGroupUserGroupToPerm.target_user_group),
1589 joinedload(UserGroupUserGroupToPerm.permission),)
1589 joinedload(UserGroupUserGroupToPerm.permission),)
1590
1590
1591 perm_rows = []
1591 perm_rows = []
1592 for _user_group in q.all():
1592 for _user_group in q.all():
1593 entry = AttributeDict(_user_group.user_group.get_dict())
1593 entry = AttributeDict(_user_group.user_group.get_dict())
1594 entry.permission = _user_group.permission.permission_name
1594 entry.permission = _user_group.permission.permission_name
1595 if with_members:
1595 if with_members:
1596 entry.members = [x.user.get_dict()
1596 entry.members = [x.user.get_dict()
1597 for x in _user_group.user_group.members]
1597 for x in _user_group.user_group.members]
1598 perm_rows.append(entry)
1598 perm_rows.append(entry)
1599
1599
1600 perm_rows = sorted(perm_rows, key=display_user_group_sort)
1600 perm_rows = sorted(perm_rows, key=display_user_group_sort)
1601 return perm_rows
1601 return perm_rows
1602
1602
1603 def _get_default_perms(self, user_group, suffix=''):
1603 def _get_default_perms(self, user_group, suffix=''):
1604 from rhodecode.model.permission import PermissionModel
1604 from rhodecode.model.permission import PermissionModel
1605 return PermissionModel().get_default_perms(user_group.users_group_to_perm, suffix)
1605 return PermissionModel().get_default_perms(user_group.users_group_to_perm, suffix)
1606
1606
1607 def get_default_perms(self, suffix=''):
1607 def get_default_perms(self, suffix=''):
1608 return self._get_default_perms(self, suffix)
1608 return self._get_default_perms(self, suffix)
1609
1609
1610 def get_api_data(self, with_group_members=True, include_secrets=False):
1610 def get_api_data(self, with_group_members=True, include_secrets=False):
1611 """
1611 """
1612 :param include_secrets: See :meth:`User.get_api_data`, this parameter is
1612 :param include_secrets: See :meth:`User.get_api_data`, this parameter is
1613 basically forwarded.
1613 basically forwarded.
1614
1614
1615 """
1615 """
1616 user_group = self
1616 user_group = self
1617 data = {
1617 data = {
1618 'users_group_id': user_group.users_group_id,
1618 'users_group_id': user_group.users_group_id,
1619 'group_name': user_group.users_group_name,
1619 'group_name': user_group.users_group_name,
1620 'group_description': user_group.user_group_description,
1620 'group_description': user_group.user_group_description,
1621 'active': user_group.users_group_active,
1621 'active': user_group.users_group_active,
1622 'owner': user_group.user.username,
1622 'owner': user_group.user.username,
1623 'sync': user_group.sync,
1623 'sync': user_group.sync,
1624 'owner_email': user_group.user.email,
1624 'owner_email': user_group.user.email,
1625 }
1625 }
1626
1626
1627 if with_group_members:
1627 if with_group_members:
1628 users = []
1628 users = []
1629 for user in user_group.members:
1629 for user in user_group.members:
1630 user = user.user
1630 user = user.user
1631 users.append(user.get_api_data(include_secrets=include_secrets))
1631 users.append(user.get_api_data(include_secrets=include_secrets))
1632 data['users'] = users
1632 data['users'] = users
1633
1633
1634 return data
1634 return data
1635
1635
1636
1636
1637 class UserGroupMember(Base, BaseModel):
1637 class UserGroupMember(Base, BaseModel):
1638 __tablename__ = 'users_groups_members'
1638 __tablename__ = 'users_groups_members'
1639 __table_args__ = (
1639 __table_args__ = (
1640 base_table_args,
1640 base_table_args,
1641 )
1641 )
1642
1642
1643 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1643 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1644 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1644 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1645 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1645 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1646
1646
1647 user = relationship('User', lazy='joined', back_populates='group_member')
1647 user = relationship('User', lazy='joined', back_populates='group_member')
1648 users_group = relationship('UserGroup', back_populates='members')
1648 users_group = relationship('UserGroup', back_populates='members')
1649
1649
1650 def __init__(self, gr_id='', u_id=''):
1650 def __init__(self, gr_id='', u_id=''):
1651 self.users_group_id = gr_id
1651 self.users_group_id = gr_id
1652 self.user_id = u_id
1652 self.user_id = u_id
1653
1653
1654
1654
1655 class RepositoryField(Base, BaseModel):
1655 class RepositoryField(Base, BaseModel):
1656 __tablename__ = 'repositories_fields'
1656 __tablename__ = 'repositories_fields'
1657 __table_args__ = (
1657 __table_args__ = (
1658 UniqueConstraint('repository_id', 'field_key'), # no-multi field
1658 UniqueConstraint('repository_id', 'field_key'), # no-multi field
1659 base_table_args,
1659 base_table_args,
1660 )
1660 )
1661
1661
1662 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
1662 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
1663
1663
1664 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1664 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1665 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1665 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1666 field_key = Column("field_key", String(250))
1666 field_key = Column("field_key", String(250))
1667 field_label = Column("field_label", String(1024), nullable=False)
1667 field_label = Column("field_label", String(1024), nullable=False)
1668 field_value = Column("field_value", String(10000), nullable=False)
1668 field_value = Column("field_value", String(10000), nullable=False)
1669 field_desc = Column("field_desc", String(1024), nullable=False)
1669 field_desc = Column("field_desc", String(1024), nullable=False)
1670 field_type = Column("field_type", String(255), nullable=False, unique=None)
1670 field_type = Column("field_type", String(255), nullable=False, unique=None)
1671 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1671 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1672
1672
1673 repository = relationship('Repository', back_populates='extra_fields')
1673 repository = relationship('Repository', back_populates='extra_fields')
1674
1674
1675 @property
1675 @property
1676 def field_key_prefixed(self):
1676 def field_key_prefixed(self):
1677 return 'ex_%s' % self.field_key
1677 return 'ex_%s' % self.field_key
1678
1678
1679 @classmethod
1679 @classmethod
1680 def un_prefix_key(cls, key):
1680 def un_prefix_key(cls, key):
1681 if key.startswith(cls.PREFIX):
1681 if key.startswith(cls.PREFIX):
1682 return key[len(cls.PREFIX):]
1682 return key[len(cls.PREFIX):]
1683 return key
1683 return key
1684
1684
1685 @classmethod
1685 @classmethod
1686 def get_by_key_name(cls, key, repo):
1686 def get_by_key_name(cls, key, repo):
1687 row = cls.query()\
1687 row = cls.query()\
1688 .filter(cls.repository == repo)\
1688 .filter(cls.repository == repo)\
1689 .filter(cls.field_key == key).scalar()
1689 .filter(cls.field_key == key).scalar()
1690 return row
1690 return row
1691
1691
1692
1692
1693 class Repository(Base, BaseModel):
1693 class Repository(Base, BaseModel):
1694 __tablename__ = 'repositories'
1694 __tablename__ = 'repositories'
1695 __table_args__ = (
1695 __table_args__ = (
1696 Index('r_repo_name_idx', 'repo_name', mysql_length=255),
1696 Index('r_repo_name_idx', 'repo_name', mysql_length=255),
1697 base_table_args,
1697 base_table_args,
1698 )
1698 )
1699 DEFAULT_CLONE_URI = '{scheme}://{user}@{netloc}/{repo}'
1699 DEFAULT_CLONE_URI = '{scheme}://{user}@{netloc}/{repo}'
1700 DEFAULT_CLONE_URI_ID = '{scheme}://{user}@{netloc}/_{repoid}'
1700 DEFAULT_CLONE_URI_ID = '{scheme}://{user}@{netloc}/_{repoid}'
1701 DEFAULT_CLONE_URI_SSH = 'ssh://{sys_user}@{hostname}/{repo}'
1701 DEFAULT_CLONE_URI_SSH = 'ssh://{sys_user}@{hostname}/{repo}'
1702
1702
1703 STATE_CREATED = 'repo_state_created'
1703 STATE_CREATED = 'repo_state_created'
1704 STATE_PENDING = 'repo_state_pending'
1704 STATE_PENDING = 'repo_state_pending'
1705 STATE_ERROR = 'repo_state_error'
1705 STATE_ERROR = 'repo_state_error'
1706
1706
1707 LOCK_AUTOMATIC = 'lock_auto'
1707 LOCK_AUTOMATIC = 'lock_auto'
1708 LOCK_API = 'lock_api'
1708 LOCK_API = 'lock_api'
1709 LOCK_WEB = 'lock_web'
1709 LOCK_WEB = 'lock_web'
1710 LOCK_PULL = 'lock_pull'
1710 LOCK_PULL = 'lock_pull'
1711
1711
1712 NAME_SEP = URL_SEP
1712 NAME_SEP = URL_SEP
1713
1713
1714 repo_id = Column(
1714 repo_id = Column(
1715 "repo_id", Integer(), nullable=False, unique=True, default=None,
1715 "repo_id", Integer(), nullable=False, unique=True, default=None,
1716 primary_key=True)
1716 primary_key=True)
1717 _repo_name = Column(
1717 _repo_name = Column(
1718 "repo_name", Text(), nullable=False, default=None)
1718 "repo_name", Text(), nullable=False, default=None)
1719 repo_name_hash = Column(
1719 repo_name_hash = Column(
1720 "repo_name_hash", String(255), nullable=False, unique=True)
1720 "repo_name_hash", String(255), nullable=False, unique=True)
1721 repo_state = Column("repo_state", String(255), nullable=True)
1721 repo_state = Column("repo_state", String(255), nullable=True)
1722
1722
1723 clone_uri = Column(
1723 clone_uri = Column(
1724 "clone_uri", EncryptedTextValue(), nullable=True, unique=False,
1724 "clone_uri", EncryptedTextValue(), nullable=True, unique=False,
1725 default=None)
1725 default=None)
1726 push_uri = Column(
1726 push_uri = Column(
1727 "push_uri", EncryptedTextValue(), nullable=True, unique=False,
1727 "push_uri", EncryptedTextValue(), nullable=True, unique=False,
1728 default=None)
1728 default=None)
1729 repo_type = Column(
1729 repo_type = Column(
1730 "repo_type", String(255), nullable=False, unique=False, default=None)
1730 "repo_type", String(255), nullable=False, unique=False, default=None)
1731 user_id = Column(
1731 user_id = Column(
1732 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
1732 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
1733 unique=False, default=None)
1733 unique=False, default=None)
1734 private = Column(
1734 private = Column(
1735 "private", Boolean(), nullable=True, unique=None, default=None)
1735 "private", Boolean(), nullable=True, unique=None, default=None)
1736 archived = Column(
1736 archived = Column(
1737 "archived", Boolean(), nullable=True, unique=None, default=None)
1737 "archived", Boolean(), nullable=True, unique=None, default=None)
1738 enable_statistics = Column(
1738 enable_statistics = Column(
1739 "statistics", Boolean(), nullable=True, unique=None, default=True)
1739 "statistics", Boolean(), nullable=True, unique=None, default=True)
1740 enable_downloads = Column(
1740 enable_downloads = Column(
1741 "downloads", Boolean(), nullable=True, unique=None, default=True)
1741 "downloads", Boolean(), nullable=True, unique=None, default=True)
1742 description = Column(
1742 description = Column(
1743 "description", String(10000), nullable=True, unique=None, default=None)
1743 "description", String(10000), nullable=True, unique=None, default=None)
1744 created_on = Column(
1744 created_on = Column(
1745 'created_on', DateTime(timezone=False), nullable=True, unique=None,
1745 'created_on', DateTime(timezone=False), nullable=True, unique=None,
1746 default=datetime.datetime.now)
1746 default=datetime.datetime.now)
1747 updated_on = Column(
1747 updated_on = Column(
1748 'updated_on', DateTime(timezone=False), nullable=True, unique=None,
1748 'updated_on', DateTime(timezone=False), nullable=True, unique=None,
1749 default=datetime.datetime.now)
1749 default=datetime.datetime.now)
1750 _landing_revision = Column(
1750 _landing_revision = Column(
1751 "landing_revision", String(255), nullable=False, unique=False,
1751 "landing_revision", String(255), nullable=False, unique=False,
1752 default=None)
1752 default=None)
1753 enable_locking = Column(
1753 enable_locking = Column(
1754 "enable_locking", Boolean(), nullable=False, unique=None,
1754 "enable_locking", Boolean(), nullable=False, unique=None,
1755 default=False)
1755 default=False)
1756 _locked = Column(
1756 _locked = Column(
1757 "locked", String(255), nullable=True, unique=False, default=None)
1757 "locked", String(255), nullable=True, unique=False, default=None)
1758 _changeset_cache = Column(
1758 _changeset_cache = Column(
1759 "changeset_cache", LargeBinary(), nullable=True) # JSON data
1759 "changeset_cache", LargeBinary(), nullable=True) # JSON data
1760
1760
1761 fork_id = Column(
1761 fork_id = Column(
1762 "fork_id", Integer(), ForeignKey('repositories.repo_id'),
1762 "fork_id", Integer(), ForeignKey('repositories.repo_id'),
1763 nullable=True, unique=False, default=None)
1763 nullable=True, unique=False, default=None)
1764 group_id = Column(
1764 group_id = Column(
1765 "group_id", Integer(), ForeignKey('groups.group_id'), nullable=True,
1765 "group_id", Integer(), ForeignKey('groups.group_id'), nullable=True,
1766 unique=False, default=None)
1766 unique=False, default=None)
1767
1767
1768 user = relationship('User', lazy='joined', back_populates='repositories')
1768 user = relationship('User', lazy='joined', back_populates='repositories')
1769 fork = relationship('Repository', remote_side=repo_id, lazy='joined')
1769 fork = relationship('Repository', remote_side=repo_id, lazy='joined')
1770 group = relationship('RepoGroup', lazy='joined')
1770 group = relationship('RepoGroup', lazy='joined')
1771 repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
1771 repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
1772 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all', back_populates='repository')
1772 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all', back_populates='repository')
1773 stats = relationship('Statistics', cascade='all', uselist=False)
1773 stats = relationship('Statistics', cascade='all', uselist=False)
1774
1774
1775 followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id', cascade='all', back_populates='follows_repository')
1775 followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id', cascade='all', back_populates='follows_repository')
1776 extra_fields = relationship('RepositoryField', cascade="all, delete-orphan", back_populates='repository')
1776 extra_fields = relationship('RepositoryField', cascade="all, delete-orphan", back_populates='repository')
1777
1777
1778 logs = relationship('UserLog', back_populates='repository')
1778 logs = relationship('UserLog', back_populates='repository')
1779
1779
1780 comments = relationship('ChangesetComment', cascade="all, delete-orphan", back_populates='repo')
1780 comments = relationship('ChangesetComment', cascade="all, delete-orphan", back_populates='repo')
1781
1781
1782 pull_requests_source = relationship(
1782 pull_requests_source = relationship(
1783 'PullRequest',
1783 'PullRequest',
1784 primaryjoin='PullRequest.source_repo_id==Repository.repo_id',
1784 primaryjoin='PullRequest.source_repo_id==Repository.repo_id',
1785 cascade="all, delete-orphan",
1785 cascade="all, delete-orphan",
1786 #back_populates="pr_source"
1786 #back_populates="pr_source"
1787 )
1787 )
1788 pull_requests_target = relationship(
1788 pull_requests_target = relationship(
1789 'PullRequest',
1789 'PullRequest',
1790 primaryjoin='PullRequest.target_repo_id==Repository.repo_id',
1790 primaryjoin='PullRequest.target_repo_id==Repository.repo_id',
1791 cascade="all, delete-orphan",
1791 cascade="all, delete-orphan",
1792 #back_populates="pr_target"
1792 #back_populates="pr_target"
1793 )
1793 )
1794
1794
1795 ui = relationship('RepoRhodeCodeUi', cascade="all")
1795 ui = relationship('RepoRhodeCodeUi', cascade="all")
1796 settings = relationship('RepoRhodeCodeSetting', cascade="all")
1796 settings = relationship('RepoRhodeCodeSetting', cascade="all")
1797 integrations = relationship('Integration', cascade="all, delete-orphan", back_populates='repo')
1797 integrations = relationship('Integration', cascade="all, delete-orphan", back_populates='repo')
1798
1798
1799 scoped_tokens = relationship('UserApiKeys', cascade="all", back_populates='repo')
1799 scoped_tokens = relationship('UserApiKeys', cascade="all", back_populates='repo')
1800
1800
1801 # no cascade, set NULL
1801 # no cascade, set NULL
1802 artifacts = relationship('FileStore', primaryjoin='FileStore.scope_repo_id==Repository.repo_id', viewonly=True)
1802 artifacts = relationship('FileStore', primaryjoin='FileStore.scope_repo_id==Repository.repo_id', viewonly=True)
1803
1803
1804 review_rules = relationship('RepoReviewRule')
1804 review_rules = relationship('RepoReviewRule')
1805 user_branch_perms = relationship('UserToRepoBranchPermission')
1805 user_branch_perms = relationship('UserToRepoBranchPermission')
1806 user_group_branch_perms = relationship('UserGroupToRepoBranchPermission')
1806 user_group_branch_perms = relationship('UserGroupToRepoBranchPermission')
1807
1807
1808 def __repr__(self):
1808 def __repr__(self):
1809 return "<%s('%s:%s')>" % (self.cls_name, self.repo_id, self.repo_name)
1809 return "<%s('%s:%s')>" % (self.cls_name, self.repo_id, self.repo_name)
1810
1810
1811 @hybrid_property
1811 @hybrid_property
1812 def description_safe(self):
1812 def description_safe(self):
1813 from rhodecode.lib import helpers as h
1813 from rhodecode.lib import helpers as h
1814 return h.escape(self.description)
1814 return h.escape(self.description)
1815
1815
1816 @hybrid_property
1816 @hybrid_property
1817 def landing_rev(self):
1817 def landing_rev(self):
1818 # always should return [rev_type, rev], e.g ['branch', 'master']
1818 # always should return [rev_type, rev], e.g ['branch', 'master']
1819 if self._landing_revision:
1819 if self._landing_revision:
1820 _rev_info = self._landing_revision.split(':')
1820 _rev_info = self._landing_revision.split(':')
1821 if len(_rev_info) < 2:
1821 if len(_rev_info) < 2:
1822 _rev_info.insert(0, 'rev')
1822 _rev_info.insert(0, 'rev')
1823 return [_rev_info[0], _rev_info[1]]
1823 return [_rev_info[0], _rev_info[1]]
1824 return [None, None]
1824 return [None, None]
1825
1825
1826 @property
1826 @property
1827 def landing_ref_type(self):
1827 def landing_ref_type(self):
1828 return self.landing_rev[0]
1828 return self.landing_rev[0]
1829
1829
1830 @property
1830 @property
1831 def landing_ref_name(self):
1831 def landing_ref_name(self):
1832 return self.landing_rev[1]
1832 return self.landing_rev[1]
1833
1833
1834 @landing_rev.setter
1834 @landing_rev.setter
1835 def landing_rev(self, val):
1835 def landing_rev(self, val):
1836 if ':' not in val:
1836 if ':' not in val:
1837 raise ValueError('value must be delimited with `:` and consist '
1837 raise ValueError('value must be delimited with `:` and consist '
1838 'of <rev_type>:<rev>, got %s instead' % val)
1838 'of <rev_type>:<rev>, got %s instead' % val)
1839 self._landing_revision = val
1839 self._landing_revision = val
1840
1840
1841 @hybrid_property
1841 @hybrid_property
1842 def locked(self):
1842 def locked(self):
1843 if self._locked:
1843 if self._locked:
1844 user_id, timelocked, reason = self._locked.split(':')
1844 user_id, timelocked, reason = self._locked.split(':')
1845 lock_values = int(user_id), timelocked, reason
1845 lock_values = int(user_id), timelocked, reason
1846 else:
1846 else:
1847 lock_values = [None, None, None]
1847 lock_values = [None, None, None]
1848 return lock_values
1848 return lock_values
1849
1849
1850 @locked.setter
1850 @locked.setter
1851 def locked(self, val):
1851 def locked(self, val):
1852 if val and isinstance(val, (list, tuple)):
1852 if val and isinstance(val, (list, tuple)):
1853 self._locked = ':'.join(map(str, val))
1853 self._locked = ':'.join(map(str, val))
1854 else:
1854 else:
1855 self._locked = None
1855 self._locked = None
1856
1856
1857 @classmethod
1857 @classmethod
1858 def _load_changeset_cache(cls, repo_id, changeset_cache_raw):
1858 def _load_changeset_cache(cls, repo_id, changeset_cache_raw):
1859 from rhodecode.lib.vcs.backends.base import EmptyCommit
1859 from rhodecode.lib.vcs.backends.base import EmptyCommit
1860 dummy = EmptyCommit().__json__()
1860 dummy = EmptyCommit().__json__()
1861 if not changeset_cache_raw:
1861 if not changeset_cache_raw:
1862 dummy['source_repo_id'] = repo_id
1862 dummy['source_repo_id'] = repo_id
1863 return json.loads(json.dumps(dummy))
1863 return json.loads(json.dumps(dummy))
1864
1864
1865 try:
1865 try:
1866 return json.loads(changeset_cache_raw)
1866 return json.loads(changeset_cache_raw)
1867 except TypeError:
1867 except TypeError:
1868 return dummy
1868 return dummy
1869 except Exception:
1869 except Exception:
1870 log.error(traceback.format_exc())
1870 log.error(traceback.format_exc())
1871 return dummy
1871 return dummy
1872
1872
1873 @hybrid_property
1873 @hybrid_property
1874 def changeset_cache(self):
1874 def changeset_cache(self):
1875 return self._load_changeset_cache(self.repo_id, self._changeset_cache)
1875 return self._load_changeset_cache(self.repo_id, self._changeset_cache)
1876
1876
1877 @changeset_cache.setter
1877 @changeset_cache.setter
1878 def changeset_cache(self, val):
1878 def changeset_cache(self, val):
1879 try:
1879 try:
1880 self._changeset_cache = json.dumps(val)
1880 self._changeset_cache = json.dumps(val)
1881 except Exception:
1881 except Exception:
1882 log.error(traceback.format_exc())
1882 log.error(traceback.format_exc())
1883
1883
1884 @hybrid_property
1884 @hybrid_property
1885 def repo_name(self):
1885 def repo_name(self):
1886 return self._repo_name
1886 return self._repo_name
1887
1887
1888 @repo_name.setter
1888 @repo_name.setter
1889 def repo_name(self, value):
1889 def repo_name(self, value):
1890 self._repo_name = value
1890 self._repo_name = value
1891 self.repo_name_hash = sha1(safe_bytes(value))
1891 self.repo_name_hash = sha1(safe_bytes(value))
1892
1892
1893 @classmethod
1893 @classmethod
1894 def normalize_repo_name(cls, repo_name):
1894 def normalize_repo_name(cls, repo_name):
1895 """
1895 """
1896 Normalizes os specific repo_name to the format internally stored inside
1896 Normalizes os specific repo_name to the format internally stored inside
1897 database using URL_SEP
1897 database using URL_SEP
1898
1898
1899 :param cls:
1899 :param cls:
1900 :param repo_name:
1900 :param repo_name:
1901 """
1901 """
1902 return cls.NAME_SEP.join(repo_name.split(os.sep))
1902 return cls.NAME_SEP.join(repo_name.split(os.sep))
1903
1903
1904 @classmethod
1904 @classmethod
1905 def get_by_repo_name(cls, repo_name, cache=False, identity_cache=False):
1905 def get_by_repo_name(cls, repo_name, cache=False, identity_cache=False):
1906 session = Session()
1906 session = Session()
1907 q = session.query(cls).filter(cls.repo_name == repo_name)
1907 q = session.query(cls).filter(cls.repo_name == repo_name)
1908
1908
1909 if cache:
1909 if cache:
1910 if identity_cache:
1910 if identity_cache:
1911 val = cls.identity_cache(session, 'repo_name', repo_name)
1911 val = cls.identity_cache(session, 'repo_name', repo_name)
1912 if val:
1912 if val:
1913 return val
1913 return val
1914 else:
1914 else:
1915 cache_key = "get_repo_by_name_%s" % _hash_key(repo_name)
1915 cache_key = "get_repo_by_name_%s" % _hash_key(repo_name)
1916 q = q.options(
1916 q = q.options(
1917 FromCache("sql_cache_short", cache_key))
1917 FromCache("sql_cache_short", cache_key))
1918
1918
1919 return q.scalar()
1919 return q.scalar()
1920
1920
1921 @classmethod
1921 @classmethod
1922 def get_by_id_or_repo_name(cls, repoid):
1922 def get_by_id_or_repo_name(cls, repoid):
1923 if isinstance(repoid, int):
1923 if isinstance(repoid, int):
1924 try:
1924 try:
1925 repo = cls.get(repoid)
1925 repo = cls.get(repoid)
1926 except ValueError:
1926 except ValueError:
1927 repo = None
1927 repo = None
1928 else:
1928 else:
1929 repo = cls.get_by_repo_name(repoid)
1929 repo = cls.get_by_repo_name(repoid)
1930 return repo
1930 return repo
1931
1931
1932 @classmethod
1932 @classmethod
1933 def get_by_full_path(cls, repo_full_path):
1933 def get_by_full_path(cls, repo_full_path):
1934 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
1934 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
1935 repo_name = cls.normalize_repo_name(repo_name)
1935 repo_name = cls.normalize_repo_name(repo_name)
1936 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
1936 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
1937
1937
1938 @classmethod
1938 @classmethod
1939 def get_repo_forks(cls, repo_id):
1939 def get_repo_forks(cls, repo_id):
1940 return cls.query().filter(Repository.fork_id == repo_id)
1940 return cls.query().filter(Repository.fork_id == repo_id)
1941
1941
1942 @classmethod
1942 @classmethod
1943 def base_path(cls):
1943 def base_path(cls):
1944 """
1944 """
1945 Returns base path when all repos are stored
1945 Returns base path when all repos are stored
1946
1946
1947 :param cls:
1947 :param cls:
1948 """
1948 """
1949 from rhodecode.lib.utils import get_rhodecode_base_path
1949 from rhodecode.lib.utils import get_rhodecode_base_path
1950 return get_rhodecode_base_path()
1950 return get_rhodecode_base_path()
1951
1951
1952 @classmethod
1952 @classmethod
1953 def get_all_repos(cls, user_id=Optional(None), group_id=Optional(None),
1953 def get_all_repos(cls, user_id=Optional(None), group_id=Optional(None),
1954 case_insensitive=True, archived=False):
1954 case_insensitive=True, archived=False):
1955 q = Repository.query()
1955 q = Repository.query()
1956
1956
1957 if not archived:
1957 if not archived:
1958 q = q.filter(Repository.archived.isnot(true()))
1958 q = q.filter(Repository.archived.isnot(true()))
1959
1959
1960 if not isinstance(user_id, Optional):
1960 if not isinstance(user_id, Optional):
1961 q = q.filter(Repository.user_id == user_id)
1961 q = q.filter(Repository.user_id == user_id)
1962
1962
1963 if not isinstance(group_id, Optional):
1963 if not isinstance(group_id, Optional):
1964 q = q.filter(Repository.group_id == group_id)
1964 q = q.filter(Repository.group_id == group_id)
1965
1965
1966 if case_insensitive:
1966 if case_insensitive:
1967 q = q.order_by(func.lower(Repository.repo_name))
1967 q = q.order_by(func.lower(Repository.repo_name))
1968 else:
1968 else:
1969 q = q.order_by(Repository.repo_name)
1969 q = q.order_by(Repository.repo_name)
1970
1970
1971 return q.all()
1971 return q.all()
1972
1972
1973 @property
1973 @property
1974 def repo_uid(self):
1974 def repo_uid(self):
1975 return '_{}'.format(self.repo_id)
1975 return '_{}'.format(self.repo_id)
1976
1976
1977 @property
1977 @property
1978 def forks(self):
1978 def forks(self):
1979 """
1979 """
1980 Return forks of this repo
1980 Return forks of this repo
1981 """
1981 """
1982 return Repository.get_repo_forks(self.repo_id)
1982 return Repository.get_repo_forks(self.repo_id)
1983
1983
1984 @property
1984 @property
1985 def parent(self):
1985 def parent(self):
1986 """
1986 """
1987 Returns fork parent
1987 Returns fork parent
1988 """
1988 """
1989 return self.fork
1989 return self.fork
1990
1990
1991 @property
1991 @property
1992 def just_name(self):
1992 def just_name(self):
1993 return self.repo_name.split(self.NAME_SEP)[-1]
1993 return self.repo_name.split(self.NAME_SEP)[-1]
1994
1994
1995 @property
1995 @property
1996 def groups_with_parents(self):
1996 def groups_with_parents(self):
1997 groups = []
1997 groups = []
1998 if self.group is None:
1998 if self.group is None:
1999 return groups
1999 return groups
2000
2000
2001 cur_gr = self.group
2001 cur_gr = self.group
2002 groups.insert(0, cur_gr)
2002 groups.insert(0, cur_gr)
2003 while 1:
2003 while 1:
2004 gr = getattr(cur_gr, 'parent_group', None)
2004 gr = getattr(cur_gr, 'parent_group', None)
2005 cur_gr = cur_gr.parent_group
2005 cur_gr = cur_gr.parent_group
2006 if gr is None:
2006 if gr is None:
2007 break
2007 break
2008 groups.insert(0, gr)
2008 groups.insert(0, gr)
2009
2009
2010 return groups
2010 return groups
2011
2011
2012 @property
2012 @property
2013 def groups_and_repo(self):
2013 def groups_and_repo(self):
2014 return self.groups_with_parents, self
2014 return self.groups_with_parents, self
2015
2015
2016 @LazyProperty
2016 @LazyProperty
2017 def repo_path(self):
2017 def repo_path(self):
2018 """
2018 """
2019 Returns base full path for that repository means where it actually
2019 Returns base full path for that repository means where it actually
2020 exists on a filesystem
2020 exists on a filesystem
2021 """
2021 """
2022 q = Session().query(RhodeCodeUi).filter(
2022 q = Session().query(RhodeCodeUi).filter(
2023 RhodeCodeUi.ui_key == self.NAME_SEP)
2023 RhodeCodeUi.ui_key == self.NAME_SEP)
2024 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
2024 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
2025 return q.one().ui_value
2025 return q.one().ui_value
2026
2026
2027 @property
2027 @property
2028 def repo_full_path(self):
2028 def repo_full_path(self):
2029 p = [self.repo_path]
2029 p = [self.repo_path]
2030 # we need to split the name by / since this is how we store the
2030 # we need to split the name by / since this is how we store the
2031 # names in the database, but that eventually needs to be converted
2031 # names in the database, but that eventually needs to be converted
2032 # into a valid system path
2032 # into a valid system path
2033 p += self.repo_name.split(self.NAME_SEP)
2033 p += self.repo_name.split(self.NAME_SEP)
2034 return os.path.join(*map(safe_str, p))
2034 return os.path.join(*map(safe_str, p))
2035
2035
2036 @property
2036 @property
2037 def cache_keys(self):
2037 def cache_keys(self):
2038 """
2038 """
2039 Returns associated cache keys for that repo
2039 Returns associated cache keys for that repo
2040 """
2040 """
2041 invalidation_namespace = CacheKey.REPO_INVALIDATION_NAMESPACE.format(
2041 invalidation_namespace = CacheKey.REPO_INVALIDATION_NAMESPACE.format(
2042 repo_id=self.repo_id)
2042 repo_id=self.repo_id)
2043 return CacheKey.query()\
2043 return CacheKey.query()\
2044 .filter(CacheKey.cache_args == invalidation_namespace)\
2044 .filter(CacheKey.cache_args == invalidation_namespace)\
2045 .order_by(CacheKey.cache_key)\
2045 .order_by(CacheKey.cache_key)\
2046 .all()
2046 .all()
2047
2047
2048 @property
2048 @property
2049 def cached_diffs_relative_dir(self):
2049 def cached_diffs_relative_dir(self):
2050 """
2050 """
2051 Return a relative to the repository store path of cached diffs
2051 Return a relative to the repository store path of cached diffs
2052 used for safe display for users, who shouldn't know the absolute store
2052 used for safe display for users, who shouldn't know the absolute store
2053 path
2053 path
2054 """
2054 """
2055 return os.path.join(
2055 return os.path.join(
2056 os.path.dirname(self.repo_name),
2056 os.path.dirname(self.repo_name),
2057 self.cached_diffs_dir.split(os.path.sep)[-1])
2057 self.cached_diffs_dir.split(os.path.sep)[-1])
2058
2058
2059 @property
2059 @property
2060 def cached_diffs_dir(self):
2060 def cached_diffs_dir(self):
2061 path = self.repo_full_path
2061 path = self.repo_full_path
2062 return os.path.join(
2062 return os.path.join(
2063 os.path.dirname(path),
2063 os.path.dirname(path),
2064 f'.__shadow_diff_cache_repo_{self.repo_id}')
2064 f'.__shadow_diff_cache_repo_{self.repo_id}')
2065
2065
2066 def cached_diffs(self):
2066 def cached_diffs(self):
2067 diff_cache_dir = self.cached_diffs_dir
2067 diff_cache_dir = self.cached_diffs_dir
2068 if os.path.isdir(diff_cache_dir):
2068 if os.path.isdir(diff_cache_dir):
2069 return os.listdir(diff_cache_dir)
2069 return os.listdir(diff_cache_dir)
2070 return []
2070 return []
2071
2071
2072 def shadow_repos(self):
2072 def shadow_repos(self):
2073 shadow_repos_pattern = f'.__shadow_repo_{self.repo_id}'
2073 shadow_repos_pattern = f'.__shadow_repo_{self.repo_id}'
2074 return [
2074 return [
2075 x for x in os.listdir(os.path.dirname(self.repo_full_path))
2075 x for x in os.listdir(os.path.dirname(self.repo_full_path))
2076 if x.startswith(shadow_repos_pattern)
2076 if x.startswith(shadow_repos_pattern)
2077 ]
2077 ]
2078
2078
2079 def get_new_name(self, repo_name):
2079 def get_new_name(self, repo_name):
2080 """
2080 """
2081 returns new full repository name based on assigned group and new new
2081 returns new full repository name based on assigned group and new new
2082
2082
2083 :param repo_name:
2083 :param repo_name:
2084 """
2084 """
2085 path_prefix = self.group.full_path_splitted if self.group else []
2085 path_prefix = self.group.full_path_splitted if self.group else []
2086 return self.NAME_SEP.join(path_prefix + [repo_name])
2086 return self.NAME_SEP.join(path_prefix + [repo_name])
2087
2087
2088 @property
2088 @property
2089 def _config(self):
2089 def _config(self):
2090 """
2090 """
2091 Returns db based config object.
2091 Returns db based config object.
2092 """
2092 """
2093 from rhodecode.lib.utils import make_db_config
2093 from rhodecode.lib.utils import make_db_config
2094 return make_db_config(clear_session=False, repo=self)
2094 return make_db_config(clear_session=False, repo=self)
2095
2095
2096 def permissions(self, with_admins=True, with_owner=True,
2096 def permissions(self, with_admins=True, with_owner=True,
2097 expand_from_user_groups=False):
2097 expand_from_user_groups=False):
2098 """
2098 """
2099 Permissions for repositories
2099 Permissions for repositories
2100 """
2100 """
2101 _admin_perm = 'repository.admin'
2101 _admin_perm = 'repository.admin'
2102
2102
2103 owner_row = []
2103 owner_row = []
2104 if with_owner:
2104 if with_owner:
2105 usr = AttributeDict(self.user.get_dict())
2105 usr = AttributeDict(self.user.get_dict())
2106 usr.owner_row = True
2106 usr.owner_row = True
2107 usr.permission = _admin_perm
2107 usr.permission = _admin_perm
2108 usr.permission_id = None
2108 usr.permission_id = None
2109 owner_row.append(usr)
2109 owner_row.append(usr)
2110
2110
2111 super_admin_ids = []
2111 super_admin_ids = []
2112 super_admin_rows = []
2112 super_admin_rows = []
2113 if with_admins:
2113 if with_admins:
2114 for usr in User.get_all_super_admins():
2114 for usr in User.get_all_super_admins():
2115 super_admin_ids.append(usr.user_id)
2115 super_admin_ids.append(usr.user_id)
2116 # if this admin is also owner, don't double the record
2116 # if this admin is also owner, don't double the record
2117 if usr.user_id == owner_row[0].user_id:
2117 if usr.user_id == owner_row[0].user_id:
2118 owner_row[0].admin_row = True
2118 owner_row[0].admin_row = True
2119 else:
2119 else:
2120 usr = AttributeDict(usr.get_dict())
2120 usr = AttributeDict(usr.get_dict())
2121 usr.admin_row = True
2121 usr.admin_row = True
2122 usr.permission = _admin_perm
2122 usr.permission = _admin_perm
2123 usr.permission_id = None
2123 usr.permission_id = None
2124 super_admin_rows.append(usr)
2124 super_admin_rows.append(usr)
2125
2125
2126 q = UserRepoToPerm.query().filter(UserRepoToPerm.repository == self)
2126 q = UserRepoToPerm.query().filter(UserRepoToPerm.repository == self)
2127 q = q.options(joinedload(UserRepoToPerm.repository),
2127 q = q.options(joinedload(UserRepoToPerm.repository),
2128 joinedload(UserRepoToPerm.user),
2128 joinedload(UserRepoToPerm.user),
2129 joinedload(UserRepoToPerm.permission),)
2129 joinedload(UserRepoToPerm.permission),)
2130
2130
2131 # get owners and admins and permissions. We do a trick of re-writing
2131 # get owners and admins and permissions. We do a trick of re-writing
2132 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2132 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2133 # has a global reference and changing one object propagates to all
2133 # has a global reference and changing one object propagates to all
2134 # others. This means if admin is also an owner admin_row that change
2134 # others. This means if admin is also an owner admin_row that change
2135 # would propagate to both objects
2135 # would propagate to both objects
2136 perm_rows = []
2136 perm_rows = []
2137 for _usr in q.all():
2137 for _usr in q.all():
2138 usr = AttributeDict(_usr.user.get_dict())
2138 usr = AttributeDict(_usr.user.get_dict())
2139 # if this user is also owner/admin, mark as duplicate record
2139 # if this user is also owner/admin, mark as duplicate record
2140 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
2140 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
2141 usr.duplicate_perm = True
2141 usr.duplicate_perm = True
2142 # also check if this permission is maybe used by branch_permissions
2142 # also check if this permission is maybe used by branch_permissions
2143 if _usr.branch_perm_entry:
2143 if _usr.branch_perm_entry:
2144 usr.branch_rules = [x.branch_rule_id for x in _usr.branch_perm_entry]
2144 usr.branch_rules = [x.branch_rule_id for x in _usr.branch_perm_entry]
2145
2145
2146 usr.permission = _usr.permission.permission_name
2146 usr.permission = _usr.permission.permission_name
2147 usr.permission_id = _usr.repo_to_perm_id
2147 usr.permission_id = _usr.repo_to_perm_id
2148 perm_rows.append(usr)
2148 perm_rows.append(usr)
2149
2149
2150 # filter the perm rows by 'default' first and then sort them by
2150 # filter the perm rows by 'default' first and then sort them by
2151 # admin,write,read,none permissions sorted again alphabetically in
2151 # admin,write,read,none permissions sorted again alphabetically in
2152 # each group
2152 # each group
2153 perm_rows = sorted(perm_rows, key=display_user_sort)
2153 perm_rows = sorted(perm_rows, key=display_user_sort)
2154
2154
2155 user_groups_rows = []
2155 user_groups_rows = []
2156 if expand_from_user_groups:
2156 if expand_from_user_groups:
2157 for ug in self.permission_user_groups(with_members=True):
2157 for ug in self.permission_user_groups(with_members=True):
2158 for user_data in ug.members:
2158 for user_data in ug.members:
2159 user_groups_rows.append(user_data)
2159 user_groups_rows.append(user_data)
2160
2160
2161 return super_admin_rows + owner_row + perm_rows + user_groups_rows
2161 return super_admin_rows + owner_row + perm_rows + user_groups_rows
2162
2162
2163 def permission_user_groups(self, with_members=True):
2163 def permission_user_groups(self, with_members=True):
2164 q = UserGroupRepoToPerm.query()\
2164 q = UserGroupRepoToPerm.query()\
2165 .filter(UserGroupRepoToPerm.repository == self)
2165 .filter(UserGroupRepoToPerm.repository == self)
2166 q = q.options(joinedload(UserGroupRepoToPerm.repository),
2166 q = q.options(joinedload(UserGroupRepoToPerm.repository),
2167 joinedload(UserGroupRepoToPerm.users_group),
2167 joinedload(UserGroupRepoToPerm.users_group),
2168 joinedload(UserGroupRepoToPerm.permission),)
2168 joinedload(UserGroupRepoToPerm.permission),)
2169
2169
2170 perm_rows = []
2170 perm_rows = []
2171 for _user_group in q.all():
2171 for _user_group in q.all():
2172 entry = AttributeDict(_user_group.users_group.get_dict())
2172 entry = AttributeDict(_user_group.users_group.get_dict())
2173 entry.permission = _user_group.permission.permission_name
2173 entry.permission = _user_group.permission.permission_name
2174 if with_members:
2174 if with_members:
2175 entry.members = [x.user.get_dict()
2175 entry.members = [x.user.get_dict()
2176 for x in _user_group.users_group.members]
2176 for x in _user_group.users_group.members]
2177 perm_rows.append(entry)
2177 perm_rows.append(entry)
2178
2178
2179 perm_rows = sorted(perm_rows, key=display_user_group_sort)
2179 perm_rows = sorted(perm_rows, key=display_user_group_sort)
2180 return perm_rows
2180 return perm_rows
2181
2181
2182 def get_api_data(self, include_secrets=False):
2182 def get_api_data(self, include_secrets=False):
2183 """
2183 """
2184 Common function for generating repo api data
2184 Common function for generating repo api data
2185
2185
2186 :param include_secrets: See :meth:`User.get_api_data`.
2186 :param include_secrets: See :meth:`User.get_api_data`.
2187
2187
2188 """
2188 """
2189 # TODO: mikhail: Here there is an anti-pattern, we probably need to
2189 # TODO: mikhail: Here there is an anti-pattern, we probably need to
2190 # move this methods on models level.
2190 # move this methods on models level.
2191 from rhodecode.model.settings import SettingsModel
2191 from rhodecode.model.settings import SettingsModel
2192 from rhodecode.model.repo import RepoModel
2192 from rhodecode.model.repo import RepoModel
2193
2193
2194 repo = self
2194 repo = self
2195 _user_id, _time, _reason = self.locked
2195 _user_id, _time, _reason = self.locked
2196
2196
2197 data = {
2197 data = {
2198 'repo_id': repo.repo_id,
2198 'repo_id': repo.repo_id,
2199 'repo_name': repo.repo_name,
2199 'repo_name': repo.repo_name,
2200 'repo_type': repo.repo_type,
2200 'repo_type': repo.repo_type,
2201 'clone_uri': repo.clone_uri or '',
2201 'clone_uri': repo.clone_uri or '',
2202 'push_uri': repo.push_uri or '',
2202 'push_uri': repo.push_uri or '',
2203 'url': RepoModel().get_url(self),
2203 'url': RepoModel().get_url(self),
2204 'private': repo.private,
2204 'private': repo.private,
2205 'created_on': repo.created_on,
2205 'created_on': repo.created_on,
2206 'description': repo.description_safe,
2206 'description': repo.description_safe,
2207 'landing_rev': repo.landing_rev,
2207 'landing_rev': repo.landing_rev,
2208 'owner': repo.user.username,
2208 'owner': repo.user.username,
2209 'fork_of': repo.fork.repo_name if repo.fork else None,
2209 'fork_of': repo.fork.repo_name if repo.fork else None,
2210 'fork_of_id': repo.fork.repo_id if repo.fork else None,
2210 'fork_of_id': repo.fork.repo_id if repo.fork else None,
2211 'enable_statistics': repo.enable_statistics,
2211 'enable_statistics': repo.enable_statistics,
2212 'enable_locking': repo.enable_locking,
2212 'enable_locking': repo.enable_locking,
2213 'enable_downloads': repo.enable_downloads,
2213 'enable_downloads': repo.enable_downloads,
2214 'last_changeset': repo.changeset_cache,
2214 'last_changeset': repo.changeset_cache,
2215 'locked_by': User.get(_user_id).get_api_data(
2215 'locked_by': User.get(_user_id).get_api_data(
2216 include_secrets=include_secrets) if _user_id else None,
2216 include_secrets=include_secrets) if _user_id else None,
2217 'locked_date': time_to_datetime(_time) if _time else None,
2217 'locked_date': time_to_datetime(_time) if _time else None,
2218 'lock_reason': _reason if _reason else None,
2218 'lock_reason': _reason if _reason else None,
2219 }
2219 }
2220
2220
2221 # TODO: mikhail: should be per-repo settings here
2221 # TODO: mikhail: should be per-repo settings here
2222 rc_config = SettingsModel().get_all_settings()
2222 rc_config = SettingsModel().get_all_settings()
2223 repository_fields = str2bool(
2223 repository_fields = str2bool(
2224 rc_config.get('rhodecode_repository_fields'))
2224 rc_config.get('rhodecode_repository_fields'))
2225 if repository_fields:
2225 if repository_fields:
2226 for f in self.extra_fields:
2226 for f in self.extra_fields:
2227 data[f.field_key_prefixed] = f.field_value
2227 data[f.field_key_prefixed] = f.field_value
2228
2228
2229 return data
2229 return data
2230
2230
2231 @classmethod
2231 @classmethod
2232 def lock(cls, repo, user_id, lock_time=None, lock_reason=None):
2232 def lock(cls, repo, user_id, lock_time=None, lock_reason=None):
2233 if not lock_time:
2233 if not lock_time:
2234 lock_time = time.time()
2234 lock_time = time.time()
2235 if not lock_reason:
2235 if not lock_reason:
2236 lock_reason = cls.LOCK_AUTOMATIC
2236 lock_reason = cls.LOCK_AUTOMATIC
2237 repo.locked = [user_id, lock_time, lock_reason]
2237 repo.locked = [user_id, lock_time, lock_reason]
2238 Session().add(repo)
2238 Session().add(repo)
2239 Session().commit()
2239 Session().commit()
2240
2240
2241 @classmethod
2241 @classmethod
2242 def unlock(cls, repo):
2242 def unlock(cls, repo):
2243 repo.locked = None
2243 repo.locked = None
2244 Session().add(repo)
2244 Session().add(repo)
2245 Session().commit()
2245 Session().commit()
2246
2246
2247 @classmethod
2247 @classmethod
2248 def getlock(cls, repo):
2248 def getlock(cls, repo):
2249 return repo.locked
2249 return repo.locked
2250
2250
2251 def get_locking_state(self, action, user_id, only_when_enabled=True):
2251 def get_locking_state(self, action, user_id, only_when_enabled=True):
2252 """
2252 """
2253 Checks locking on this repository, if locking is enabled and lock is
2253 Checks locking on this repository, if locking is enabled and lock is
2254 present returns a tuple of make_lock, locked, locked_by.
2254 present returns a tuple of make_lock, locked, locked_by.
2255 make_lock can have 3 states None (do nothing) True, make lock
2255 make_lock can have 3 states None (do nothing) True, make lock
2256 False release lock, This value is later propagated to hooks, which
2256 False release lock, This value is later propagated to hooks, which
2257 do the locking. Think about this as signals passed to hooks what to do.
2257 do the locking. Think about this as signals passed to hooks what to do.
2258
2258
2259 """
2259 """
2260 # TODO: johbo: This is part of the business logic and should be moved
2260 # TODO: johbo: This is part of the business logic and should be moved
2261 # into the RepositoryModel.
2261 # into the RepositoryModel.
2262
2262
2263 if action not in ('push', 'pull'):
2263 if action not in ('push', 'pull'):
2264 raise ValueError("Invalid action value: %s" % repr(action))
2264 raise ValueError("Invalid action value: %s" % repr(action))
2265
2265
2266 # defines if locked error should be thrown to user
2266 # defines if locked error should be thrown to user
2267 currently_locked = False
2267 currently_locked = False
2268 # defines if new lock should be made, tri-state
2268 # defines if new lock should be made, tri-state
2269 make_lock = None
2269 make_lock = None
2270 repo = self
2270 repo = self
2271 user = User.get(user_id)
2271 user = User.get(user_id)
2272
2272
2273 lock_info = repo.locked
2273 lock_info = repo.locked
2274
2274
2275 if repo and (repo.enable_locking or not only_when_enabled):
2275 if repo and (repo.enable_locking or not only_when_enabled):
2276 if action == 'push':
2276 if action == 'push':
2277 # check if it's already locked !, if it is compare users
2277 # check if it's already locked !, if it is compare users
2278 locked_by_user_id = lock_info[0]
2278 locked_by_user_id = lock_info[0]
2279 if user.user_id == locked_by_user_id:
2279 if user.user_id == locked_by_user_id:
2280 log.debug(
2280 log.debug(
2281 'Got `push` action from user %s, now unlocking', user)
2281 'Got `push` action from user %s, now unlocking', user)
2282 # unlock if we have push from user who locked
2282 # unlock if we have push from user who locked
2283 make_lock = False
2283 make_lock = False
2284 else:
2284 else:
2285 # we're not the same user who locked, ban with
2285 # we're not the same user who locked, ban with
2286 # code defined in settings (default is 423 HTTP Locked) !
2286 # code defined in settings (default is 423 HTTP Locked) !
2287 log.debug('Repo %s is currently locked by %s', repo, user)
2287 log.debug('Repo %s is currently locked by %s', repo, user)
2288 currently_locked = True
2288 currently_locked = True
2289 elif action == 'pull':
2289 elif action == 'pull':
2290 # [0] user [1] date
2290 # [0] user [1] date
2291 if lock_info[0] and lock_info[1]:
2291 if lock_info[0] and lock_info[1]:
2292 log.debug('Repo %s is currently locked by %s', repo, user)
2292 log.debug('Repo %s is currently locked by %s', repo, user)
2293 currently_locked = True
2293 currently_locked = True
2294 else:
2294 else:
2295 log.debug('Setting lock on repo %s by %s', repo, user)
2295 log.debug('Setting lock on repo %s by %s', repo, user)
2296 make_lock = True
2296 make_lock = True
2297
2297
2298 else:
2298 else:
2299 log.debug('Repository %s do not have locking enabled', repo)
2299 log.debug('Repository %s do not have locking enabled', repo)
2300
2300
2301 log.debug('FINAL locking values make_lock:%s,locked:%s,locked_by:%s',
2301 log.debug('FINAL locking values make_lock:%s,locked:%s,locked_by:%s',
2302 make_lock, currently_locked, lock_info)
2302 make_lock, currently_locked, lock_info)
2303
2303
2304 from rhodecode.lib.auth import HasRepoPermissionAny
2304 from rhodecode.lib.auth import HasRepoPermissionAny
2305 perm_check = HasRepoPermissionAny('repository.write', 'repository.admin')
2305 perm_check = HasRepoPermissionAny('repository.write', 'repository.admin')
2306 if make_lock and not perm_check(repo_name=repo.repo_name, user=user):
2306 if make_lock and not perm_check(repo_name=repo.repo_name, user=user):
2307 # if we don't have at least write permission we cannot make a lock
2307 # if we don't have at least write permission we cannot make a lock
2308 log.debug('lock state reset back to FALSE due to lack '
2308 log.debug('lock state reset back to FALSE due to lack '
2309 'of at least read permission')
2309 'of at least read permission')
2310 make_lock = False
2310 make_lock = False
2311
2311
2312 return make_lock, currently_locked, lock_info
2312 return make_lock, currently_locked, lock_info
2313
2313
2314 @property
2314 @property
2315 def last_commit_cache_update_diff(self):
2315 def last_commit_cache_update_diff(self):
2316 return time.time() - (safe_int(self.changeset_cache.get('updated_on')) or 0)
2316 return time.time() - (safe_int(self.changeset_cache.get('updated_on')) or 0)
2317
2317
2318 @classmethod
2318 @classmethod
2319 def _load_commit_change(cls, last_commit_cache):
2319 def _load_commit_change(cls, last_commit_cache):
2320 from rhodecode.lib.vcs.utils.helpers import parse_datetime
2320 from rhodecode.lib.vcs.utils.helpers import parse_datetime
2321 empty_date = datetime.datetime.fromtimestamp(0)
2321 empty_date = datetime.datetime.fromtimestamp(0)
2322 date_latest = last_commit_cache.get('date', empty_date)
2322 date_latest = last_commit_cache.get('date', empty_date)
2323 try:
2323 try:
2324 return parse_datetime(date_latest)
2324 return parse_datetime(date_latest)
2325 except Exception:
2325 except Exception:
2326 return empty_date
2326 return empty_date
2327
2327
2328 @property
2328 @property
2329 def last_commit_change(self):
2329 def last_commit_change(self):
2330 return self._load_commit_change(self.changeset_cache)
2330 return self._load_commit_change(self.changeset_cache)
2331
2331
2332 @property
2332 @property
2333 def last_db_change(self):
2333 def last_db_change(self):
2334 return self.updated_on
2334 return self.updated_on
2335
2335
2336 @property
2336 @property
2337 def clone_uri_hidden(self):
2337 def clone_uri_hidden(self):
2338 clone_uri = self.clone_uri
2338 clone_uri = self.clone_uri
2339 if clone_uri:
2339 if clone_uri:
2340 import urlobject
2340 import urlobject
2341 url_obj = urlobject.URLObject(cleaned_uri(clone_uri))
2341 url_obj = urlobject.URLObject(cleaned_uri(clone_uri))
2342 if url_obj.password:
2342 if url_obj.password:
2343 clone_uri = url_obj.with_password('*****')
2343 clone_uri = url_obj.with_password('*****')
2344 return clone_uri
2344 return clone_uri
2345
2345
2346 @property
2346 @property
2347 def push_uri_hidden(self):
2347 def push_uri_hidden(self):
2348 push_uri = self.push_uri
2348 push_uri = self.push_uri
2349 if push_uri:
2349 if push_uri:
2350 import urlobject
2350 import urlobject
2351 url_obj = urlobject.URLObject(cleaned_uri(push_uri))
2351 url_obj = urlobject.URLObject(cleaned_uri(push_uri))
2352 if url_obj.password:
2352 if url_obj.password:
2353 push_uri = url_obj.with_password('*****')
2353 push_uri = url_obj.with_password('*****')
2354 return push_uri
2354 return push_uri
2355
2355
2356 def clone_url(self, **override):
2356 def clone_url(self, **override):
2357 from rhodecode.model.settings import SettingsModel
2357 from rhodecode.model.settings import SettingsModel
2358
2358
2359 uri_tmpl = None
2359 uri_tmpl = None
2360 if 'with_id' in override:
2360 if 'with_id' in override:
2361 uri_tmpl = self.DEFAULT_CLONE_URI_ID
2361 uri_tmpl = self.DEFAULT_CLONE_URI_ID
2362 del override['with_id']
2362 del override['with_id']
2363
2363
2364 if 'uri_tmpl' in override:
2364 if 'uri_tmpl' in override:
2365 uri_tmpl = override['uri_tmpl']
2365 uri_tmpl = override['uri_tmpl']
2366 del override['uri_tmpl']
2366 del override['uri_tmpl']
2367
2367
2368 ssh = False
2368 ssh = False
2369 if 'ssh' in override:
2369 if 'ssh' in override:
2370 ssh = True
2370 ssh = True
2371 del override['ssh']
2371 del override['ssh']
2372
2372
2373 # we didn't override our tmpl from **overrides
2373 # we didn't override our tmpl from **overrides
2374 request = get_current_request()
2374 request = get_current_request()
2375 if not uri_tmpl:
2375 if not uri_tmpl:
2376 if hasattr(request, 'call_context') and hasattr(request.call_context, 'rc_config'):
2376 if hasattr(request, 'call_context') and hasattr(request.call_context, 'rc_config'):
2377 rc_config = request.call_context.rc_config
2377 rc_config = request.call_context.rc_config
2378 else:
2378 else:
2379 rc_config = SettingsModel().get_all_settings(cache=True)
2379 rc_config = SettingsModel().get_all_settings(cache=True)
2380
2380
2381 if ssh:
2381 if ssh:
2382 uri_tmpl = rc_config.get(
2382 uri_tmpl = rc_config.get(
2383 'rhodecode_clone_uri_ssh_tmpl') or self.DEFAULT_CLONE_URI_SSH
2383 'rhodecode_clone_uri_ssh_tmpl') or self.DEFAULT_CLONE_URI_SSH
2384
2384
2385 else:
2385 else:
2386 uri_tmpl = rc_config.get(
2386 uri_tmpl = rc_config.get(
2387 'rhodecode_clone_uri_tmpl') or self.DEFAULT_CLONE_URI
2387 'rhodecode_clone_uri_tmpl') or self.DEFAULT_CLONE_URI
2388
2388
2389 return get_clone_url(request=request,
2389 return get_clone_url(request=request,
2390 uri_tmpl=uri_tmpl,
2390 uri_tmpl=uri_tmpl,
2391 repo_name=self.repo_name,
2391 repo_name=self.repo_name,
2392 repo_id=self.repo_id,
2392 repo_id=self.repo_id,
2393 repo_type=self.repo_type,
2393 repo_type=self.repo_type,
2394 **override)
2394 **override)
2395
2395
2396 def set_state(self, state):
2396 def set_state(self, state):
2397 self.repo_state = state
2397 self.repo_state = state
2398 Session().add(self)
2398 Session().add(self)
2399 #==========================================================================
2399 #==========================================================================
2400 # SCM PROPERTIES
2400 # SCM PROPERTIES
2401 #==========================================================================
2401 #==========================================================================
2402
2402
2403 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None, maybe_unreachable=False, reference_obj=None):
2403 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None, maybe_unreachable=False, reference_obj=None):
2404 return get_commit_safe(
2404 return get_commit_safe(
2405 self.scm_instance(), commit_id, commit_idx, pre_load=pre_load,
2405 self.scm_instance(), commit_id, commit_idx, pre_load=pre_load,
2406 maybe_unreachable=maybe_unreachable, reference_obj=reference_obj)
2406 maybe_unreachable=maybe_unreachable, reference_obj=reference_obj)
2407
2407
2408 def get_changeset(self, rev=None, pre_load=None):
2408 def get_changeset(self, rev=None, pre_load=None):
2409 warnings.warn("Use get_commit", DeprecationWarning)
2409 warnings.warn("Use get_commit", DeprecationWarning)
2410 commit_id = None
2410 commit_id = None
2411 commit_idx = None
2411 commit_idx = None
2412 if isinstance(rev, str):
2412 if isinstance(rev, str):
2413 commit_id = rev
2413 commit_id = rev
2414 else:
2414 else:
2415 commit_idx = rev
2415 commit_idx = rev
2416 return self.get_commit(commit_id=commit_id, commit_idx=commit_idx,
2416 return self.get_commit(commit_id=commit_id, commit_idx=commit_idx,
2417 pre_load=pre_load)
2417 pre_load=pre_load)
2418
2418
2419 def get_landing_commit(self):
2419 def get_landing_commit(self):
2420 """
2420 """
2421 Returns landing commit, or if that doesn't exist returns the tip
2421 Returns landing commit, or if that doesn't exist returns the tip
2422 """
2422 """
2423 _rev_type, _rev = self.landing_rev
2423 _rev_type, _rev = self.landing_rev
2424 commit = self.get_commit(_rev)
2424 commit = self.get_commit(_rev)
2425 if isinstance(commit, EmptyCommit):
2425 if isinstance(commit, EmptyCommit):
2426 return self.get_commit()
2426 return self.get_commit()
2427 return commit
2427 return commit
2428
2428
2429 def flush_commit_cache(self):
2429 def flush_commit_cache(self):
2430 self.update_commit_cache(cs_cache={'raw_id':'0'})
2430 self.update_commit_cache(cs_cache={'raw_id':'0'})
2431 self.update_commit_cache()
2431 self.update_commit_cache()
2432
2432
2433 def update_commit_cache(self, cs_cache=None, config=None):
2433 def update_commit_cache(self, cs_cache=None, config=None):
2434 """
2434 """
2435 Update cache of last commit for repository
2435 Update cache of last commit for repository
2436 cache_keys should be::
2436 cache_keys should be::
2437
2437
2438 source_repo_id
2438 source_repo_id
2439 short_id
2439 short_id
2440 raw_id
2440 raw_id
2441 revision
2441 revision
2442 parents
2442 parents
2443 message
2443 message
2444 date
2444 date
2445 author
2445 author
2446 updated_on
2446 updated_on
2447
2447
2448 """
2448 """
2449 from rhodecode.lib.vcs.backends.base import BaseCommit
2449 from rhodecode.lib.vcs.backends.base import BaseCommit
2450 from rhodecode.lib.vcs.utils.helpers import parse_datetime
2450 from rhodecode.lib.vcs.utils.helpers import parse_datetime
2451 empty_date = datetime.datetime.fromtimestamp(0)
2451 empty_date = datetime.datetime.fromtimestamp(0)
2452 repo_commit_count = 0
2452 repo_commit_count = 0
2453
2453
2454 if cs_cache is None:
2454 if cs_cache is None:
2455 # use no-cache version here
2455 # use no-cache version here
2456 try:
2456 try:
2457 scm_repo = self.scm_instance(cache=False, config=config)
2457 scm_repo = self.scm_instance(cache=False, config=config)
2458 except VCSError:
2458 except VCSError:
2459 scm_repo = None
2459 scm_repo = None
2460 empty = scm_repo is None or scm_repo.is_empty()
2460 empty = scm_repo is None or scm_repo.is_empty()
2461
2461
2462 if not empty:
2462 if not empty:
2463 cs_cache = scm_repo.get_commit(
2463 cs_cache = scm_repo.get_commit(
2464 pre_load=["author", "date", "message", "parents", "branch"])
2464 pre_load=["author", "date", "message", "parents", "branch"])
2465 repo_commit_count = scm_repo.count()
2465 repo_commit_count = scm_repo.count()
2466 else:
2466 else:
2467 cs_cache = EmptyCommit()
2467 cs_cache = EmptyCommit()
2468
2468
2469 if isinstance(cs_cache, BaseCommit):
2469 if isinstance(cs_cache, BaseCommit):
2470 cs_cache = cs_cache.__json__()
2470 cs_cache = cs_cache.__json__()
2471
2471
2472 def is_outdated(new_cs_cache):
2472 def is_outdated(new_cs_cache):
2473 if (new_cs_cache['raw_id'] != self.changeset_cache['raw_id'] or
2473 if (new_cs_cache['raw_id'] != self.changeset_cache['raw_id'] or
2474 new_cs_cache['revision'] != self.changeset_cache['revision']):
2474 new_cs_cache['revision'] != self.changeset_cache['revision']):
2475 return True
2475 return True
2476 return False
2476 return False
2477
2477
2478 # check if we have maybe already latest cached revision
2478 # check if we have maybe already latest cached revision
2479 if is_outdated(cs_cache) or not self.changeset_cache:
2479 if is_outdated(cs_cache) or not self.changeset_cache:
2480 _current_datetime = datetime.datetime.utcnow()
2480 _current_datetime = datetime.datetime.utcnow()
2481 last_change = cs_cache.get('date') or _current_datetime
2481 last_change = cs_cache.get('date') or _current_datetime
2482 # we check if last update is newer than the new value
2482 # we check if last update is newer than the new value
2483 # if yes, we use the current timestamp instead. Imagine you get
2483 # if yes, we use the current timestamp instead. Imagine you get
2484 # old commit pushed 1y ago, we'd set last update 1y to ago.
2484 # old commit pushed 1y ago, we'd set last update 1y to ago.
2485 last_change_timestamp = datetime_to_time(last_change)
2485 last_change_timestamp = datetime_to_time(last_change)
2486 current_timestamp = datetime_to_time(last_change)
2486 current_timestamp = datetime_to_time(last_change)
2487 if last_change_timestamp > current_timestamp and not empty:
2487 if last_change_timestamp > current_timestamp and not empty:
2488 cs_cache['date'] = _current_datetime
2488 cs_cache['date'] = _current_datetime
2489
2489
2490 # also store size of repo
2490 # also store size of repo
2491 cs_cache['repo_commit_count'] = repo_commit_count
2491 cs_cache['repo_commit_count'] = repo_commit_count
2492
2492
2493 _date_latest = parse_datetime(cs_cache.get('date') or empty_date)
2493 _date_latest = parse_datetime(cs_cache.get('date') or empty_date)
2494 cs_cache['updated_on'] = time.time()
2494 cs_cache['updated_on'] = time.time()
2495 self.changeset_cache = cs_cache
2495 self.changeset_cache = cs_cache
2496 self.updated_on = last_change
2496 self.updated_on = last_change
2497 Session().add(self)
2497 Session().add(self)
2498 Session().commit()
2498 Session().commit()
2499
2499
2500 else:
2500 else:
2501 if empty:
2501 if empty:
2502 cs_cache = EmptyCommit().__json__()
2502 cs_cache = EmptyCommit().__json__()
2503 else:
2503 else:
2504 cs_cache = self.changeset_cache
2504 cs_cache = self.changeset_cache
2505
2505
2506 _date_latest = parse_datetime(cs_cache.get('date') or empty_date)
2506 _date_latest = parse_datetime(cs_cache.get('date') or empty_date)
2507
2507
2508 cs_cache['updated_on'] = time.time()
2508 cs_cache['updated_on'] = time.time()
2509 self.changeset_cache = cs_cache
2509 self.changeset_cache = cs_cache
2510 self.updated_on = _date_latest
2510 self.updated_on = _date_latest
2511 Session().add(self)
2511 Session().add(self)
2512 Session().commit()
2512 Session().commit()
2513
2513
2514 log.debug('updated repo `%s` with new commit cache %s, and last update_date: %s',
2514 log.debug('updated repo `%s` with new commit cache %s, and last update_date: %s',
2515 self.repo_name, cs_cache, _date_latest)
2515 self.repo_name, cs_cache, _date_latest)
2516
2516
2517 @property
2517 @property
2518 def tip(self):
2518 def tip(self):
2519 return self.get_commit('tip')
2519 return self.get_commit('tip')
2520
2520
2521 @property
2521 @property
2522 def author(self):
2522 def author(self):
2523 return self.tip.author
2523 return self.tip.author
2524
2524
2525 @property
2525 @property
2526 def last_change(self):
2526 def last_change(self):
2527 return self.scm_instance().last_change
2527 return self.scm_instance().last_change
2528
2528
2529 def get_comments(self, revisions=None):
2529 def get_comments(self, revisions=None):
2530 """
2530 """
2531 Returns comments for this repository grouped by revisions
2531 Returns comments for this repository grouped by revisions
2532
2532
2533 :param revisions: filter query by revisions only
2533 :param revisions: filter query by revisions only
2534 """
2534 """
2535 cmts = ChangesetComment.query()\
2535 cmts = ChangesetComment.query()\
2536 .filter(ChangesetComment.repo == self)
2536 .filter(ChangesetComment.repo == self)
2537 if revisions:
2537 if revisions:
2538 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
2538 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
2539 grouped = collections.defaultdict(list)
2539 grouped = collections.defaultdict(list)
2540 for cmt in cmts.all():
2540 for cmt in cmts.all():
2541 grouped[cmt.revision].append(cmt)
2541 grouped[cmt.revision].append(cmt)
2542 return grouped
2542 return grouped
2543
2543
2544 def statuses(self, revisions=None):
2544 def statuses(self, revisions=None):
2545 """
2545 """
2546 Returns statuses for this repository
2546 Returns statuses for this repository
2547
2547
2548 :param revisions: list of revisions to get statuses for
2548 :param revisions: list of revisions to get statuses for
2549 """
2549 """
2550 statuses = ChangesetStatus.query()\
2550 statuses = ChangesetStatus.query()\
2551 .filter(ChangesetStatus.repo == self)\
2551 .filter(ChangesetStatus.repo == self)\
2552 .filter(ChangesetStatus.version == 0)
2552 .filter(ChangesetStatus.version == 0)
2553
2553
2554 if revisions:
2554 if revisions:
2555 # Try doing the filtering in chunks to avoid hitting limits
2555 # Try doing the filtering in chunks to avoid hitting limits
2556 size = 500
2556 size = 500
2557 status_results = []
2557 status_results = []
2558 for chunk in range(0, len(revisions), size):
2558 for chunk in range(0, len(revisions), size):
2559 status_results += statuses.filter(
2559 status_results += statuses.filter(
2560 ChangesetStatus.revision.in_(
2560 ChangesetStatus.revision.in_(
2561 revisions[chunk: chunk+size])
2561 revisions[chunk: chunk+size])
2562 ).all()
2562 ).all()
2563 else:
2563 else:
2564 status_results = statuses.all()
2564 status_results = statuses.all()
2565
2565
2566 grouped = {}
2566 grouped = {}
2567
2567
2568 # maybe we have open new pullrequest without a status?
2568 # maybe we have open new pullrequest without a status?
2569 stat = ChangesetStatus.STATUS_UNDER_REVIEW
2569 stat = ChangesetStatus.STATUS_UNDER_REVIEW
2570 status_lbl = ChangesetStatus.get_status_lbl(stat)
2570 status_lbl = ChangesetStatus.get_status_lbl(stat)
2571 for pr in PullRequest.query().filter(PullRequest.source_repo == self).all():
2571 for pr in PullRequest.query().filter(PullRequest.source_repo == self).all():
2572 for rev in pr.revisions:
2572 for rev in pr.revisions:
2573 pr_id = pr.pull_request_id
2573 pr_id = pr.pull_request_id
2574 pr_repo = pr.target_repo.repo_name
2574 pr_repo = pr.target_repo.repo_name
2575 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
2575 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
2576
2576
2577 for stat in status_results:
2577 for stat in status_results:
2578 pr_id = pr_repo = None
2578 pr_id = pr_repo = None
2579 if stat.pull_request:
2579 if stat.pull_request:
2580 pr_id = stat.pull_request.pull_request_id
2580 pr_id = stat.pull_request.pull_request_id
2581 pr_repo = stat.pull_request.target_repo.repo_name
2581 pr_repo = stat.pull_request.target_repo.repo_name
2582 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
2582 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
2583 pr_id, pr_repo]
2583 pr_id, pr_repo]
2584 return grouped
2584 return grouped
2585
2585
2586 # ==========================================================================
2586 # ==========================================================================
2587 # SCM CACHE INSTANCE
2587 # SCM CACHE INSTANCE
2588 # ==========================================================================
2588 # ==========================================================================
2589
2589
2590 def scm_instance(self, **kwargs):
2590 def scm_instance(self, **kwargs):
2591 import rhodecode
2591 import rhodecode
2592
2592
2593 # Passing a config will not hit the cache currently only used
2593 # Passing a config will not hit the cache currently only used
2594 # for repo2dbmapper
2594 # for repo2dbmapper
2595 config = kwargs.pop('config', None)
2595 config = kwargs.pop('config', None)
2596 cache = kwargs.pop('cache', None)
2596 cache = kwargs.pop('cache', None)
2597 vcs_full_cache = kwargs.pop('vcs_full_cache', None)
2597 vcs_full_cache = kwargs.pop('vcs_full_cache', None)
2598 if vcs_full_cache is not None:
2598 if vcs_full_cache is not None:
2599 # allows override global config
2599 # allows override global config
2600 full_cache = vcs_full_cache
2600 full_cache = vcs_full_cache
2601 else:
2601 else:
2602 full_cache = rhodecode.ConfigGet().get_bool('vcs_full_cache')
2602 full_cache = rhodecode.ConfigGet().get_bool('vcs_full_cache')
2603 # if cache is NOT defined use default global, else we have a full
2603 # if cache is NOT defined use default global, else we have a full
2604 # control over cache behaviour
2604 # control over cache behaviour
2605 if cache is None and full_cache and not config:
2605 if cache is None and full_cache and not config:
2606 log.debug('Initializing pure cached instance for %s', self.repo_path)
2606 log.debug('Initializing pure cached instance for %s', self.repo_path)
2607 return self._get_instance_cached()
2607 return self._get_instance_cached()
2608
2608
2609 # cache here is sent to the "vcs server"
2609 # cache here is sent to the "vcs server"
2610 return self._get_instance(cache=bool(cache), config=config)
2610 return self._get_instance(cache=bool(cache), config=config)
2611
2611
2612 def _get_instance_cached(self):
2612 def _get_instance_cached(self):
2613 from rhodecode.lib import rc_cache
2613 from rhodecode.lib import rc_cache
2614
2614
2615 cache_namespace_uid = f'repo_instance.{self.repo_id}'
2615 cache_namespace_uid = f'repo_instance.{self.repo_id}'
2616 invalidation_namespace = CacheKey.REPO_INVALIDATION_NAMESPACE.format(
2616 invalidation_namespace = CacheKey.REPO_INVALIDATION_NAMESPACE.format(
2617 repo_id=self.repo_id)
2617 repo_id=self.repo_id)
2618 region = rc_cache.get_or_create_region('cache_repo_longterm', cache_namespace_uid)
2618 region = rc_cache.get_or_create_region('cache_repo_longterm', cache_namespace_uid)
2619
2619
2620 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid)
2620 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid)
2621 def get_instance_cached(repo_id, context_id, _cache_state_uid):
2621 def get_instance_cached(repo_id, context_id, _cache_state_uid):
2622 return self._get_instance(repo_state_uid=_cache_state_uid)
2622 return self._get_instance(repo_state_uid=_cache_state_uid)
2623
2623
2624 # we must use thread scoped cache here,
2624 # we must use thread scoped cache here,
2625 # because each thread of gevent needs it's own not shared connection and cache
2625 # because each thread of gevent needs it's own not shared connection and cache
2626 # we also alter `args` so the cache key is individual for every green thread.
2626 # we also alter `args` so the cache key is individual for every green thread.
2627 inv_context_manager = rc_cache.InvalidationContext(
2627 inv_context_manager = rc_cache.InvalidationContext(
2628 uid=cache_namespace_uid, invalidation_namespace=invalidation_namespace,
2628 uid=cache_namespace_uid, invalidation_namespace=invalidation_namespace,
2629 thread_scoped=True)
2629 thread_scoped=True)
2630 with inv_context_manager as invalidation_context:
2630 with inv_context_manager as invalidation_context:
2631 cache_state_uid = invalidation_context.cache_data['cache_state_uid']
2631 cache_state_uid = invalidation_context.cache_data['cache_state_uid']
2632 args = (self.repo_id, inv_context_manager.cache_key, cache_state_uid)
2632 args = (self.repo_id, inv_context_manager.cache_key, cache_state_uid)
2633
2633
2634 # re-compute and store cache if we get invalidate signal
2634 # re-compute and store cache if we get invalidate signal
2635 if invalidation_context.should_invalidate():
2635 if invalidation_context.should_invalidate():
2636 instance = get_instance_cached.refresh(*args)
2636 instance = get_instance_cached.refresh(*args)
2637 else:
2637 else:
2638 instance = get_instance_cached(*args)
2638 instance = get_instance_cached(*args)
2639
2639
2640 log.debug('Repo instance fetched in %.4fs', inv_context_manager.compute_time)
2640 log.debug('Repo instance fetched in %.4fs', inv_context_manager.compute_time)
2641 return instance
2641 return instance
2642
2642
2643 def _get_instance(self, cache=True, config=None, repo_state_uid=None):
2643 def _get_instance(self, cache=True, config=None, repo_state_uid=None):
2644 log.debug('Initializing %s instance `%s` with cache flag set to: %s',
2644 log.debug('Initializing %s instance `%s` with cache flag set to: %s',
2645 self.repo_type, self.repo_path, cache)
2645 self.repo_type, self.repo_path, cache)
2646 config = config or self._config
2646 config = config or self._config
2647 custom_wire = {
2647 custom_wire = {
2648 'cache': cache, # controls the vcs.remote cache
2648 'cache': cache, # controls the vcs.remote cache
2649 'repo_state_uid': repo_state_uid
2649 'repo_state_uid': repo_state_uid
2650 }
2650 }
2651 repo = get_vcs_instance(
2651 repo = get_vcs_instance(
2652 repo_path=safe_str(self.repo_full_path),
2652 repo_path=safe_str(self.repo_full_path),
2653 config=config,
2653 config=config,
2654 with_wire=custom_wire,
2654 with_wire=custom_wire,
2655 create=False,
2655 create=False,
2656 _vcs_alias=self.repo_type)
2656 _vcs_alias=self.repo_type)
2657 if repo is not None:
2657 if repo is not None:
2658 repo.count() # cache rebuild
2658 repo.count() # cache rebuild
2659 return repo
2659 return repo
2660
2660
2661 def get_shadow_repository_path(self, workspace_id):
2661 def get_shadow_repository_path(self, workspace_id):
2662 from rhodecode.lib.vcs.backends.base import BaseRepository
2662 from rhodecode.lib.vcs.backends.base import BaseRepository
2663 shadow_repo_path = BaseRepository._get_shadow_repository_path(
2663 shadow_repo_path = BaseRepository._get_shadow_repository_path(
2664 self.repo_full_path, self.repo_id, workspace_id)
2664 self.repo_full_path, self.repo_id, workspace_id)
2665 return shadow_repo_path
2665 return shadow_repo_path
2666
2666
2667 def __json__(self):
2667 def __json__(self):
2668 return {'landing_rev': self.landing_rev}
2668 return {'landing_rev': self.landing_rev}
2669
2669
2670 def get_dict(self):
2670 def get_dict(self):
2671
2671
2672 # Since we transformed `repo_name` to a hybrid property, we need to
2672 # Since we transformed `repo_name` to a hybrid property, we need to
2673 # keep compatibility with the code which uses `repo_name` field.
2673 # keep compatibility with the code which uses `repo_name` field.
2674
2674
2675 result = super(Repository, self).get_dict()
2675 result = super(Repository, self).get_dict()
2676 result['repo_name'] = result.pop('_repo_name', None)
2676 result['repo_name'] = result.pop('_repo_name', None)
2677 result.pop('_changeset_cache', '')
2677 result.pop('_changeset_cache', '')
2678 return result
2678 return result
2679
2679
2680
2680
2681 class RepoGroup(Base, BaseModel):
2681 class RepoGroup(Base, BaseModel):
2682 __tablename__ = 'groups'
2682 __tablename__ = 'groups'
2683 __table_args__ = (
2683 __table_args__ = (
2684 UniqueConstraint('group_name', 'group_parent_id'),
2684 UniqueConstraint('group_name', 'group_parent_id'),
2685 base_table_args,
2685 base_table_args,
2686 )
2686 )
2687 __mapper_args__ = {
2687 __mapper_args__ = {
2688 #TODO: this is now depracated ?!
2688 #TODO: this is now depracated ?!
2689 # 'order_by': 'group_name'
2689 # 'order_by': 'group_name'
2690 }
2690 }
2691
2691
2692 CHOICES_SEPARATOR = '/' # used to generate select2 choices for nested groups
2692 CHOICES_SEPARATOR = '/' # used to generate select2 choices for nested groups
2693
2693
2694 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2694 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2695 _group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
2695 _group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
2696 group_name_hash = Column("repo_group_name_hash", String(1024), nullable=False, unique=False)
2696 group_name_hash = Column("repo_group_name_hash", String(1024), nullable=False, unique=False)
2697 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
2697 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
2698 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
2698 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
2699 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
2699 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
2700 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
2700 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
2701 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2701 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2702 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
2702 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
2703 personal = Column('personal', Boolean(), nullable=True, unique=None, default=None)
2703 personal = Column('personal', Boolean(), nullable=True, unique=None, default=None)
2704 _changeset_cache = Column("changeset_cache", LargeBinary(), nullable=True) # JSON data
2704 _changeset_cache = Column("changeset_cache", LargeBinary(), nullable=True) # JSON data
2705
2705
2706 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id', back_populates='group')
2706 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id', back_populates='group')
2707 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all', back_populates='group')
2707 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all', back_populates='group')
2708 parent_group = relationship('RepoGroup', remote_side=group_id)
2708 parent_group = relationship('RepoGroup', remote_side=group_id)
2709 user = relationship('User', back_populates='repository_groups')
2709 user = relationship('User', back_populates='repository_groups')
2710 integrations = relationship('Integration', cascade="all, delete-orphan", back_populates='repo_group')
2710 integrations = relationship('Integration', cascade="all, delete-orphan", back_populates='repo_group')
2711
2711
2712 # no cascade, set NULL
2712 # no cascade, set NULL
2713 scope_artifacts = relationship('FileStore', primaryjoin='FileStore.scope_repo_group_id==RepoGroup.group_id', viewonly=True)
2713 scope_artifacts = relationship('FileStore', primaryjoin='FileStore.scope_repo_group_id==RepoGroup.group_id', viewonly=True)
2714
2714
2715 def __init__(self, group_name='', parent_group=None):
2715 def __init__(self, group_name='', parent_group=None):
2716 self.group_name = group_name
2716 self.group_name = group_name
2717 self.parent_group = parent_group
2717 self.parent_group = parent_group
2718
2718
2719 def __repr__(self):
2719 def __repr__(self):
2720 return f"<{self.cls_name}('id:{self.group_id}:{self.group_name}')>"
2720 return f"<{self.cls_name}('id:{self.group_id}:{self.group_name}')>"
2721
2721
2722 @hybrid_property
2722 @hybrid_property
2723 def group_name(self):
2723 def group_name(self):
2724 return self._group_name
2724 return self._group_name
2725
2725
2726 @group_name.setter
2726 @group_name.setter
2727 def group_name(self, value):
2727 def group_name(self, value):
2728 self._group_name = value
2728 self._group_name = value
2729 self.group_name_hash = self.hash_repo_group_name(value)
2729 self.group_name_hash = self.hash_repo_group_name(value)
2730
2730
2731 @classmethod
2731 @classmethod
2732 def _load_changeset_cache(cls, repo_id, changeset_cache_raw):
2732 def _load_changeset_cache(cls, repo_id, changeset_cache_raw):
2733 from rhodecode.lib.vcs.backends.base import EmptyCommit
2733 from rhodecode.lib.vcs.backends.base import EmptyCommit
2734 dummy = EmptyCommit().__json__()
2734 dummy = EmptyCommit().__json__()
2735 if not changeset_cache_raw:
2735 if not changeset_cache_raw:
2736 dummy['source_repo_id'] = repo_id
2736 dummy['source_repo_id'] = repo_id
2737 return json.loads(json.dumps(dummy))
2737 return json.loads(json.dumps(dummy))
2738
2738
2739 try:
2739 try:
2740 return json.loads(changeset_cache_raw)
2740 return json.loads(changeset_cache_raw)
2741 except TypeError:
2741 except TypeError:
2742 return dummy
2742 return dummy
2743 except Exception:
2743 except Exception:
2744 log.error(traceback.format_exc())
2744 log.error(traceback.format_exc())
2745 return dummy
2745 return dummy
2746
2746
2747 @hybrid_property
2747 @hybrid_property
2748 def changeset_cache(self):
2748 def changeset_cache(self):
2749 return self._load_changeset_cache('', self._changeset_cache)
2749 return self._load_changeset_cache('', self._changeset_cache)
2750
2750
2751 @changeset_cache.setter
2751 @changeset_cache.setter
2752 def changeset_cache(self, val):
2752 def changeset_cache(self, val):
2753 try:
2753 try:
2754 self._changeset_cache = json.dumps(val)
2754 self._changeset_cache = json.dumps(val)
2755 except Exception:
2755 except Exception:
2756 log.error(traceback.format_exc())
2756 log.error(traceback.format_exc())
2757
2757
2758 @validates('group_parent_id')
2758 @validates('group_parent_id')
2759 def validate_group_parent_id(self, key, val):
2759 def validate_group_parent_id(self, key, val):
2760 """
2760 """
2761 Check cycle references for a parent group to self
2761 Check cycle references for a parent group to self
2762 """
2762 """
2763 if self.group_id and val:
2763 if self.group_id and val:
2764 assert val != self.group_id
2764 assert val != self.group_id
2765
2765
2766 return val
2766 return val
2767
2767
2768 @hybrid_property
2768 @hybrid_property
2769 def description_safe(self):
2769 def description_safe(self):
2770 from rhodecode.lib import helpers as h
2770 from rhodecode.lib import helpers as h
2771 return h.escape(self.group_description)
2771 return h.escape(self.group_description)
2772
2772
2773 @classmethod
2773 @classmethod
2774 def hash_repo_group_name(cls, repo_group_name):
2774 def hash_repo_group_name(cls, repo_group_name):
2775 val = remove_formatting(repo_group_name)
2775 val = remove_formatting(repo_group_name)
2776 val = safe_str(val).lower()
2776 val = safe_str(val).lower()
2777 chars = []
2777 chars = []
2778 for c in val:
2778 for c in val:
2779 if c not in string.ascii_letters:
2779 if c not in string.ascii_letters:
2780 c = str(ord(c))
2780 c = str(ord(c))
2781 chars.append(c)
2781 chars.append(c)
2782
2782
2783 return ''.join(chars)
2783 return ''.join(chars)
2784
2784
2785 @classmethod
2785 @classmethod
2786 def _generate_choice(cls, repo_group):
2786 def _generate_choice(cls, repo_group):
2787 from webhelpers2.html import literal as _literal
2787 from webhelpers2.html import literal as _literal
2788
2788
2789 def _name(k):
2789 def _name(k):
2790 return _literal(cls.CHOICES_SEPARATOR.join(k))
2790 return _literal(cls.CHOICES_SEPARATOR.join(k))
2791
2791
2792 return repo_group.group_id, _name(repo_group.full_path_splitted)
2792 return repo_group.group_id, _name(repo_group.full_path_splitted)
2793
2793
2794 @classmethod
2794 @classmethod
2795 def groups_choices(cls, groups=None, show_empty_group=True):
2795 def groups_choices(cls, groups=None, show_empty_group=True):
2796 if not groups:
2796 if not groups:
2797 groups = cls.query().all()
2797 groups = cls.query().all()
2798
2798
2799 repo_groups = []
2799 repo_groups = []
2800 if show_empty_group:
2800 if show_empty_group:
2801 repo_groups = [(-1, '-- %s --' % _('No parent'))]
2801 repo_groups = [(-1, '-- %s --' % _('No parent'))]
2802
2802
2803 repo_groups.extend([cls._generate_choice(x) for x in groups])
2803 repo_groups.extend([cls._generate_choice(x) for x in groups])
2804
2804
2805 repo_groups = sorted(
2805 repo_groups = sorted(
2806 repo_groups, key=lambda t: t[1].split(cls.CHOICES_SEPARATOR)[0])
2806 repo_groups, key=lambda t: t[1].split(cls.CHOICES_SEPARATOR)[0])
2807 return repo_groups
2807 return repo_groups
2808
2808
2809 @classmethod
2809 @classmethod
2810 def url_sep(cls):
2810 def url_sep(cls):
2811 return URL_SEP
2811 return URL_SEP
2812
2812
2813 @classmethod
2813 @classmethod
2814 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
2814 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
2815 if case_insensitive:
2815 if case_insensitive:
2816 gr = cls.query().filter(func.lower(cls.group_name)
2816 gr = cls.query().filter(func.lower(cls.group_name)
2817 == func.lower(group_name))
2817 == func.lower(group_name))
2818 else:
2818 else:
2819 gr = cls.query().filter(cls.group_name == group_name)
2819 gr = cls.query().filter(cls.group_name == group_name)
2820 if cache:
2820 if cache:
2821 name_key = _hash_key(group_name)
2821 name_key = _hash_key(group_name)
2822 gr = gr.options(
2822 gr = gr.options(
2823 FromCache("sql_cache_short", f"get_group_{name_key}"))
2823 FromCache("sql_cache_short", f"get_group_{name_key}"))
2824 return gr.scalar()
2824 return gr.scalar()
2825
2825
2826 @classmethod
2826 @classmethod
2827 def get_user_personal_repo_group(cls, user_id):
2827 def get_user_personal_repo_group(cls, user_id):
2828 user = User.get(user_id)
2828 user = User.get(user_id)
2829 if user.username == User.DEFAULT_USER:
2829 if user.username == User.DEFAULT_USER:
2830 return None
2830 return None
2831
2831
2832 return cls.query()\
2832 return cls.query()\
2833 .filter(cls.personal == true()) \
2833 .filter(cls.personal == true()) \
2834 .filter(cls.user == user) \
2834 .filter(cls.user == user) \
2835 .order_by(cls.group_id.asc()) \
2835 .order_by(cls.group_id.asc()) \
2836 .first()
2836 .first()
2837
2837
2838 @classmethod
2838 @classmethod
2839 def get_all_repo_groups(cls, user_id=Optional(None), group_id=Optional(None),
2839 def get_all_repo_groups(cls, user_id=Optional(None), group_id=Optional(None),
2840 case_insensitive=True):
2840 case_insensitive=True):
2841 q = RepoGroup.query()
2841 q = RepoGroup.query()
2842
2842
2843 if not isinstance(user_id, Optional):
2843 if not isinstance(user_id, Optional):
2844 q = q.filter(RepoGroup.user_id == user_id)
2844 q = q.filter(RepoGroup.user_id == user_id)
2845
2845
2846 if not isinstance(group_id, Optional):
2846 if not isinstance(group_id, Optional):
2847 q = q.filter(RepoGroup.group_parent_id == group_id)
2847 q = q.filter(RepoGroup.group_parent_id == group_id)
2848
2848
2849 if case_insensitive:
2849 if case_insensitive:
2850 q = q.order_by(func.lower(RepoGroup.group_name))
2850 q = q.order_by(func.lower(RepoGroup.group_name))
2851 else:
2851 else:
2852 q = q.order_by(RepoGroup.group_name)
2852 q = q.order_by(RepoGroup.group_name)
2853 return q.all()
2853 return q.all()
2854
2854
2855 @property
2855 @property
2856 def parents(self, parents_recursion_limit=10):
2856 def parents(self, parents_recursion_limit=10):
2857 groups = []
2857 groups = []
2858 if self.parent_group is None:
2858 if self.parent_group is None:
2859 return groups
2859 return groups
2860 cur_gr = self.parent_group
2860 cur_gr = self.parent_group
2861 groups.insert(0, cur_gr)
2861 groups.insert(0, cur_gr)
2862 cnt = 0
2862 cnt = 0
2863 while 1:
2863 while 1:
2864 cnt += 1
2864 cnt += 1
2865 gr = getattr(cur_gr, 'parent_group', None)
2865 gr = getattr(cur_gr, 'parent_group', None)
2866 cur_gr = cur_gr.parent_group
2866 cur_gr = cur_gr.parent_group
2867 if gr is None:
2867 if gr is None:
2868 break
2868 break
2869 if cnt == parents_recursion_limit:
2869 if cnt == parents_recursion_limit:
2870 # this will prevent accidental infinit loops
2870 # this will prevent accidental infinit loops
2871 log.error('more than %s parents found for group %s, stopping '
2871 log.error('more than %s parents found for group %s, stopping '
2872 'recursive parent fetching', parents_recursion_limit, self)
2872 'recursive parent fetching', parents_recursion_limit, self)
2873 break
2873 break
2874
2874
2875 groups.insert(0, gr)
2875 groups.insert(0, gr)
2876 return groups
2876 return groups
2877
2877
2878 @property
2878 @property
2879 def last_commit_cache_update_diff(self):
2879 def last_commit_cache_update_diff(self):
2880 return time.time() - (safe_int(self.changeset_cache.get('updated_on')) or 0)
2880 return time.time() - (safe_int(self.changeset_cache.get('updated_on')) or 0)
2881
2881
2882 @classmethod
2882 @classmethod
2883 def _load_commit_change(cls, last_commit_cache):
2883 def _load_commit_change(cls, last_commit_cache):
2884 from rhodecode.lib.vcs.utils.helpers import parse_datetime
2884 from rhodecode.lib.vcs.utils.helpers import parse_datetime
2885 empty_date = datetime.datetime.fromtimestamp(0)
2885 empty_date = datetime.datetime.fromtimestamp(0)
2886 date_latest = last_commit_cache.get('date', empty_date)
2886 date_latest = last_commit_cache.get('date', empty_date)
2887 try:
2887 try:
2888 return parse_datetime(date_latest)
2888 return parse_datetime(date_latest)
2889 except Exception:
2889 except Exception:
2890 return empty_date
2890 return empty_date
2891
2891
2892 @property
2892 @property
2893 def last_commit_change(self):
2893 def last_commit_change(self):
2894 return self._load_commit_change(self.changeset_cache)
2894 return self._load_commit_change(self.changeset_cache)
2895
2895
2896 @property
2896 @property
2897 def last_db_change(self):
2897 def last_db_change(self):
2898 return self.updated_on
2898 return self.updated_on
2899
2899
2900 @property
2900 @property
2901 def children(self):
2901 def children(self):
2902 return RepoGroup.query().filter(RepoGroup.parent_group == self)
2902 return RepoGroup.query().filter(RepoGroup.parent_group == self)
2903
2903
2904 @property
2904 @property
2905 def name(self):
2905 def name(self):
2906 return self.group_name.split(RepoGroup.url_sep())[-1]
2906 return self.group_name.split(RepoGroup.url_sep())[-1]
2907
2907
2908 @property
2908 @property
2909 def full_path(self):
2909 def full_path(self):
2910 return self.group_name
2910 return self.group_name
2911
2911
2912 @property
2912 @property
2913 def full_path_splitted(self):
2913 def full_path_splitted(self):
2914 return self.group_name.split(RepoGroup.url_sep())
2914 return self.group_name.split(RepoGroup.url_sep())
2915
2915
2916 @property
2916 @property
2917 def repositories(self):
2917 def repositories(self):
2918 return Repository.query()\
2918 return Repository.query()\
2919 .filter(Repository.group == self)\
2919 .filter(Repository.group == self)\
2920 .order_by(Repository.repo_name)
2920 .order_by(Repository.repo_name)
2921
2921
2922 @property
2922 @property
2923 def repositories_recursive_count(self):
2923 def repositories_recursive_count(self):
2924 cnt = self.repositories.count()
2924 cnt = self.repositories.count()
2925
2925
2926 def children_count(group):
2926 def children_count(group):
2927 cnt = 0
2927 cnt = 0
2928 for child in group.children:
2928 for child in group.children:
2929 cnt += child.repositories.count()
2929 cnt += child.repositories.count()
2930 cnt += children_count(child)
2930 cnt += children_count(child)
2931 return cnt
2931 return cnt
2932
2932
2933 return cnt + children_count(self)
2933 return cnt + children_count(self)
2934
2934
2935 def _recursive_objects(self, include_repos=True, include_groups=True):
2935 def _recursive_objects(self, include_repos=True, include_groups=True):
2936 all_ = []
2936 all_ = []
2937
2937
2938 def _get_members(root_gr):
2938 def _get_members(root_gr):
2939 if include_repos:
2939 if include_repos:
2940 for r in root_gr.repositories:
2940 for r in root_gr.repositories:
2941 all_.append(r)
2941 all_.append(r)
2942 childs = root_gr.children.all()
2942 childs = root_gr.children.all()
2943 if childs:
2943 if childs:
2944 for gr in childs:
2944 for gr in childs:
2945 if include_groups:
2945 if include_groups:
2946 all_.append(gr)
2946 all_.append(gr)
2947 _get_members(gr)
2947 _get_members(gr)
2948
2948
2949 root_group = []
2949 root_group = []
2950 if include_groups:
2950 if include_groups:
2951 root_group = [self]
2951 root_group = [self]
2952
2952
2953 _get_members(self)
2953 _get_members(self)
2954 return root_group + all_
2954 return root_group + all_
2955
2955
2956 def recursive_groups_and_repos(self):
2956 def recursive_groups_and_repos(self):
2957 """
2957 """
2958 Recursive return all groups, with repositories in those groups
2958 Recursive return all groups, with repositories in those groups
2959 """
2959 """
2960 return self._recursive_objects()
2960 return self._recursive_objects()
2961
2961
2962 def recursive_groups(self):
2962 def recursive_groups(self):
2963 """
2963 """
2964 Returns all children groups for this group including children of children
2964 Returns all children groups for this group including children of children
2965 """
2965 """
2966 return self._recursive_objects(include_repos=False)
2966 return self._recursive_objects(include_repos=False)
2967
2967
2968 def recursive_repos(self):
2968 def recursive_repos(self):
2969 """
2969 """
2970 Returns all children repositories for this group
2970 Returns all children repositories for this group
2971 """
2971 """
2972 return self._recursive_objects(include_groups=False)
2972 return self._recursive_objects(include_groups=False)
2973
2973
2974 def get_new_name(self, group_name):
2974 def get_new_name(self, group_name):
2975 """
2975 """
2976 returns new full group name based on parent and new name
2976 returns new full group name based on parent and new name
2977
2977
2978 :param group_name:
2978 :param group_name:
2979 """
2979 """
2980 path_prefix = (self.parent_group.full_path_splitted if
2980 path_prefix = (self.parent_group.full_path_splitted if
2981 self.parent_group else [])
2981 self.parent_group else [])
2982 return RepoGroup.url_sep().join(path_prefix + [group_name])
2982 return RepoGroup.url_sep().join(path_prefix + [group_name])
2983
2983
2984 def update_commit_cache(self, config=None):
2984 def update_commit_cache(self, config=None):
2985 """
2985 """
2986 Update cache of last commit for newest repository inside this repository group.
2986 Update cache of last commit for newest repository inside this repository group.
2987 cache_keys should be::
2987 cache_keys should be::
2988
2988
2989 source_repo_id
2989 source_repo_id
2990 short_id
2990 short_id
2991 raw_id
2991 raw_id
2992 revision
2992 revision
2993 parents
2993 parents
2994 message
2994 message
2995 date
2995 date
2996 author
2996 author
2997
2997
2998 """
2998 """
2999 from rhodecode.lib.vcs.utils.helpers import parse_datetime
2999 from rhodecode.lib.vcs.utils.helpers import parse_datetime
3000 empty_date = datetime.datetime.fromtimestamp(0)
3000 empty_date = datetime.datetime.fromtimestamp(0)
3001
3001
3002 def repo_groups_and_repos(root_gr):
3002 def repo_groups_and_repos(root_gr):
3003 for _repo in root_gr.repositories:
3003 for _repo in root_gr.repositories:
3004 yield _repo
3004 yield _repo
3005 for child_group in root_gr.children.all():
3005 for child_group in root_gr.children.all():
3006 yield child_group
3006 yield child_group
3007
3007
3008 latest_repo_cs_cache = {}
3008 latest_repo_cs_cache = {}
3009 for obj in repo_groups_and_repos(self):
3009 for obj in repo_groups_and_repos(self):
3010 repo_cs_cache = obj.changeset_cache
3010 repo_cs_cache = obj.changeset_cache
3011 date_latest = latest_repo_cs_cache.get('date', empty_date)
3011 date_latest = latest_repo_cs_cache.get('date', empty_date)
3012 date_current = repo_cs_cache.get('date', empty_date)
3012 date_current = repo_cs_cache.get('date', empty_date)
3013 current_timestamp = datetime_to_time(parse_datetime(date_latest))
3013 current_timestamp = datetime_to_time(parse_datetime(date_latest))
3014 if current_timestamp < datetime_to_time(parse_datetime(date_current)):
3014 if current_timestamp < datetime_to_time(parse_datetime(date_current)):
3015 latest_repo_cs_cache = repo_cs_cache
3015 latest_repo_cs_cache = repo_cs_cache
3016 if hasattr(obj, 'repo_id'):
3016 if hasattr(obj, 'repo_id'):
3017 latest_repo_cs_cache['source_repo_id'] = obj.repo_id
3017 latest_repo_cs_cache['source_repo_id'] = obj.repo_id
3018 else:
3018 else:
3019 latest_repo_cs_cache['source_repo_id'] = repo_cs_cache.get('source_repo_id')
3019 latest_repo_cs_cache['source_repo_id'] = repo_cs_cache.get('source_repo_id')
3020
3020
3021 _date_latest = parse_datetime(latest_repo_cs_cache.get('date') or empty_date)
3021 _date_latest = parse_datetime(latest_repo_cs_cache.get('date') or empty_date)
3022
3022
3023 latest_repo_cs_cache['updated_on'] = time.time()
3023 latest_repo_cs_cache['updated_on'] = time.time()
3024 self.changeset_cache = latest_repo_cs_cache
3024 self.changeset_cache = latest_repo_cs_cache
3025 self.updated_on = _date_latest
3025 self.updated_on = _date_latest
3026 Session().add(self)
3026 Session().add(self)
3027 Session().commit()
3027 Session().commit()
3028
3028
3029 log.debug('updated repo group `%s` with new commit cache %s, and last update_date: %s',
3029 log.debug('updated repo group `%s` with new commit cache %s, and last update_date: %s',
3030 self.group_name, latest_repo_cs_cache, _date_latest)
3030 self.group_name, latest_repo_cs_cache, _date_latest)
3031
3031
3032 def permissions(self, with_admins=True, with_owner=True,
3032 def permissions(self, with_admins=True, with_owner=True,
3033 expand_from_user_groups=False):
3033 expand_from_user_groups=False):
3034 """
3034 """
3035 Permissions for repository groups
3035 Permissions for repository groups
3036 """
3036 """
3037 _admin_perm = 'group.admin'
3037 _admin_perm = 'group.admin'
3038
3038
3039 owner_row = []
3039 owner_row = []
3040 if with_owner:
3040 if with_owner:
3041 usr = AttributeDict(self.user.get_dict())
3041 usr = AttributeDict(self.user.get_dict())
3042 usr.owner_row = True
3042 usr.owner_row = True
3043 usr.permission = _admin_perm
3043 usr.permission = _admin_perm
3044 owner_row.append(usr)
3044 owner_row.append(usr)
3045
3045
3046 super_admin_ids = []
3046 super_admin_ids = []
3047 super_admin_rows = []
3047 super_admin_rows = []
3048 if with_admins:
3048 if with_admins:
3049 for usr in User.get_all_super_admins():
3049 for usr in User.get_all_super_admins():
3050 super_admin_ids.append(usr.user_id)
3050 super_admin_ids.append(usr.user_id)
3051 # if this admin is also owner, don't double the record
3051 # if this admin is also owner, don't double the record
3052 if usr.user_id == owner_row[0].user_id:
3052 if usr.user_id == owner_row[0].user_id:
3053 owner_row[0].admin_row = True
3053 owner_row[0].admin_row = True
3054 else:
3054 else:
3055 usr = AttributeDict(usr.get_dict())
3055 usr = AttributeDict(usr.get_dict())
3056 usr.admin_row = True
3056 usr.admin_row = True
3057 usr.permission = _admin_perm
3057 usr.permission = _admin_perm
3058 super_admin_rows.append(usr)
3058 super_admin_rows.append(usr)
3059
3059
3060 q = UserRepoGroupToPerm.query().filter(UserRepoGroupToPerm.group == self)
3060 q = UserRepoGroupToPerm.query().filter(UserRepoGroupToPerm.group == self)
3061 q = q.options(joinedload(UserRepoGroupToPerm.group),
3061 q = q.options(joinedload(UserRepoGroupToPerm.group),
3062 joinedload(UserRepoGroupToPerm.user),
3062 joinedload(UserRepoGroupToPerm.user),
3063 joinedload(UserRepoGroupToPerm.permission),)
3063 joinedload(UserRepoGroupToPerm.permission),)
3064
3064
3065 # get owners and admins and permissions. We do a trick of re-writing
3065 # get owners and admins and permissions. We do a trick of re-writing
3066 # objects from sqlalchemy to named-tuples due to sqlalchemy session
3066 # objects from sqlalchemy to named-tuples due to sqlalchemy session
3067 # has a global reference and changing one object propagates to all
3067 # has a global reference and changing one object propagates to all
3068 # others. This means if admin is also an owner admin_row that change
3068 # others. This means if admin is also an owner admin_row that change
3069 # would propagate to both objects
3069 # would propagate to both objects
3070 perm_rows = []
3070 perm_rows = []
3071 for _usr in q.all():
3071 for _usr in q.all():
3072 usr = AttributeDict(_usr.user.get_dict())
3072 usr = AttributeDict(_usr.user.get_dict())
3073 # if this user is also owner/admin, mark as duplicate record
3073 # if this user is also owner/admin, mark as duplicate record
3074 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
3074 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
3075 usr.duplicate_perm = True
3075 usr.duplicate_perm = True
3076 usr.permission = _usr.permission.permission_name
3076 usr.permission = _usr.permission.permission_name
3077 perm_rows.append(usr)
3077 perm_rows.append(usr)
3078
3078
3079 # filter the perm rows by 'default' first and then sort them by
3079 # filter the perm rows by 'default' first and then sort them by
3080 # admin,write,read,none permissions sorted again alphabetically in
3080 # admin,write,read,none permissions sorted again alphabetically in
3081 # each group
3081 # each group
3082 perm_rows = sorted(perm_rows, key=display_user_sort)
3082 perm_rows = sorted(perm_rows, key=display_user_sort)
3083
3083
3084 user_groups_rows = []
3084 user_groups_rows = []
3085 if expand_from_user_groups:
3085 if expand_from_user_groups:
3086 for ug in self.permission_user_groups(with_members=True):
3086 for ug in self.permission_user_groups(with_members=True):
3087 for user_data in ug.members:
3087 for user_data in ug.members:
3088 user_groups_rows.append(user_data)
3088 user_groups_rows.append(user_data)
3089
3089
3090 return super_admin_rows + owner_row + perm_rows + user_groups_rows
3090 return super_admin_rows + owner_row + perm_rows + user_groups_rows
3091
3091
3092 def permission_user_groups(self, with_members=False):
3092 def permission_user_groups(self, with_members=False):
3093 q = UserGroupRepoGroupToPerm.query()\
3093 q = UserGroupRepoGroupToPerm.query()\
3094 .filter(UserGroupRepoGroupToPerm.group == self)
3094 .filter(UserGroupRepoGroupToPerm.group == self)
3095 q = q.options(joinedload(UserGroupRepoGroupToPerm.group),
3095 q = q.options(joinedload(UserGroupRepoGroupToPerm.group),
3096 joinedload(UserGroupRepoGroupToPerm.users_group),
3096 joinedload(UserGroupRepoGroupToPerm.users_group),
3097 joinedload(UserGroupRepoGroupToPerm.permission),)
3097 joinedload(UserGroupRepoGroupToPerm.permission),)
3098
3098
3099 perm_rows = []
3099 perm_rows = []
3100 for _user_group in q.all():
3100 for _user_group in q.all():
3101 entry = AttributeDict(_user_group.users_group.get_dict())
3101 entry = AttributeDict(_user_group.users_group.get_dict())
3102 entry.permission = _user_group.permission.permission_name
3102 entry.permission = _user_group.permission.permission_name
3103 if with_members:
3103 if with_members:
3104 entry.members = [x.user.get_dict()
3104 entry.members = [x.user.get_dict()
3105 for x in _user_group.users_group.members]
3105 for x in _user_group.users_group.members]
3106 perm_rows.append(entry)
3106 perm_rows.append(entry)
3107
3107
3108 perm_rows = sorted(perm_rows, key=display_user_group_sort)
3108 perm_rows = sorted(perm_rows, key=display_user_group_sort)
3109 return perm_rows
3109 return perm_rows
3110
3110
3111 def get_api_data(self):
3111 def get_api_data(self):
3112 """
3112 """
3113 Common function for generating api data
3113 Common function for generating api data
3114
3114
3115 """
3115 """
3116 group = self
3116 group = self
3117 data = {
3117 data = {
3118 'group_id': group.group_id,
3118 'group_id': group.group_id,
3119 'group_name': group.group_name,
3119 'group_name': group.group_name,
3120 'group_description': group.description_safe,
3120 'group_description': group.description_safe,
3121 'parent_group': group.parent_group.group_name if group.parent_group else None,
3121 'parent_group': group.parent_group.group_name if group.parent_group else None,
3122 'repositories': [x.repo_name for x in group.repositories],
3122 'repositories': [x.repo_name for x in group.repositories],
3123 'owner': group.user.username,
3123 'owner': group.user.username,
3124 }
3124 }
3125 return data
3125 return data
3126
3126
3127 def get_dict(self):
3127 def get_dict(self):
3128 # Since we transformed `group_name` to a hybrid property, we need to
3128 # Since we transformed `group_name` to a hybrid property, we need to
3129 # keep compatibility with the code which uses `group_name` field.
3129 # keep compatibility with the code which uses `group_name` field.
3130 result = super(RepoGroup, self).get_dict()
3130 result = super(RepoGroup, self).get_dict()
3131 result['group_name'] = result.pop('_group_name', None)
3131 result['group_name'] = result.pop('_group_name', None)
3132 result.pop('_changeset_cache', '')
3132 result.pop('_changeset_cache', '')
3133 return result
3133 return result
3134
3134
3135
3135
3136 class Permission(Base, BaseModel):
3136 class Permission(Base, BaseModel):
3137 __tablename__ = 'permissions'
3137 __tablename__ = 'permissions'
3138 __table_args__ = (
3138 __table_args__ = (
3139 Index('p_perm_name_idx', 'permission_name'),
3139 Index('p_perm_name_idx', 'permission_name'),
3140 base_table_args,
3140 base_table_args,
3141 )
3141 )
3142
3142
3143 PERMS = [
3143 PERMS = [
3144 ('hg.admin', _('RhodeCode Super Administrator')),
3144 ('hg.admin', _('RhodeCode Super Administrator')),
3145
3145
3146 ('repository.none', _('Repository no access')),
3146 ('repository.none', _('Repository no access')),
3147 ('repository.read', _('Repository read access')),
3147 ('repository.read', _('Repository read access')),
3148 ('repository.write', _('Repository write access')),
3148 ('repository.write', _('Repository write access')),
3149 ('repository.admin', _('Repository admin access')),
3149 ('repository.admin', _('Repository admin access')),
3150
3150
3151 ('group.none', _('Repository group no access')),
3151 ('group.none', _('Repository group no access')),
3152 ('group.read', _('Repository group read access')),
3152 ('group.read', _('Repository group read access')),
3153 ('group.write', _('Repository group write access')),
3153 ('group.write', _('Repository group write access')),
3154 ('group.admin', _('Repository group admin access')),
3154 ('group.admin', _('Repository group admin access')),
3155
3155
3156 ('usergroup.none', _('User group no access')),
3156 ('usergroup.none', _('User group no access')),
3157 ('usergroup.read', _('User group read access')),
3157 ('usergroup.read', _('User group read access')),
3158 ('usergroup.write', _('User group write access')),
3158 ('usergroup.write', _('User group write access')),
3159 ('usergroup.admin', _('User group admin access')),
3159 ('usergroup.admin', _('User group admin access')),
3160
3160
3161 ('branch.none', _('Branch no permissions')),
3161 ('branch.none', _('Branch no permissions')),
3162 ('branch.merge', _('Branch access by web merge')),
3162 ('branch.merge', _('Branch access by web merge')),
3163 ('branch.push', _('Branch access by push')),
3163 ('branch.push', _('Branch access by push')),
3164 ('branch.push_force', _('Branch access by push with force')),
3164 ('branch.push_force', _('Branch access by push with force')),
3165
3165
3166 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
3166 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
3167 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
3167 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
3168
3168
3169 ('hg.usergroup.create.false', _('User Group creation disabled')),
3169 ('hg.usergroup.create.false', _('User Group creation disabled')),
3170 ('hg.usergroup.create.true', _('User Group creation enabled')),
3170 ('hg.usergroup.create.true', _('User Group creation enabled')),
3171
3171
3172 ('hg.create.none', _('Repository creation disabled')),
3172 ('hg.create.none', _('Repository creation disabled')),
3173 ('hg.create.repository', _('Repository creation enabled')),
3173 ('hg.create.repository', _('Repository creation enabled')),
3174 ('hg.create.write_on_repogroup.true', _('Repository creation enabled with write permission to a repository group')),
3174 ('hg.create.write_on_repogroup.true', _('Repository creation enabled with write permission to a repository group')),
3175 ('hg.create.write_on_repogroup.false', _('Repository creation disabled with write permission to a repository group')),
3175 ('hg.create.write_on_repogroup.false', _('Repository creation disabled with write permission to a repository group')),
3176
3176
3177 ('hg.fork.none', _('Repository forking disabled')),
3177 ('hg.fork.none', _('Repository forking disabled')),
3178 ('hg.fork.repository', _('Repository forking enabled')),
3178 ('hg.fork.repository', _('Repository forking enabled')),
3179
3179
3180 ('hg.register.none', _('Registration disabled')),
3180 ('hg.register.none', _('Registration disabled')),
3181 ('hg.register.manual_activate', _('User Registration with manual account activation')),
3181 ('hg.register.manual_activate', _('User Registration with manual account activation')),
3182 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
3182 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
3183
3183
3184 ('hg.password_reset.enabled', _('Password reset enabled')),
3184 ('hg.password_reset.enabled', _('Password reset enabled')),
3185 ('hg.password_reset.hidden', _('Password reset hidden')),
3185 ('hg.password_reset.hidden', _('Password reset hidden')),
3186 ('hg.password_reset.disabled', _('Password reset disabled')),
3186 ('hg.password_reset.disabled', _('Password reset disabled')),
3187
3187
3188 ('hg.extern_activate.manual', _('Manual activation of external account')),
3188 ('hg.extern_activate.manual', _('Manual activation of external account')),
3189 ('hg.extern_activate.auto', _('Automatic activation of external account')),
3189 ('hg.extern_activate.auto', _('Automatic activation of external account')),
3190
3190
3191 ('hg.inherit_default_perms.false', _('Inherit object permissions from default user disabled')),
3191 ('hg.inherit_default_perms.false', _('Inherit object permissions from default user disabled')),
3192 ('hg.inherit_default_perms.true', _('Inherit object permissions from default user enabled')),
3192 ('hg.inherit_default_perms.true', _('Inherit object permissions from default user enabled')),
3193 ]
3193 ]
3194
3194
3195 # definition of system default permissions for DEFAULT user, created on
3195 # definition of system default permissions for DEFAULT user, created on
3196 # system setup
3196 # system setup
3197 DEFAULT_USER_PERMISSIONS = [
3197 DEFAULT_USER_PERMISSIONS = [
3198 # object perms
3198 # object perms
3199 'repository.read',
3199 'repository.read',
3200 'group.read',
3200 'group.read',
3201 'usergroup.read',
3201 'usergroup.read',
3202 # branch, for backward compat we need same value as before so forced pushed
3202 # branch, for backward compat we need same value as before so forced pushed
3203 'branch.push_force',
3203 'branch.push_force',
3204 # global
3204 # global
3205 'hg.create.repository',
3205 'hg.create.repository',
3206 'hg.repogroup.create.false',
3206 'hg.repogroup.create.false',
3207 'hg.usergroup.create.false',
3207 'hg.usergroup.create.false',
3208 'hg.create.write_on_repogroup.true',
3208 'hg.create.write_on_repogroup.true',
3209 'hg.fork.repository',
3209 'hg.fork.repository',
3210 'hg.register.manual_activate',
3210 'hg.register.manual_activate',
3211 'hg.password_reset.enabled',
3211 'hg.password_reset.enabled',
3212 'hg.extern_activate.auto',
3212 'hg.extern_activate.auto',
3213 'hg.inherit_default_perms.true',
3213 'hg.inherit_default_perms.true',
3214 ]
3214 ]
3215
3215
3216 # defines which permissions are more important higher the more important
3216 # defines which permissions are more important higher the more important
3217 # Weight defines which permissions are more important.
3217 # Weight defines which permissions are more important.
3218 # The higher number the more important.
3218 # The higher number the more important.
3219 PERM_WEIGHTS = {
3219 PERM_WEIGHTS = {
3220 'repository.none': 0,
3220 'repository.none': 0,
3221 'repository.read': 1,
3221 'repository.read': 1,
3222 'repository.write': 3,
3222 'repository.write': 3,
3223 'repository.admin': 4,
3223 'repository.admin': 4,
3224
3224
3225 'group.none': 0,
3225 'group.none': 0,
3226 'group.read': 1,
3226 'group.read': 1,
3227 'group.write': 3,
3227 'group.write': 3,
3228 'group.admin': 4,
3228 'group.admin': 4,
3229
3229
3230 'usergroup.none': 0,
3230 'usergroup.none': 0,
3231 'usergroup.read': 1,
3231 'usergroup.read': 1,
3232 'usergroup.write': 3,
3232 'usergroup.write': 3,
3233 'usergroup.admin': 4,
3233 'usergroup.admin': 4,
3234
3234
3235 'branch.none': 0,
3235 'branch.none': 0,
3236 'branch.merge': 1,
3236 'branch.merge': 1,
3237 'branch.push': 3,
3237 'branch.push': 3,
3238 'branch.push_force': 4,
3238 'branch.push_force': 4,
3239
3239
3240 'hg.repogroup.create.false': 0,
3240 'hg.repogroup.create.false': 0,
3241 'hg.repogroup.create.true': 1,
3241 'hg.repogroup.create.true': 1,
3242
3242
3243 'hg.usergroup.create.false': 0,
3243 'hg.usergroup.create.false': 0,
3244 'hg.usergroup.create.true': 1,
3244 'hg.usergroup.create.true': 1,
3245
3245
3246 'hg.fork.none': 0,
3246 'hg.fork.none': 0,
3247 'hg.fork.repository': 1,
3247 'hg.fork.repository': 1,
3248 'hg.create.none': 0,
3248 'hg.create.none': 0,
3249 'hg.create.repository': 1
3249 'hg.create.repository': 1
3250 }
3250 }
3251
3251
3252 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3252 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3253 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
3253 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
3254 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
3254 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
3255
3255
3256 def __repr__(self):
3256 def __repr__(self):
3257 return "<%s('%s:%s')>" % (
3257 return "<%s('%s:%s')>" % (
3258 self.cls_name, self.permission_id, self.permission_name
3258 self.cls_name, self.permission_id, self.permission_name
3259 )
3259 )
3260
3260
3261 @classmethod
3261 @classmethod
3262 def get_by_key(cls, key):
3262 def get_by_key(cls, key):
3263 return cls.query().filter(cls.permission_name == key).scalar()
3263 return cls.query().filter(cls.permission_name == key).scalar()
3264
3264
3265 @classmethod
3265 @classmethod
3266 def get_default_repo_perms(cls, user_id, repo_id=None):
3266 def get_default_repo_perms(cls, user_id, repo_id=None):
3267 q = Session().query(UserRepoToPerm, Repository, Permission)\
3267 q = Session().query(UserRepoToPerm, Repository, Permission)\
3268 .join((Permission, UserRepoToPerm.permission_id == Permission.permission_id))\
3268 .join((Permission, UserRepoToPerm.permission_id == Permission.permission_id))\
3269 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
3269 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
3270 .filter(UserRepoToPerm.user_id == user_id)
3270 .filter(UserRepoToPerm.user_id == user_id)
3271 if repo_id:
3271 if repo_id:
3272 q = q.filter(UserRepoToPerm.repository_id == repo_id)
3272 q = q.filter(UserRepoToPerm.repository_id == repo_id)
3273 return q.all()
3273 return q.all()
3274
3274
3275 @classmethod
3275 @classmethod
3276 def get_default_repo_branch_perms(cls, user_id, repo_id=None):
3276 def get_default_repo_branch_perms(cls, user_id, repo_id=None):
3277 q = Session().query(UserToRepoBranchPermission, UserRepoToPerm, Permission) \
3277 q = Session().query(UserToRepoBranchPermission, UserRepoToPerm, Permission) \
3278 .join(
3278 .join(
3279 Permission,
3279 Permission,
3280 UserToRepoBranchPermission.permission_id == Permission.permission_id) \
3280 UserToRepoBranchPermission.permission_id == Permission.permission_id) \
3281 .join(
3281 .join(
3282 UserRepoToPerm,
3282 UserRepoToPerm,
3283 UserToRepoBranchPermission.rule_to_perm_id == UserRepoToPerm.repo_to_perm_id) \
3283 UserToRepoBranchPermission.rule_to_perm_id == UserRepoToPerm.repo_to_perm_id) \
3284 .filter(UserRepoToPerm.user_id == user_id)
3284 .filter(UserRepoToPerm.user_id == user_id)
3285
3285
3286 if repo_id:
3286 if repo_id:
3287 q = q.filter(UserToRepoBranchPermission.repository_id == repo_id)
3287 q = q.filter(UserToRepoBranchPermission.repository_id == repo_id)
3288 return q.order_by(UserToRepoBranchPermission.rule_order).all()
3288 return q.order_by(UserToRepoBranchPermission.rule_order).all()
3289
3289
3290 @classmethod
3290 @classmethod
3291 def get_default_repo_perms_from_user_group(cls, user_id, repo_id=None):
3291 def get_default_repo_perms_from_user_group(cls, user_id, repo_id=None):
3292 q = Session().query(UserGroupRepoToPerm, Repository, Permission)\
3292 q = Session().query(UserGroupRepoToPerm, Repository, Permission)\
3293 .join(
3293 .join(
3294 Permission,
3294 Permission,
3295 UserGroupRepoToPerm.permission_id == Permission.permission_id)\
3295 UserGroupRepoToPerm.permission_id == Permission.permission_id)\
3296 .join(
3296 .join(
3297 Repository,
3297 Repository,
3298 UserGroupRepoToPerm.repository_id == Repository.repo_id)\
3298 UserGroupRepoToPerm.repository_id == Repository.repo_id)\
3299 .join(
3299 .join(
3300 UserGroup,
3300 UserGroup,
3301 UserGroupRepoToPerm.users_group_id ==
3301 UserGroupRepoToPerm.users_group_id ==
3302 UserGroup.users_group_id)\
3302 UserGroup.users_group_id)\
3303 .join(
3303 .join(
3304 UserGroupMember,
3304 UserGroupMember,
3305 UserGroupRepoToPerm.users_group_id ==
3305 UserGroupRepoToPerm.users_group_id ==
3306 UserGroupMember.users_group_id)\
3306 UserGroupMember.users_group_id)\
3307 .filter(
3307 .filter(
3308 UserGroupMember.user_id == user_id,
3308 UserGroupMember.user_id == user_id,
3309 UserGroup.users_group_active == true())
3309 UserGroup.users_group_active == true())
3310 if repo_id:
3310 if repo_id:
3311 q = q.filter(UserGroupRepoToPerm.repository_id == repo_id)
3311 q = q.filter(UserGroupRepoToPerm.repository_id == repo_id)
3312 return q.all()
3312 return q.all()
3313
3313
3314 @classmethod
3314 @classmethod
3315 def get_default_repo_branch_perms_from_user_group(cls, user_id, repo_id=None):
3315 def get_default_repo_branch_perms_from_user_group(cls, user_id, repo_id=None):
3316 q = Session().query(UserGroupToRepoBranchPermission, UserGroupRepoToPerm, Permission) \
3316 q = Session().query(UserGroupToRepoBranchPermission, UserGroupRepoToPerm, Permission) \
3317 .join(
3317 .join(
3318 Permission,
3318 Permission,
3319 UserGroupToRepoBranchPermission.permission_id == Permission.permission_id) \
3319 UserGroupToRepoBranchPermission.permission_id == Permission.permission_id) \
3320 .join(
3320 .join(
3321 UserGroupRepoToPerm,
3321 UserGroupRepoToPerm,
3322 UserGroupToRepoBranchPermission.rule_to_perm_id == UserGroupRepoToPerm.users_group_to_perm_id) \
3322 UserGroupToRepoBranchPermission.rule_to_perm_id == UserGroupRepoToPerm.users_group_to_perm_id) \
3323 .join(
3323 .join(
3324 UserGroup,
3324 UserGroup,
3325 UserGroupRepoToPerm.users_group_id == UserGroup.users_group_id) \
3325 UserGroupRepoToPerm.users_group_id == UserGroup.users_group_id) \
3326 .join(
3326 .join(
3327 UserGroupMember,
3327 UserGroupMember,
3328 UserGroupRepoToPerm.users_group_id == UserGroupMember.users_group_id) \
3328 UserGroupRepoToPerm.users_group_id == UserGroupMember.users_group_id) \
3329 .filter(
3329 .filter(
3330 UserGroupMember.user_id == user_id,
3330 UserGroupMember.user_id == user_id,
3331 UserGroup.users_group_active == true())
3331 UserGroup.users_group_active == true())
3332
3332
3333 if repo_id:
3333 if repo_id:
3334 q = q.filter(UserGroupToRepoBranchPermission.repository_id == repo_id)
3334 q = q.filter(UserGroupToRepoBranchPermission.repository_id == repo_id)
3335 return q.order_by(UserGroupToRepoBranchPermission.rule_order).all()
3335 return q.order_by(UserGroupToRepoBranchPermission.rule_order).all()
3336
3336
3337 @classmethod
3337 @classmethod
3338 def get_default_group_perms(cls, user_id, repo_group_id=None):
3338 def get_default_group_perms(cls, user_id, repo_group_id=None):
3339 q = Session().query(UserRepoGroupToPerm, RepoGroup, Permission)\
3339 q = Session().query(UserRepoGroupToPerm, RepoGroup, Permission)\
3340 .join(
3340 .join(
3341 Permission,
3341 Permission,
3342 UserRepoGroupToPerm.permission_id == Permission.permission_id)\
3342 UserRepoGroupToPerm.permission_id == Permission.permission_id)\
3343 .join(
3343 .join(
3344 RepoGroup,
3344 RepoGroup,
3345 UserRepoGroupToPerm.group_id == RepoGroup.group_id)\
3345 UserRepoGroupToPerm.group_id == RepoGroup.group_id)\
3346 .filter(UserRepoGroupToPerm.user_id == user_id)
3346 .filter(UserRepoGroupToPerm.user_id == user_id)
3347 if repo_group_id:
3347 if repo_group_id:
3348 q = q.filter(UserRepoGroupToPerm.group_id == repo_group_id)
3348 q = q.filter(UserRepoGroupToPerm.group_id == repo_group_id)
3349 return q.all()
3349 return q.all()
3350
3350
3351 @classmethod
3351 @classmethod
3352 def get_default_group_perms_from_user_group(
3352 def get_default_group_perms_from_user_group(
3353 cls, user_id, repo_group_id=None):
3353 cls, user_id, repo_group_id=None):
3354 q = Session().query(UserGroupRepoGroupToPerm, RepoGroup, Permission)\
3354 q = Session().query(UserGroupRepoGroupToPerm, RepoGroup, Permission)\
3355 .join(
3355 .join(
3356 Permission,
3356 Permission,
3357 UserGroupRepoGroupToPerm.permission_id ==
3357 UserGroupRepoGroupToPerm.permission_id ==
3358 Permission.permission_id)\
3358 Permission.permission_id)\
3359 .join(
3359 .join(
3360 RepoGroup,
3360 RepoGroup,
3361 UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id)\
3361 UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id)\
3362 .join(
3362 .join(
3363 UserGroup,
3363 UserGroup,
3364 UserGroupRepoGroupToPerm.users_group_id ==
3364 UserGroupRepoGroupToPerm.users_group_id ==
3365 UserGroup.users_group_id)\
3365 UserGroup.users_group_id)\
3366 .join(
3366 .join(
3367 UserGroupMember,
3367 UserGroupMember,
3368 UserGroupRepoGroupToPerm.users_group_id ==
3368 UserGroupRepoGroupToPerm.users_group_id ==
3369 UserGroupMember.users_group_id)\
3369 UserGroupMember.users_group_id)\
3370 .filter(
3370 .filter(
3371 UserGroupMember.user_id == user_id,
3371 UserGroupMember.user_id == user_id,
3372 UserGroup.users_group_active == true())
3372 UserGroup.users_group_active == true())
3373 if repo_group_id:
3373 if repo_group_id:
3374 q = q.filter(UserGroupRepoGroupToPerm.group_id == repo_group_id)
3374 q = q.filter(UserGroupRepoGroupToPerm.group_id == repo_group_id)
3375 return q.all()
3375 return q.all()
3376
3376
3377 @classmethod
3377 @classmethod
3378 def get_default_user_group_perms(cls, user_id, user_group_id=None):
3378 def get_default_user_group_perms(cls, user_id, user_group_id=None):
3379 q = Session().query(UserUserGroupToPerm, UserGroup, Permission)\
3379 q = Session().query(UserUserGroupToPerm, UserGroup, Permission)\
3380 .join((Permission, UserUserGroupToPerm.permission_id == Permission.permission_id))\
3380 .join((Permission, UserUserGroupToPerm.permission_id == Permission.permission_id))\
3381 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
3381 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
3382 .filter(UserUserGroupToPerm.user_id == user_id)
3382 .filter(UserUserGroupToPerm.user_id == user_id)
3383 if user_group_id:
3383 if user_group_id:
3384 q = q.filter(UserUserGroupToPerm.user_group_id == user_group_id)
3384 q = q.filter(UserUserGroupToPerm.user_group_id == user_group_id)
3385 return q.all()
3385 return q.all()
3386
3386
3387 @classmethod
3387 @classmethod
3388 def get_default_user_group_perms_from_user_group(
3388 def get_default_user_group_perms_from_user_group(
3389 cls, user_id, user_group_id=None):
3389 cls, user_id, user_group_id=None):
3390 TargetUserGroup = aliased(UserGroup, name='target_user_group')
3390 TargetUserGroup = aliased(UserGroup, name='target_user_group')
3391 q = Session().query(UserGroupUserGroupToPerm, UserGroup, Permission)\
3391 q = Session().query(UserGroupUserGroupToPerm, UserGroup, Permission)\
3392 .join(
3392 .join(
3393 Permission,
3393 Permission,
3394 UserGroupUserGroupToPerm.permission_id ==
3394 UserGroupUserGroupToPerm.permission_id ==
3395 Permission.permission_id)\
3395 Permission.permission_id)\
3396 .join(
3396 .join(
3397 TargetUserGroup,
3397 TargetUserGroup,
3398 UserGroupUserGroupToPerm.target_user_group_id ==
3398 UserGroupUserGroupToPerm.target_user_group_id ==
3399 TargetUserGroup.users_group_id)\
3399 TargetUserGroup.users_group_id)\
3400 .join(
3400 .join(
3401 UserGroup,
3401 UserGroup,
3402 UserGroupUserGroupToPerm.user_group_id ==
3402 UserGroupUserGroupToPerm.user_group_id ==
3403 UserGroup.users_group_id)\
3403 UserGroup.users_group_id)\
3404 .join(
3404 .join(
3405 UserGroupMember,
3405 UserGroupMember,
3406 UserGroupUserGroupToPerm.user_group_id ==
3406 UserGroupUserGroupToPerm.user_group_id ==
3407 UserGroupMember.users_group_id)\
3407 UserGroupMember.users_group_id)\
3408 .filter(
3408 .filter(
3409 UserGroupMember.user_id == user_id,
3409 UserGroupMember.user_id == user_id,
3410 UserGroup.users_group_active == true())
3410 UserGroup.users_group_active == true())
3411 if user_group_id:
3411 if user_group_id:
3412 q = q.filter(
3412 q = q.filter(
3413 UserGroupUserGroupToPerm.user_group_id == user_group_id)
3413 UserGroupUserGroupToPerm.user_group_id == user_group_id)
3414
3414
3415 return q.all()
3415 return q.all()
3416
3416
3417
3417
3418 class UserRepoToPerm(Base, BaseModel):
3418 class UserRepoToPerm(Base, BaseModel):
3419 __tablename__ = 'repo_to_perm'
3419 __tablename__ = 'repo_to_perm'
3420 __table_args__ = (
3420 __table_args__ = (
3421 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
3421 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
3422 base_table_args
3422 base_table_args
3423 )
3423 )
3424
3424
3425 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3425 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3426 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3426 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3427 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3427 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3428 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
3428 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
3429
3429
3430 user = relationship('User', back_populates="repo_to_perm")
3430 user = relationship('User', back_populates="repo_to_perm")
3431 repository = relationship('Repository', back_populates="repo_to_perm")
3431 repository = relationship('Repository', back_populates="repo_to_perm")
3432 permission = relationship('Permission')
3432 permission = relationship('Permission')
3433
3433
3434 branch_perm_entry = relationship('UserToRepoBranchPermission', cascade="all, delete-orphan", lazy='joined', back_populates='user_repo_to_perm')
3434 branch_perm_entry = relationship('UserToRepoBranchPermission', cascade="all, delete-orphan", lazy='joined', back_populates='user_repo_to_perm')
3435
3435
3436 @classmethod
3436 @classmethod
3437 def create(cls, user, repository, permission):
3437 def create(cls, user, repository, permission):
3438 n = cls()
3438 n = cls()
3439 n.user = user
3439 n.user = user
3440 n.repository = repository
3440 n.repository = repository
3441 n.permission = permission
3441 n.permission = permission
3442 Session().add(n)
3442 Session().add(n)
3443 return n
3443 return n
3444
3444
3445 def __repr__(self):
3445 def __repr__(self):
3446 return f'<{self.user} => {self.repository} >'
3446 return f'<{self.user} => {self.repository} >'
3447
3447
3448
3448
3449 class UserUserGroupToPerm(Base, BaseModel):
3449 class UserUserGroupToPerm(Base, BaseModel):
3450 __tablename__ = 'user_user_group_to_perm'
3450 __tablename__ = 'user_user_group_to_perm'
3451 __table_args__ = (
3451 __table_args__ = (
3452 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
3452 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
3453 base_table_args
3453 base_table_args
3454 )
3454 )
3455
3455
3456 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3456 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3457 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3457 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3458 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3458 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3459 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3459 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3460
3460
3461 user = relationship('User', back_populates='user_group_to_perm')
3461 user = relationship('User', back_populates='user_group_to_perm')
3462 user_group = relationship('UserGroup', back_populates='user_user_group_to_perm')
3462 user_group = relationship('UserGroup', back_populates='user_user_group_to_perm')
3463 permission = relationship('Permission')
3463 permission = relationship('Permission')
3464
3464
3465 @classmethod
3465 @classmethod
3466 def create(cls, user, user_group, permission):
3466 def create(cls, user, user_group, permission):
3467 n = cls()
3467 n = cls()
3468 n.user = user
3468 n.user = user
3469 n.user_group = user_group
3469 n.user_group = user_group
3470 n.permission = permission
3470 n.permission = permission
3471 Session().add(n)
3471 Session().add(n)
3472 return n
3472 return n
3473
3473
3474 def __repr__(self):
3474 def __repr__(self):
3475 return f'<{self.user} => {self.user_group} >'
3475 return f'<{self.user} => {self.user_group} >'
3476
3476
3477
3477
3478 class UserToPerm(Base, BaseModel):
3478 class UserToPerm(Base, BaseModel):
3479 __tablename__ = 'user_to_perm'
3479 __tablename__ = 'user_to_perm'
3480 __table_args__ = (
3480 __table_args__ = (
3481 UniqueConstraint('user_id', 'permission_id'),
3481 UniqueConstraint('user_id', 'permission_id'),
3482 base_table_args
3482 base_table_args
3483 )
3483 )
3484
3484
3485 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3485 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3486 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3486 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3487 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3487 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3488
3488
3489 user = relationship('User', back_populates='user_perms')
3489 user = relationship('User', back_populates='user_perms')
3490 permission = relationship('Permission', lazy='joined')
3490 permission = relationship('Permission', lazy='joined')
3491
3491
3492 def __repr__(self):
3492 def __repr__(self):
3493 return f'<{self.user} => {self.permission} >'
3493 return f'<{self.user} => {self.permission} >'
3494
3494
3495
3495
3496 class UserGroupRepoToPerm(Base, BaseModel):
3496 class UserGroupRepoToPerm(Base, BaseModel):
3497 __tablename__ = 'users_group_repo_to_perm'
3497 __tablename__ = 'users_group_repo_to_perm'
3498 __table_args__ = (
3498 __table_args__ = (
3499 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
3499 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
3500 base_table_args
3500 base_table_args
3501 )
3501 )
3502
3502
3503 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3503 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3504 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3504 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3505 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3505 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3506 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
3506 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
3507
3507
3508 users_group = relationship('UserGroup', back_populates='users_group_repo_to_perm')
3508 users_group = relationship('UserGroup', back_populates='users_group_repo_to_perm')
3509 permission = relationship('Permission')
3509 permission = relationship('Permission')
3510 repository = relationship('Repository', back_populates='users_group_to_perm')
3510 repository = relationship('Repository', back_populates='users_group_to_perm')
3511 user_group_branch_perms = relationship('UserGroupToRepoBranchPermission', cascade='all', back_populates='user_group_repo_to_perm')
3511 user_group_branch_perms = relationship('UserGroupToRepoBranchPermission', cascade='all', back_populates='user_group_repo_to_perm')
3512
3512
3513 @classmethod
3513 @classmethod
3514 def create(cls, users_group, repository, permission):
3514 def create(cls, users_group, repository, permission):
3515 n = cls()
3515 n = cls()
3516 n.users_group = users_group
3516 n.users_group = users_group
3517 n.repository = repository
3517 n.repository = repository
3518 n.permission = permission
3518 n.permission = permission
3519 Session().add(n)
3519 Session().add(n)
3520 return n
3520 return n
3521
3521
3522 def __repr__(self):
3522 def __repr__(self):
3523 return f'<UserGroupRepoToPerm:{self.users_group} => {self.repository} >'
3523 return f'<UserGroupRepoToPerm:{self.users_group} => {self.repository} >'
3524
3524
3525
3525
3526 class UserGroupUserGroupToPerm(Base, BaseModel):
3526 class UserGroupUserGroupToPerm(Base, BaseModel):
3527 __tablename__ = 'user_group_user_group_to_perm'
3527 __tablename__ = 'user_group_user_group_to_perm'
3528 __table_args__ = (
3528 __table_args__ = (
3529 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
3529 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
3530 CheckConstraint('target_user_group_id != user_group_id'),
3530 CheckConstraint('target_user_group_id != user_group_id'),
3531 base_table_args
3531 base_table_args
3532 )
3532 )
3533
3533
3534 user_group_user_group_to_perm_id = Column("user_group_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3534 user_group_user_group_to_perm_id = Column("user_group_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3535 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3535 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3536 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3536 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3537 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3537 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3538
3538
3539 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id', back_populates='user_group_user_group_to_perm')
3539 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id', back_populates='user_group_user_group_to_perm')
3540 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
3540 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
3541 permission = relationship('Permission')
3541 permission = relationship('Permission')
3542
3542
3543 @classmethod
3543 @classmethod
3544 def create(cls, target_user_group, user_group, permission):
3544 def create(cls, target_user_group, user_group, permission):
3545 n = cls()
3545 n = cls()
3546 n.target_user_group = target_user_group
3546 n.target_user_group = target_user_group
3547 n.user_group = user_group
3547 n.user_group = user_group
3548 n.permission = permission
3548 n.permission = permission
3549 Session().add(n)
3549 Session().add(n)
3550 return n
3550 return n
3551
3551
3552 def __repr__(self):
3552 def __repr__(self):
3553 return f'<UserGroupUserGroup:{self.target_user_group} => {self.user_group} >'
3553 return f'<UserGroupUserGroup:{self.target_user_group} => {self.user_group} >'
3554
3554
3555
3555
3556 class UserGroupToPerm(Base, BaseModel):
3556 class UserGroupToPerm(Base, BaseModel):
3557 __tablename__ = 'users_group_to_perm'
3557 __tablename__ = 'users_group_to_perm'
3558 __table_args__ = (
3558 __table_args__ = (
3559 UniqueConstraint('users_group_id', 'permission_id',),
3559 UniqueConstraint('users_group_id', 'permission_id',),
3560 base_table_args
3560 base_table_args
3561 )
3561 )
3562
3562
3563 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3563 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3564 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3564 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3565 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3565 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3566
3566
3567 users_group = relationship('UserGroup', back_populates='users_group_to_perm')
3567 users_group = relationship('UserGroup', back_populates='users_group_to_perm')
3568 permission = relationship('Permission')
3568 permission = relationship('Permission')
3569
3569
3570
3570
3571 class UserRepoGroupToPerm(Base, BaseModel):
3571 class UserRepoGroupToPerm(Base, BaseModel):
3572 __tablename__ = 'user_repo_group_to_perm'
3572 __tablename__ = 'user_repo_group_to_perm'
3573 __table_args__ = (
3573 __table_args__ = (
3574 UniqueConstraint('user_id', 'group_id', 'permission_id'),
3574 UniqueConstraint('user_id', 'group_id', 'permission_id'),
3575 base_table_args
3575 base_table_args
3576 )
3576 )
3577
3577
3578 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3578 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3579 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3579 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3580 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
3580 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
3581 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3581 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3582
3582
3583 user = relationship('User', back_populates='repo_group_to_perm')
3583 user = relationship('User', back_populates='repo_group_to_perm')
3584 group = relationship('RepoGroup', back_populates='repo_group_to_perm')
3584 group = relationship('RepoGroup', back_populates='repo_group_to_perm')
3585 permission = relationship('Permission')
3585 permission = relationship('Permission')
3586
3586
3587 @classmethod
3587 @classmethod
3588 def create(cls, user, repository_group, permission):
3588 def create(cls, user, repository_group, permission):
3589 n = cls()
3589 n = cls()
3590 n.user = user
3590 n.user = user
3591 n.group = repository_group
3591 n.group = repository_group
3592 n.permission = permission
3592 n.permission = permission
3593 Session().add(n)
3593 Session().add(n)
3594 return n
3594 return n
3595
3595
3596
3596
3597 class UserGroupRepoGroupToPerm(Base, BaseModel):
3597 class UserGroupRepoGroupToPerm(Base, BaseModel):
3598 __tablename__ = 'users_group_repo_group_to_perm'
3598 __tablename__ = 'users_group_repo_group_to_perm'
3599 __table_args__ = (
3599 __table_args__ = (
3600 UniqueConstraint('users_group_id', 'group_id'),
3600 UniqueConstraint('users_group_id', 'group_id'),
3601 base_table_args
3601 base_table_args
3602 )
3602 )
3603
3603
3604 users_group_repo_group_to_perm_id = Column("users_group_repo_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3604 users_group_repo_group_to_perm_id = Column("users_group_repo_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3605 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3605 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3606 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
3606 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
3607 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3607 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3608
3608
3609 users_group = relationship('UserGroup', back_populates='users_group_repo_group_to_perm')
3609 users_group = relationship('UserGroup', back_populates='users_group_repo_group_to_perm')
3610 permission = relationship('Permission')
3610 permission = relationship('Permission')
3611 group = relationship('RepoGroup', back_populates='users_group_to_perm')
3611 group = relationship('RepoGroup', back_populates='users_group_to_perm')
3612
3612
3613 @classmethod
3613 @classmethod
3614 def create(cls, user_group, repository_group, permission):
3614 def create(cls, user_group, repository_group, permission):
3615 n = cls()
3615 n = cls()
3616 n.users_group = user_group
3616 n.users_group = user_group
3617 n.group = repository_group
3617 n.group = repository_group
3618 n.permission = permission
3618 n.permission = permission
3619 Session().add(n)
3619 Session().add(n)
3620 return n
3620 return n
3621
3621
3622 def __repr__(self):
3622 def __repr__(self):
3623 return '<UserGroupRepoGroupToPerm:%s => %s >' % (self.users_group, self.group)
3623 return '<UserGroupRepoGroupToPerm:%s => %s >' % (self.users_group, self.group)
3624
3624
3625
3625
3626 class Statistics(Base, BaseModel):
3626 class Statistics(Base, BaseModel):
3627 __tablename__ = 'statistics'
3627 __tablename__ = 'statistics'
3628 __table_args__ = (
3628 __table_args__ = (
3629 base_table_args
3629 base_table_args
3630 )
3630 )
3631
3631
3632 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3632 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3633 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
3633 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
3634 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
3634 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
3635 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False) #JSON data
3635 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False) #JSON data
3636 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False) #JSON data
3636 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False) #JSON data
3637 languages = Column("languages", LargeBinary(1000000), nullable=False) #JSON data
3637 languages = Column("languages", LargeBinary(1000000), nullable=False) #JSON data
3638
3638
3639 repository = relationship('Repository', single_parent=True, viewonly=True)
3639 repository = relationship('Repository', single_parent=True, viewonly=True)
3640
3640
3641
3641
3642 class UserFollowing(Base, BaseModel):
3642 class UserFollowing(Base, BaseModel):
3643 __tablename__ = 'user_followings'
3643 __tablename__ = 'user_followings'
3644 __table_args__ = (
3644 __table_args__ = (
3645 UniqueConstraint('user_id', 'follows_repository_id'),
3645 UniqueConstraint('user_id', 'follows_repository_id'),
3646 UniqueConstraint('user_id', 'follows_user_id'),
3646 UniqueConstraint('user_id', 'follows_user_id'),
3647 base_table_args
3647 base_table_args
3648 )
3648 )
3649
3649
3650 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3650 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3651 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3651 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3652 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
3652 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
3653 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
3653 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
3654 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
3654 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
3655
3655
3656 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id', back_populates='followings')
3656 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id', back_populates='followings')
3657
3657
3658 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
3658 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
3659 follows_repository = relationship('Repository', order_by='Repository.repo_name', back_populates='followers')
3659 follows_repository = relationship('Repository', order_by='Repository.repo_name', back_populates='followers')
3660
3660
3661 @classmethod
3661 @classmethod
3662 def get_repo_followers(cls, repo_id):
3662 def get_repo_followers(cls, repo_id):
3663 return cls.query().filter(cls.follows_repo_id == repo_id)
3663 return cls.query().filter(cls.follows_repo_id == repo_id)
3664
3664
3665
3665
3666 class CacheKey(Base, BaseModel):
3666 class CacheKey(Base, BaseModel):
3667 __tablename__ = 'cache_invalidation'
3667 __tablename__ = 'cache_invalidation'
3668 __table_args__ = (
3668 __table_args__ = (
3669 UniqueConstraint('cache_key'),
3669 UniqueConstraint('cache_key'),
3670 Index('key_idx', 'cache_key'),
3670 Index('key_idx', 'cache_key'),
3671 Index('cache_args_idx', 'cache_args'),
3671 Index('cache_args_idx', 'cache_args'),
3672 base_table_args,
3672 base_table_args,
3673 )
3673 )
3674
3674
3675 CACHE_TYPE_FEED = 'FEED'
3675 CACHE_TYPE_FEED = 'FEED'
3676
3676
3677 # namespaces used to register process/thread aware caches
3677 # namespaces used to register process/thread aware caches
3678 REPO_INVALIDATION_NAMESPACE = 'repo_cache.v1:{repo_id}'
3678 REPO_INVALIDATION_NAMESPACE = 'repo_cache.v1:{repo_id}'
3679
3679
3680 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3680 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3681 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
3681 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
3682 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
3682 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
3683 cache_state_uid = Column("cache_state_uid", String(255), nullable=True, unique=None, default=None)
3683 cache_state_uid = Column("cache_state_uid", String(255), nullable=True, unique=None, default=None)
3684 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
3684 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
3685
3685
3686 def __init__(self, cache_key, cache_args='', cache_state_uid=None):
3686 def __init__(self, cache_key, cache_args='', cache_state_uid=None):
3687 self.cache_key = cache_key
3687 self.cache_key = cache_key
3688 self.cache_args = cache_args
3688 self.cache_args = cache_args
3689 self.cache_active = False
3689 self.cache_active = False
3690 # first key should be same for all entries, since all workers should share it
3690 # first key should be same for all entries, since all workers should share it
3691 self.cache_state_uid = cache_state_uid or self.generate_new_state_uid()
3691 self.cache_state_uid = cache_state_uid or self.generate_new_state_uid()
3692
3692
3693 def __repr__(self):
3693 def __repr__(self):
3694 return "<%s('%s:%s[%s]')>" % (
3694 return "<%s('%s:%s[%s]')>" % (
3695 self.cls_name,
3695 self.cls_name,
3696 self.cache_id, self.cache_key, self.cache_active)
3696 self.cache_id, self.cache_key, self.cache_active)
3697
3697
3698 def _cache_key_partition(self):
3698 def _cache_key_partition(self):
3699 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
3699 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
3700 return prefix, repo_name, suffix
3700 return prefix, repo_name, suffix
3701
3701
3702 def get_prefix(self):
3702 def get_prefix(self):
3703 """
3703 """
3704 Try to extract prefix from existing cache key. The key could consist
3704 Try to extract prefix from existing cache key. The key could consist
3705 of prefix, repo_name, suffix
3705 of prefix, repo_name, suffix
3706 """
3706 """
3707 # this returns prefix, repo_name, suffix
3707 # this returns prefix, repo_name, suffix
3708 return self._cache_key_partition()[0]
3708 return self._cache_key_partition()[0]
3709
3709
3710 def get_suffix(self):
3710 def get_suffix(self):
3711 """
3711 """
3712 get suffix that might have been used in _get_cache_key to
3712 get suffix that might have been used in _get_cache_key to
3713 generate self.cache_key. Only used for informational purposes
3713 generate self.cache_key. Only used for informational purposes
3714 in repo_edit.mako.
3714 in repo_edit.mako.
3715 """
3715 """
3716 # prefix, repo_name, suffix
3716 # prefix, repo_name, suffix
3717 return self._cache_key_partition()[2]
3717 return self._cache_key_partition()[2]
3718
3718
3719 @classmethod
3719 @classmethod
3720 def generate_new_state_uid(cls, based_on=None):
3720 def generate_new_state_uid(cls, based_on=None):
3721 if based_on:
3721 if based_on:
3722 return str(uuid.uuid5(uuid.NAMESPACE_URL, safe_str(based_on)))
3722 return str(uuid.uuid5(uuid.NAMESPACE_URL, safe_str(based_on)))
3723 else:
3723 else:
3724 return str(uuid.uuid4())
3724 return str(uuid.uuid4())
3725
3725
3726 @classmethod
3726 @classmethod
3727 def delete_all_cache(cls):
3727 def delete_all_cache(cls):
3728 """
3728 """
3729 Delete all cache keys from database.
3729 Delete all cache keys from database.
3730 Should only be run when all instances are down and all entries
3730 Should only be run when all instances are down and all entries
3731 thus stale.
3731 thus stale.
3732 """
3732 """
3733 cls.query().delete()
3733 cls.query().delete()
3734 Session().commit()
3734 Session().commit()
3735
3735
3736 @classmethod
3736 @classmethod
3737 def set_invalidate(cls, cache_uid, delete=False):
3737 def set_invalidate(cls, cache_uid, delete=False):
3738 """
3738 """
3739 Mark all caches of a repo as invalid in the database.
3739 Mark all caches of a repo as invalid in the database.
3740 """
3740 """
3741
3741
3742 try:
3742 try:
3743 qry = Session().query(cls).filter(cls.cache_args == cache_uid)
3743 qry = Session().query(cls).filter(cls.cache_args == cache_uid)
3744 if delete:
3744 if delete:
3745 qry.delete()
3745 qry.delete()
3746 log.debug('cache objects deleted for cache args %s',
3746 log.debug('cache objects deleted for cache args %s',
3747 safe_str(cache_uid))
3747 safe_str(cache_uid))
3748 else:
3748 else:
3749 qry.update({"cache_active": False,
3749 qry.update({"cache_active": False,
3750 "cache_state_uid": cls.generate_new_state_uid()})
3750 "cache_state_uid": cls.generate_new_state_uid()})
3751 log.debug('cache objects marked as invalid for cache args %s',
3751 log.debug('cache objects marked as invalid for cache args %s',
3752 safe_str(cache_uid))
3752 safe_str(cache_uid))
3753
3753
3754 Session().commit()
3754 Session().commit()
3755 except Exception:
3755 except Exception:
3756 log.exception(
3756 log.exception(
3757 'Cache key invalidation failed for cache args %s',
3757 'Cache key invalidation failed for cache args %s',
3758 safe_str(cache_uid))
3758 safe_str(cache_uid))
3759 Session().rollback()
3759 Session().rollback()
3760
3760
3761 @classmethod
3761 @classmethod
3762 def get_active_cache(cls, cache_key):
3762 def get_active_cache(cls, cache_key):
3763 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
3763 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
3764 if inv_obj:
3764 if inv_obj:
3765 return inv_obj
3765 return inv_obj
3766 return None
3766 return None
3767
3767
3768 @classmethod
3768 @classmethod
3769 def get_namespace_map(cls, namespace):
3769 def get_namespace_map(cls, namespace):
3770 return {
3770 return {
3771 x.cache_key: x
3771 x.cache_key: x
3772 for x in cls.query().filter(cls.cache_args == namespace)}
3772 for x in cls.query().filter(cls.cache_args == namespace)}
3773
3773
3774
3774
3775 class ChangesetComment(Base, BaseModel):
3775 class ChangesetComment(Base, BaseModel):
3776 __tablename__ = 'changeset_comments'
3776 __tablename__ = 'changeset_comments'
3777 __table_args__ = (
3777 __table_args__ = (
3778 Index('cc_revision_idx', 'revision'),
3778 Index('cc_revision_idx', 'revision'),
3779 base_table_args,
3779 base_table_args,
3780 )
3780 )
3781
3781
3782 COMMENT_OUTDATED = 'comment_outdated'
3782 COMMENT_OUTDATED = 'comment_outdated'
3783 COMMENT_TYPE_NOTE = 'note'
3783 COMMENT_TYPE_NOTE = 'note'
3784 COMMENT_TYPE_TODO = 'todo'
3784 COMMENT_TYPE_TODO = 'todo'
3785 COMMENT_TYPES = [COMMENT_TYPE_NOTE, COMMENT_TYPE_TODO]
3785 COMMENT_TYPES = [COMMENT_TYPE_NOTE, COMMENT_TYPE_TODO]
3786
3786
3787 OP_IMMUTABLE = 'immutable'
3787 OP_IMMUTABLE = 'immutable'
3788 OP_CHANGEABLE = 'changeable'
3788 OP_CHANGEABLE = 'changeable'
3789
3789
3790 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
3790 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
3791 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3791 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3792 revision = Column('revision', String(40), nullable=True)
3792 revision = Column('revision', String(40), nullable=True)
3793 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3793 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3794 pull_request_version_id = Column("pull_request_version_id", Integer(), ForeignKey('pull_request_versions.pull_request_version_id'), nullable=True)
3794 pull_request_version_id = Column("pull_request_version_id", Integer(), ForeignKey('pull_request_versions.pull_request_version_id'), nullable=True)
3795 line_no = Column('line_no', Unicode(10), nullable=True)
3795 line_no = Column('line_no', Unicode(10), nullable=True)
3796 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
3796 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
3797 f_path = Column('f_path', Unicode(1000), nullable=True)
3797 f_path = Column('f_path', Unicode(1000), nullable=True)
3798 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
3798 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
3799 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
3799 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
3800 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3800 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3801 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3801 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3802 renderer = Column('renderer', Unicode(64), nullable=True)
3802 renderer = Column('renderer', Unicode(64), nullable=True)
3803 display_state = Column('display_state', Unicode(128), nullable=True)
3803 display_state = Column('display_state', Unicode(128), nullable=True)
3804 immutable_state = Column('immutable_state', Unicode(128), nullable=True, default=OP_CHANGEABLE)
3804 immutable_state = Column('immutable_state', Unicode(128), nullable=True, default=OP_CHANGEABLE)
3805 draft = Column('draft', Boolean(), nullable=True, default=False)
3805 draft = Column('draft', Boolean(), nullable=True, default=False)
3806
3806
3807 comment_type = Column('comment_type', Unicode(128), nullable=True, default=COMMENT_TYPE_NOTE)
3807 comment_type = Column('comment_type', Unicode(128), nullable=True, default=COMMENT_TYPE_NOTE)
3808 resolved_comment_id = Column('resolved_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'), nullable=True)
3808 resolved_comment_id = Column('resolved_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'), nullable=True)
3809
3809
3810 resolved_comment = relationship('ChangesetComment', remote_side=comment_id, back_populates='resolved_by')
3810 resolved_comment = relationship('ChangesetComment', remote_side=comment_id, back_populates='resolved_by')
3811 resolved_by = relationship('ChangesetComment', back_populates='resolved_comment')
3811 resolved_by = relationship('ChangesetComment', back_populates='resolved_comment')
3812
3812
3813 author = relationship('User', lazy='select', back_populates='user_comments')
3813 author = relationship('User', lazy='select', back_populates='user_comments')
3814 repo = relationship('Repository', back_populates='comments')
3814 repo = relationship('Repository', back_populates='comments')
3815 status_change = relationship('ChangesetStatus', cascade="all, delete-orphan", lazy='select', back_populates='comment')
3815 status_change = relationship('ChangesetStatus', cascade="all, delete-orphan", lazy='select', back_populates='comment')
3816 pull_request = relationship('PullRequest', lazy='select', back_populates='comments')
3816 pull_request = relationship('PullRequest', lazy='select', back_populates='comments')
3817 pull_request_version = relationship('PullRequestVersion', lazy='select')
3817 pull_request_version = relationship('PullRequestVersion', lazy='select')
3818 history = relationship('ChangesetCommentHistory', cascade='all, delete-orphan', lazy='select', order_by='ChangesetCommentHistory.version', back_populates="comment")
3818 history = relationship('ChangesetCommentHistory', cascade='all, delete-orphan', lazy='select', order_by='ChangesetCommentHistory.version', back_populates="comment")
3819
3819
3820 @classmethod
3820 @classmethod
3821 def get_users(cls, revision=None, pull_request_id=None):
3821 def get_users(cls, revision=None, pull_request_id=None):
3822 """
3822 """
3823 Returns user associated with this ChangesetComment. ie those
3823 Returns user associated with this ChangesetComment. ie those
3824 who actually commented
3824 who actually commented
3825
3825
3826 :param cls:
3826 :param cls:
3827 :param revision:
3827 :param revision:
3828 """
3828 """
3829 q = Session().query(User).join(ChangesetComment.author)
3829 q = Session().query(User).join(ChangesetComment.author)
3830 if revision:
3830 if revision:
3831 q = q.filter(cls.revision == revision)
3831 q = q.filter(cls.revision == revision)
3832 elif pull_request_id:
3832 elif pull_request_id:
3833 q = q.filter(cls.pull_request_id == pull_request_id)
3833 q = q.filter(cls.pull_request_id == pull_request_id)
3834 return q.all()
3834 return q.all()
3835
3835
3836 @classmethod
3836 @classmethod
3837 def get_index_from_version(cls, pr_version, versions=None, num_versions=None) -> int:
3837 def get_index_from_version(cls, pr_version, versions=None, num_versions=None) -> int:
3838 if pr_version is None:
3838 if pr_version is None:
3839 return 0
3839 return 0
3840
3840
3841 if versions is not None:
3841 if versions is not None:
3842 num_versions = [x.pull_request_version_id for x in versions]
3842 num_versions = [x.pull_request_version_id for x in versions]
3843
3843
3844 num_versions = num_versions or []
3844 num_versions = num_versions or []
3845 try:
3845 try:
3846 return num_versions.index(pr_version) + 1
3846 return num_versions.index(pr_version) + 1
3847 except (IndexError, ValueError):
3847 except (IndexError, ValueError):
3848 return 0
3848 return 0
3849
3849
3850 @property
3850 @property
3851 def outdated(self):
3851 def outdated(self):
3852 return self.display_state == self.COMMENT_OUTDATED
3852 return self.display_state == self.COMMENT_OUTDATED
3853
3853
3854 @property
3854 @property
3855 def outdated_js(self):
3855 def outdated_js(self):
3856 return json.dumps(self.display_state == self.COMMENT_OUTDATED)
3856 return json.dumps(self.display_state == self.COMMENT_OUTDATED)
3857
3857
3858 @property
3858 @property
3859 def immutable(self):
3859 def immutable(self):
3860 return self.immutable_state == self.OP_IMMUTABLE
3860 return self.immutable_state == self.OP_IMMUTABLE
3861
3861
3862 def outdated_at_version(self, version):
3862 def outdated_at_version(self, version):
3863 """
3863 """
3864 Checks if comment is outdated for given pull request version
3864 Checks if comment is outdated for given pull request version
3865 """
3865 """
3866 def version_check():
3866 def version_check():
3867 return self.pull_request_version_id and self.pull_request_version_id != version
3867 return self.pull_request_version_id and self.pull_request_version_id != version
3868
3868
3869 if self.is_inline:
3869 if self.is_inline:
3870 return self.outdated and version_check()
3870 return self.outdated and version_check()
3871 else:
3871 else:
3872 # general comments don't have .outdated set, also latest don't have a version
3872 # general comments don't have .outdated set, also latest don't have a version
3873 return version_check()
3873 return version_check()
3874
3874
3875 def outdated_at_version_js(self, version):
3875 def outdated_at_version_js(self, version):
3876 """
3876 """
3877 Checks if comment is outdated for given pull request version
3877 Checks if comment is outdated for given pull request version
3878 """
3878 """
3879 return json.dumps(self.outdated_at_version(version))
3879 return json.dumps(self.outdated_at_version(version))
3880
3880
3881 def older_than_version(self, version):
3881 def older_than_version(self, version):
3882 """
3882 """
3883 Checks if comment is made from previous version than given
3883 Checks if comment is made from previous version than given
3884 """
3884 """
3885 cur_ver = 0
3885 cur_ver = 0
3886 if self.pull_request_version:
3886 if self.pull_request_version:
3887 cur_ver = self.pull_request_version.pull_request_version_id or cur_ver
3887 cur_ver = self.pull_request_version.pull_request_version_id or cur_ver
3888
3888
3889 if version is None:
3889 if version is None:
3890 return cur_ver != version
3890 return cur_ver != version
3891
3891
3892 return cur_ver < version
3892 return cur_ver < version
3893
3893
3894 def older_than_version_js(self, version):
3894 def older_than_version_js(self, version):
3895 """
3895 """
3896 Checks if comment is made from previous version than given
3896 Checks if comment is made from previous version than given
3897 """
3897 """
3898 return json.dumps(self.older_than_version(version))
3898 return json.dumps(self.older_than_version(version))
3899
3899
3900 @property
3900 @property
3901 def commit_id(self):
3901 def commit_id(self):
3902 """New style naming to stop using .revision"""
3902 """New style naming to stop using .revision"""
3903 return self.revision
3903 return self.revision
3904
3904
3905 @property
3905 @property
3906 def resolved(self):
3906 def resolved(self):
3907 return self.resolved_by[0] if self.resolved_by else None
3907 return self.resolved_by[0] if self.resolved_by else None
3908
3908
3909 @property
3909 @property
3910 def is_todo(self):
3910 def is_todo(self):
3911 return self.comment_type == self.COMMENT_TYPE_TODO
3911 return self.comment_type == self.COMMENT_TYPE_TODO
3912
3912
3913 @property
3913 @property
3914 def is_inline(self):
3914 def is_inline(self):
3915 if self.line_no and self.f_path:
3915 if self.line_no and self.f_path:
3916 return True
3916 return True
3917 return False
3917 return False
3918
3918
3919 @property
3919 @property
3920 def last_version(self):
3920 def last_version(self):
3921 version = 0
3921 version = 0
3922 if self.history:
3922 if self.history:
3923 version = self.history[-1].version
3923 version = self.history[-1].version
3924 return version
3924 return version
3925
3925
3926 def get_index_version(self, versions):
3926 def get_index_version(self, versions):
3927 return self.get_index_from_version(
3927 return self.get_index_from_version(
3928 self.pull_request_version_id, versions)
3928 self.pull_request_version_id, versions)
3929
3929
3930 @property
3930 @property
3931 def review_status(self):
3931 def review_status(self):
3932 if self.status_change:
3932 if self.status_change:
3933 return self.status_change[0].status
3933 return self.status_change[0].status
3934
3934
3935 @property
3935 @property
3936 def review_status_lbl(self):
3936 def review_status_lbl(self):
3937 if self.status_change:
3937 if self.status_change:
3938 return self.status_change[0].status_lbl
3938 return self.status_change[0].status_lbl
3939
3939
3940 def __repr__(self):
3940 def __repr__(self):
3941 if self.comment_id:
3941 if self.comment_id:
3942 return f'<DB:Comment #{self.comment_id}>'
3942 return f'<DB:Comment #{self.comment_id}>'
3943 else:
3943 else:
3944 return f'<DB:Comment at {id(self)!r}>'
3944 return f'<DB:Comment at {id(self)!r}>'
3945
3945
3946 def get_api_data(self):
3946 def get_api_data(self):
3947 comment = self
3947 comment = self
3948
3948
3949 data = {
3949 data = {
3950 'comment_id': comment.comment_id,
3950 'comment_id': comment.comment_id,
3951 'comment_type': comment.comment_type,
3951 'comment_type': comment.comment_type,
3952 'comment_text': comment.text,
3952 'comment_text': comment.text,
3953 'comment_status': comment.status_change,
3953 'comment_status': comment.status_change,
3954 'comment_f_path': comment.f_path,
3954 'comment_f_path': comment.f_path,
3955 'comment_lineno': comment.line_no,
3955 'comment_lineno': comment.line_no,
3956 'comment_author': comment.author,
3956 'comment_author': comment.author,
3957 'comment_created_on': comment.created_on,
3957 'comment_created_on': comment.created_on,
3958 'comment_resolved_by': self.resolved,
3958 'comment_resolved_by': self.resolved,
3959 'comment_commit_id': comment.revision,
3959 'comment_commit_id': comment.revision,
3960 'comment_pull_request_id': comment.pull_request_id,
3960 'comment_pull_request_id': comment.pull_request_id,
3961 'comment_last_version': self.last_version
3961 'comment_last_version': self.last_version
3962 }
3962 }
3963 return data
3963 return data
3964
3964
3965 def __json__(self):
3965 def __json__(self):
3966 data = dict()
3966 data = dict()
3967 data.update(self.get_api_data())
3967 data.update(self.get_api_data())
3968 return data
3968 return data
3969
3969
3970
3970
3971 class ChangesetCommentHistory(Base, BaseModel):
3971 class ChangesetCommentHistory(Base, BaseModel):
3972 __tablename__ = 'changeset_comments_history'
3972 __tablename__ = 'changeset_comments_history'
3973 __table_args__ = (
3973 __table_args__ = (
3974 Index('cch_comment_id_idx', 'comment_id'),
3974 Index('cch_comment_id_idx', 'comment_id'),
3975 base_table_args,
3975 base_table_args,
3976 )
3976 )
3977
3977
3978 comment_history_id = Column('comment_history_id', Integer(), nullable=False, primary_key=True)
3978 comment_history_id = Column('comment_history_id', Integer(), nullable=False, primary_key=True)
3979 comment_id = Column('comment_id', Integer(), ForeignKey('changeset_comments.comment_id'), nullable=False)
3979 comment_id = Column('comment_id', Integer(), ForeignKey('changeset_comments.comment_id'), nullable=False)
3980 version = Column("version", Integer(), nullable=False, default=0)
3980 version = Column("version", Integer(), nullable=False, default=0)
3981 created_by_user_id = Column('created_by_user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
3981 created_by_user_id = Column('created_by_user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
3982 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
3982 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
3983 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3983 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3984 deleted = Column('deleted', Boolean(), default=False)
3984 deleted = Column('deleted', Boolean(), default=False)
3985
3985
3986 author = relationship('User', lazy='joined')
3986 author = relationship('User', lazy='joined')
3987 comment = relationship('ChangesetComment', cascade="all, delete", back_populates="history")
3987 comment = relationship('ChangesetComment', cascade="all, delete", back_populates="history")
3988
3988
3989 @classmethod
3989 @classmethod
3990 def get_version(cls, comment_id):
3990 def get_version(cls, comment_id):
3991 q = Session().query(ChangesetCommentHistory).filter(
3991 q = Session().query(ChangesetCommentHistory).filter(
3992 ChangesetCommentHistory.comment_id == comment_id).order_by(ChangesetCommentHistory.version.desc())
3992 ChangesetCommentHistory.comment_id == comment_id).order_by(ChangesetCommentHistory.version.desc())
3993 if q.count() == 0:
3993 if q.count() == 0:
3994 return 1
3994 return 1
3995 elif q.count() >= q[0].version:
3995 elif q.count() >= q[0].version:
3996 return q.count() + 1
3996 return q.count() + 1
3997 else:
3997 else:
3998 return q[0].version + 1
3998 return q[0].version + 1
3999
3999
4000
4000
4001 class ChangesetStatus(Base, BaseModel):
4001 class ChangesetStatus(Base, BaseModel):
4002 __tablename__ = 'changeset_statuses'
4002 __tablename__ = 'changeset_statuses'
4003 __table_args__ = (
4003 __table_args__ = (
4004 Index('cs_revision_idx', 'revision'),
4004 Index('cs_revision_idx', 'revision'),
4005 Index('cs_version_idx', 'version'),
4005 Index('cs_version_idx', 'version'),
4006 UniqueConstraint('repo_id', 'revision', 'version'),
4006 UniqueConstraint('repo_id', 'revision', 'version'),
4007 base_table_args
4007 base_table_args
4008 )
4008 )
4009
4009
4010 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
4010 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
4011 STATUS_APPROVED = 'approved'
4011 STATUS_APPROVED = 'approved'
4012 STATUS_REJECTED = 'rejected'
4012 STATUS_REJECTED = 'rejected'
4013 STATUS_UNDER_REVIEW = 'under_review'
4013 STATUS_UNDER_REVIEW = 'under_review'
4014
4014
4015 STATUSES = [
4015 STATUSES = [
4016 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
4016 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
4017 (STATUS_APPROVED, _("Approved")),
4017 (STATUS_APPROVED, _("Approved")),
4018 (STATUS_REJECTED, _("Rejected")),
4018 (STATUS_REJECTED, _("Rejected")),
4019 (STATUS_UNDER_REVIEW, _("Under Review")),
4019 (STATUS_UNDER_REVIEW, _("Under Review")),
4020 ]
4020 ]
4021
4021
4022 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
4022 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
4023 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
4023 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
4024 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
4024 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
4025 revision = Column('revision', String(40), nullable=False)
4025 revision = Column('revision', String(40), nullable=False)
4026 status = Column('status', String(128), nullable=False, default=DEFAULT)
4026 status = Column('status', String(128), nullable=False, default=DEFAULT)
4027 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
4027 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
4028 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
4028 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
4029 version = Column('version', Integer(), nullable=False, default=0)
4029 version = Column('version', Integer(), nullable=False, default=0)
4030 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
4030 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
4031
4031
4032 author = relationship('User', lazy='select')
4032 author = relationship('User', lazy='select')
4033 repo = relationship('Repository', lazy='select')
4033 repo = relationship('Repository', lazy='select')
4034 comment = relationship('ChangesetComment', lazy='select', back_populates='status_change')
4034 comment = relationship('ChangesetComment', lazy='select', back_populates='status_change')
4035 pull_request = relationship('PullRequest', lazy='select', back_populates='statuses')
4035 pull_request = relationship('PullRequest', lazy='select', back_populates='statuses')
4036
4036
4037 def __repr__(self):
4037 def __repr__(self):
4038 return f"<{self.cls_name}('{self.status}[v{self.version}]:{self.author}')>"
4038 return f"<{self.cls_name}('{self.status}[v{self.version}]:{self.author}')>"
4039
4039
4040 @classmethod
4040 @classmethod
4041 def get_status_lbl(cls, value):
4041 def get_status_lbl(cls, value):
4042 return dict(cls.STATUSES).get(value)
4042 return dict(cls.STATUSES).get(value)
4043
4043
4044 @property
4044 @property
4045 def status_lbl(self):
4045 def status_lbl(self):
4046 return ChangesetStatus.get_status_lbl(self.status)
4046 return ChangesetStatus.get_status_lbl(self.status)
4047
4047
4048 def get_api_data(self):
4048 def get_api_data(self):
4049 status = self
4049 status = self
4050 data = {
4050 data = {
4051 'status_id': status.changeset_status_id,
4051 'status_id': status.changeset_status_id,
4052 'status': status.status,
4052 'status': status.status,
4053 }
4053 }
4054 return data
4054 return data
4055
4055
4056 def __json__(self):
4056 def __json__(self):
4057 data = dict()
4057 data = dict()
4058 data.update(self.get_api_data())
4058 data.update(self.get_api_data())
4059 return data
4059 return data
4060
4060
4061
4061
4062 class _SetState(object):
4062 class _SetState(object):
4063 """
4063 """
4064 Context processor allowing changing state for sensitive operation such as
4064 Context processor allowing changing state for sensitive operation such as
4065 pull request update or merge
4065 pull request update or merge
4066 """
4066 """
4067
4067
4068 def __init__(self, pull_request, pr_state, back_state=None):
4068 def __init__(self, pull_request, pr_state, back_state=None):
4069 self._pr = pull_request
4069 self._pr = pull_request
4070 self._org_state = back_state or pull_request.pull_request_state
4070 self._org_state = back_state or pull_request.pull_request_state
4071 self._pr_state = pr_state
4071 self._pr_state = pr_state
4072 self._current_state = None
4072 self._current_state = None
4073
4073
4074 def __enter__(self):
4074 def __enter__(self):
4075 log.debug('StateLock: entering set state context of pr %s, setting state to: `%s`',
4075 log.debug('StateLock: entering set state context of pr %s, setting state to: `%s`',
4076 self._pr, self._pr_state)
4076 self._pr, self._pr_state)
4077 self.set_pr_state(self._pr_state)
4077 self.set_pr_state(self._pr_state)
4078 return self
4078 return self
4079
4079
4080 def __exit__(self, exc_type, exc_val, exc_tb):
4080 def __exit__(self, exc_type, exc_val, exc_tb):
4081 if exc_val is not None or exc_type is not None:
4081 if exc_val is not None or exc_type is not None:
4082 log.error(traceback.format_tb(exc_tb))
4082 log.error(traceback.format_tb(exc_tb))
4083 return None
4083 return None
4084
4084
4085 self.set_pr_state(self._org_state)
4085 self.set_pr_state(self._org_state)
4086 log.debug('StateLock: exiting set state context of pr %s, setting state to: `%s`',
4086 log.debug('StateLock: exiting set state context of pr %s, setting state to: `%s`',
4087 self._pr, self._org_state)
4087 self._pr, self._org_state)
4088
4088
4089 @property
4089 @property
4090 def state(self):
4090 def state(self):
4091 return self._current_state
4091 return self._current_state
4092
4092
4093 def set_pr_state(self, pr_state):
4093 def set_pr_state(self, pr_state):
4094 try:
4094 try:
4095 self._pr.pull_request_state = pr_state
4095 self._pr.pull_request_state = pr_state
4096 Session().add(self._pr)
4096 Session().add(self._pr)
4097 Session().commit()
4097 Session().commit()
4098 self._current_state = pr_state
4098 self._current_state = pr_state
4099 except Exception:
4099 except Exception:
4100 log.exception('Failed to set PullRequest %s state to %s', self._pr, pr_state)
4100 log.exception('Failed to set PullRequest %s state to %s', self._pr, pr_state)
4101 raise
4101 raise
4102
4102
4103
4103
4104 class _PullRequestBase(BaseModel):
4104 class _PullRequestBase(BaseModel):
4105 """
4105 """
4106 Common attributes of pull request and version entries.
4106 Common attributes of pull request and version entries.
4107 """
4107 """
4108
4108
4109 # .status values
4109 # .status values
4110 STATUS_NEW = 'new'
4110 STATUS_NEW = 'new'
4111 STATUS_OPEN = 'open'
4111 STATUS_OPEN = 'open'
4112 STATUS_CLOSED = 'closed'
4112 STATUS_CLOSED = 'closed'
4113
4113
4114 # available states
4114 # available states
4115 STATE_CREATING = 'creating'
4115 STATE_CREATING = 'creating'
4116 STATE_UPDATING = 'updating'
4116 STATE_UPDATING = 'updating'
4117 STATE_MERGING = 'merging'
4117 STATE_MERGING = 'merging'
4118 STATE_CREATED = 'created'
4118 STATE_CREATED = 'created'
4119
4119
4120 title = Column('title', Unicode(255), nullable=True)
4120 title = Column('title', Unicode(255), nullable=True)
4121 description = Column(
4121 description = Column(
4122 'description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'),
4122 'description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'),
4123 nullable=True)
4123 nullable=True)
4124 description_renderer = Column('description_renderer', Unicode(64), nullable=True)
4124 description_renderer = Column('description_renderer', Unicode(64), nullable=True)
4125
4125
4126 # new/open/closed status of pull request (not approve/reject/etc)
4126 # new/open/closed status of pull request (not approve/reject/etc)
4127 status = Column('status', Unicode(255), nullable=False, default=STATUS_NEW)
4127 status = Column('status', Unicode(255), nullable=False, default=STATUS_NEW)
4128 created_on = Column(
4128 created_on = Column(
4129 'created_on', DateTime(timezone=False), nullable=False,
4129 'created_on', DateTime(timezone=False), nullable=False,
4130 default=datetime.datetime.now)
4130 default=datetime.datetime.now)
4131 updated_on = Column(
4131 updated_on = Column(
4132 'updated_on', DateTime(timezone=False), nullable=False,
4132 'updated_on', DateTime(timezone=False), nullable=False,
4133 default=datetime.datetime.now)
4133 default=datetime.datetime.now)
4134
4134
4135 pull_request_state = Column("pull_request_state", String(255), nullable=True)
4135 pull_request_state = Column("pull_request_state", String(255), nullable=True)
4136
4136
4137 @declared_attr
4137 @declared_attr
4138 def user_id(cls):
4138 def user_id(cls):
4139 return Column(
4139 return Column(
4140 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
4140 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
4141 unique=None)
4141 unique=None)
4142
4142
4143 # 500 revisions max
4143 # 500 revisions max
4144 _revisions = Column(
4144 _revisions = Column(
4145 'revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
4145 'revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
4146
4146
4147 common_ancestor_id = Column('common_ancestor_id', Unicode(255), nullable=True)
4147 common_ancestor_id = Column('common_ancestor_id', Unicode(255), nullable=True)
4148
4148
4149 @declared_attr
4149 @declared_attr
4150 def source_repo_id(cls):
4150 def source_repo_id(cls):
4151 # TODO: dan: rename column to source_repo_id
4151 # TODO: dan: rename column to source_repo_id
4152 return Column(
4152 return Column(
4153 'org_repo_id', Integer(), ForeignKey('repositories.repo_id'),
4153 'org_repo_id', Integer(), ForeignKey('repositories.repo_id'),
4154 nullable=False)
4154 nullable=False)
4155
4155
4156 @declared_attr
4156 @declared_attr
4157 def pr_source(cls):
4157 def pr_source(cls):
4158 return relationship(
4158 return relationship(
4159 'Repository',
4159 'Repository',
4160 primaryjoin=f'{cls.__name__}.source_repo_id==Repository.repo_id',
4160 primaryjoin=f'{cls.__name__}.source_repo_id==Repository.repo_id',
4161 overlaps="pull_requests_source"
4161 overlaps="pull_requests_source"
4162 )
4162 )
4163
4163
4164 _source_ref = Column('org_ref', Unicode(255), nullable=False)
4164 _source_ref = Column('org_ref', Unicode(255), nullable=False)
4165
4165
4166 @hybrid_property
4166 @hybrid_property
4167 def source_ref(self):
4167 def source_ref(self):
4168 return self._source_ref
4168 return self._source_ref
4169
4169
4170 @source_ref.setter
4170 @source_ref.setter
4171 def source_ref(self, val):
4171 def source_ref(self, val):
4172 parts = (val or '').split(':')
4172 parts = (val or '').split(':')
4173 if len(parts) != 3:
4173 if len(parts) != 3:
4174 raise ValueError(
4174 raise ValueError(
4175 'Invalid reference format given: {}, expected X:Y:Z'.format(val))
4175 'Invalid reference format given: {}, expected X:Y:Z'.format(val))
4176 self._source_ref = safe_str(val)
4176 self._source_ref = safe_str(val)
4177
4177
4178 _target_ref = Column('other_ref', Unicode(255), nullable=False)
4178 _target_ref = Column('other_ref', Unicode(255), nullable=False)
4179
4179
4180 @hybrid_property
4180 @hybrid_property
4181 def target_ref(self):
4181 def target_ref(self):
4182 return self._target_ref
4182 return self._target_ref
4183
4183
4184 @target_ref.setter
4184 @target_ref.setter
4185 def target_ref(self, val):
4185 def target_ref(self, val):
4186 parts = (val or '').split(':')
4186 parts = (val or '').split(':')
4187 if len(parts) != 3:
4187 if len(parts) != 3:
4188 raise ValueError(
4188 raise ValueError(
4189 'Invalid reference format given: {}, expected X:Y:Z'.format(val))
4189 'Invalid reference format given: {}, expected X:Y:Z'.format(val))
4190 self._target_ref = safe_str(val)
4190 self._target_ref = safe_str(val)
4191
4191
4192 @declared_attr
4192 @declared_attr
4193 def target_repo_id(cls):
4193 def target_repo_id(cls):
4194 # TODO: dan: rename column to target_repo_id
4194 # TODO: dan: rename column to target_repo_id
4195 return Column(
4195 return Column(
4196 'other_repo_id', Integer(), ForeignKey('repositories.repo_id'),
4196 'other_repo_id', Integer(), ForeignKey('repositories.repo_id'),
4197 nullable=False)
4197 nullable=False)
4198
4198
4199 @declared_attr
4199 @declared_attr
4200 def pr_target(cls):
4200 def pr_target(cls):
4201 return relationship(
4201 return relationship(
4202 'Repository',
4202 'Repository',
4203 primaryjoin=f'{cls.__name__}.target_repo_id==Repository.repo_id',
4203 primaryjoin=f'{cls.__name__}.target_repo_id==Repository.repo_id',
4204 overlaps="pull_requests_target"
4204 overlaps="pull_requests_target"
4205 )
4205 )
4206
4206
4207 _shadow_merge_ref = Column('shadow_merge_ref', Unicode(255), nullable=True)
4207 _shadow_merge_ref = Column('shadow_merge_ref', Unicode(255), nullable=True)
4208
4208
4209 # TODO: dan: rename column to last_merge_source_rev
4209 # TODO: dan: rename column to last_merge_source_rev
4210 _last_merge_source_rev = Column(
4210 _last_merge_source_rev = Column(
4211 'last_merge_org_rev', String(40), nullable=True)
4211 'last_merge_org_rev', String(40), nullable=True)
4212 # TODO: dan: rename column to last_merge_target_rev
4212 # TODO: dan: rename column to last_merge_target_rev
4213 _last_merge_target_rev = Column(
4213 _last_merge_target_rev = Column(
4214 'last_merge_other_rev', String(40), nullable=True)
4214 'last_merge_other_rev', String(40), nullable=True)
4215 _last_merge_status = Column('merge_status', Integer(), nullable=True)
4215 _last_merge_status = Column('merge_status', Integer(), nullable=True)
4216 last_merge_metadata = Column(
4216 last_merge_metadata = Column(
4217 'last_merge_metadata', MutationObj.as_mutable(
4217 'last_merge_metadata', MutationObj.as_mutable(
4218 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
4218 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
4219
4219
4220 merge_rev = Column('merge_rev', String(40), nullable=True)
4220 merge_rev = Column('merge_rev', String(40), nullable=True)
4221
4221
4222 reviewer_data = Column(
4222 reviewer_data = Column(
4223 'reviewer_data_json', MutationObj.as_mutable(
4223 'reviewer_data_json', MutationObj.as_mutable(
4224 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
4224 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
4225
4225
4226 @property
4226 @property
4227 def reviewer_data_json(self):
4227 def reviewer_data_json(self):
4228 return json.dumps(self.reviewer_data)
4228 return json.dumps(self.reviewer_data)
4229
4229
4230 @property
4230 @property
4231 def last_merge_metadata_parsed(self):
4231 def last_merge_metadata_parsed(self):
4232 metadata = {}
4232 metadata = {}
4233 if not self.last_merge_metadata:
4233 if not self.last_merge_metadata:
4234 return metadata
4234 return metadata
4235
4235
4236 if hasattr(self.last_merge_metadata, 'de_coerce'):
4236 if hasattr(self.last_merge_metadata, 'de_coerce'):
4237 for k, v in self.last_merge_metadata.de_coerce().items():
4237 for k, v in self.last_merge_metadata.de_coerce().items():
4238 if k in ['target_ref', 'source_ref']:
4238 if k in ['target_ref', 'source_ref']:
4239 metadata[k] = Reference(v['type'], v['name'], v['commit_id'])
4239 metadata[k] = Reference(v['type'], v['name'], v['commit_id'])
4240 else:
4240 else:
4241 if hasattr(v, 'de_coerce'):
4241 if hasattr(v, 'de_coerce'):
4242 metadata[k] = v.de_coerce()
4242 metadata[k] = v.de_coerce()
4243 else:
4243 else:
4244 metadata[k] = v
4244 metadata[k] = v
4245 return metadata
4245 return metadata
4246
4246
4247 @property
4247 @property
4248 def work_in_progress(self):
4248 def work_in_progress(self):
4249 """checks if pull request is work in progress by checking the title"""
4249 """checks if pull request is work in progress by checking the title"""
4250 title = self.title.upper()
4250 title = self.title.upper()
4251 if re.match(r'^(\[WIP\]\s*|WIP:\s*|WIP\s+)', title):
4251 if re.match(r'^(\[WIP\]\s*|WIP:\s*|WIP\s+)', title):
4252 return True
4252 return True
4253 return False
4253 return False
4254
4254
4255 @property
4255 @property
4256 def title_safe(self):
4256 def title_safe(self):
4257 return self.title\
4257 return self.title\
4258 .replace('{', '{{')\
4258 .replace('{', '{{')\
4259 .replace('}', '}}')
4259 .replace('}', '}}')
4260
4260
4261 @hybrid_property
4261 @hybrid_property
4262 def description_safe(self):
4262 def description_safe(self):
4263 from rhodecode.lib import helpers as h
4263 from rhodecode.lib import helpers as h
4264 return h.escape(self.description)
4264 return h.escape(self.description)
4265
4265
4266 @hybrid_property
4266 @hybrid_property
4267 def revisions(self):
4267 def revisions(self):
4268 return self._revisions.split(':') if self._revisions else []
4268 return self._revisions.split(':') if self._revisions else []
4269
4269
4270 @revisions.setter
4270 @revisions.setter
4271 def revisions(self, val):
4271 def revisions(self, val):
4272 self._revisions = ':'.join(val)
4272 self._revisions = ':'.join(val)
4273
4273
4274 @hybrid_property
4274 @hybrid_property
4275 def last_merge_status(self):
4275 def last_merge_status(self):
4276 return safe_int(self._last_merge_status)
4276 return safe_int(self._last_merge_status)
4277
4277
4278 @last_merge_status.setter
4278 @last_merge_status.setter
4279 def last_merge_status(self, val):
4279 def last_merge_status(self, val):
4280 self._last_merge_status = val
4280 self._last_merge_status = val
4281
4281
4282 @declared_attr
4282 @declared_attr
4283 def author(cls):
4283 def author(cls):
4284 return relationship(
4284 return relationship(
4285 'User', lazy='joined',
4285 'User', lazy='joined',
4286 #TODO, problem that is somehow :?
4286 #TODO, problem that is somehow :?
4287 #back_populates='user_pull_requests'
4287 #back_populates='user_pull_requests'
4288 )
4288 )
4289
4289
4290 @declared_attr
4290 @declared_attr
4291 def source_repo(cls):
4291 def source_repo(cls):
4292 return relationship(
4292 return relationship(
4293 'Repository',
4293 'Repository',
4294 primaryjoin=f'{cls.__name__}.source_repo_id==Repository.repo_id',
4294 primaryjoin=f'{cls.__name__}.source_repo_id==Repository.repo_id',
4295 #back_populates=''
4295 #back_populates=''
4296 )
4296 )
4297
4297
4298 @property
4298 @property
4299 def source_ref_parts(self):
4299 def source_ref_parts(self):
4300 return self.unicode_to_reference(self.source_ref)
4300 return self.unicode_to_reference(self.source_ref)
4301
4301
4302 @declared_attr
4302 @declared_attr
4303 def target_repo(cls):
4303 def target_repo(cls):
4304 return relationship(
4304 return relationship(
4305 'Repository',
4305 'Repository',
4306 primaryjoin=f'{cls.__name__}.target_repo_id==Repository.repo_id'
4306 primaryjoin=f'{cls.__name__}.target_repo_id==Repository.repo_id'
4307 )
4307 )
4308
4308
4309 @property
4309 @property
4310 def target_ref_parts(self):
4310 def target_ref_parts(self):
4311 return self.unicode_to_reference(self.target_ref)
4311 return self.unicode_to_reference(self.target_ref)
4312
4312
4313 @property
4313 @property
4314 def shadow_merge_ref(self):
4314 def shadow_merge_ref(self):
4315 return self.unicode_to_reference(self._shadow_merge_ref)
4315 return self.unicode_to_reference(self._shadow_merge_ref)
4316
4316
4317 @shadow_merge_ref.setter
4317 @shadow_merge_ref.setter
4318 def shadow_merge_ref(self, ref):
4318 def shadow_merge_ref(self, ref):
4319 self._shadow_merge_ref = self.reference_to_unicode(ref)
4319 self._shadow_merge_ref = self.reference_to_unicode(ref)
4320
4320
4321 @staticmethod
4321 @staticmethod
4322 def unicode_to_reference(raw):
4322 def unicode_to_reference(raw):
4323 return unicode_to_reference(raw)
4323 return unicode_to_reference(raw)
4324
4324
4325 @staticmethod
4325 @staticmethod
4326 def reference_to_unicode(ref):
4326 def reference_to_unicode(ref):
4327 return reference_to_unicode(ref)
4327 return reference_to_unicode(ref)
4328
4328
4329 def get_api_data(self, with_merge_state=True):
4329 def get_api_data(self, with_merge_state=True):
4330 from rhodecode.model.pull_request import PullRequestModel
4330 from rhodecode.model.pull_request import PullRequestModel
4331
4331
4332 pull_request = self
4332 pull_request = self
4333 if with_merge_state:
4333 if with_merge_state:
4334 merge_response, merge_status, msg = \
4334 merge_response, merge_status, msg = \
4335 PullRequestModel().merge_status(pull_request)
4335 PullRequestModel().merge_status(pull_request)
4336 merge_state = {
4336 merge_state = {
4337 'status': merge_status,
4337 'status': merge_status,
4338 'message': safe_str(msg),
4338 'message': safe_str(msg),
4339 }
4339 }
4340 else:
4340 else:
4341 merge_state = {'status': 'not_available',
4341 merge_state = {'status': 'not_available',
4342 'message': 'not_available'}
4342 'message': 'not_available'}
4343
4343
4344 merge_data = {
4344 merge_data = {
4345 'clone_url': PullRequestModel().get_shadow_clone_url(pull_request),
4345 'clone_url': PullRequestModel().get_shadow_clone_url(pull_request),
4346 'reference': (
4346 'reference': (
4347 pull_request.shadow_merge_ref.asdict()
4347 pull_request.shadow_merge_ref.asdict()
4348 if pull_request.shadow_merge_ref else None),
4348 if pull_request.shadow_merge_ref else None),
4349 }
4349 }
4350
4350
4351 data = {
4351 data = {
4352 'pull_request_id': pull_request.pull_request_id,
4352 'pull_request_id': pull_request.pull_request_id,
4353 'url': PullRequestModel().get_url(pull_request),
4353 'url': PullRequestModel().get_url(pull_request),
4354 'title': pull_request.title,
4354 'title': pull_request.title,
4355 'description': pull_request.description,
4355 'description': pull_request.description,
4356 'status': pull_request.status,
4356 'status': pull_request.status,
4357 'state': pull_request.pull_request_state,
4357 'state': pull_request.pull_request_state,
4358 'created_on': pull_request.created_on,
4358 'created_on': pull_request.created_on,
4359 'updated_on': pull_request.updated_on,
4359 'updated_on': pull_request.updated_on,
4360 'commit_ids': pull_request.revisions,
4360 'commit_ids': pull_request.revisions,
4361 'review_status': pull_request.calculated_review_status(),
4361 'review_status': pull_request.calculated_review_status(),
4362 'mergeable': merge_state,
4362 'mergeable': merge_state,
4363 'source': {
4363 'source': {
4364 'clone_url': pull_request.source_repo.clone_url(),
4364 'clone_url': pull_request.source_repo.clone_url(),
4365 'repository': pull_request.source_repo.repo_name,
4365 'repository': pull_request.source_repo.repo_name,
4366 'reference': {
4366 'reference': {
4367 'name': pull_request.source_ref_parts.name,
4367 'name': pull_request.source_ref_parts.name,
4368 'type': pull_request.source_ref_parts.type,
4368 'type': pull_request.source_ref_parts.type,
4369 'commit_id': pull_request.source_ref_parts.commit_id,
4369 'commit_id': pull_request.source_ref_parts.commit_id,
4370 },
4370 },
4371 },
4371 },
4372 'target': {
4372 'target': {
4373 'clone_url': pull_request.target_repo.clone_url(),
4373 'clone_url': pull_request.target_repo.clone_url(),
4374 'repository': pull_request.target_repo.repo_name,
4374 'repository': pull_request.target_repo.repo_name,
4375 'reference': {
4375 'reference': {
4376 'name': pull_request.target_ref_parts.name,
4376 'name': pull_request.target_ref_parts.name,
4377 'type': pull_request.target_ref_parts.type,
4377 'type': pull_request.target_ref_parts.type,
4378 'commit_id': pull_request.target_ref_parts.commit_id,
4378 'commit_id': pull_request.target_ref_parts.commit_id,
4379 },
4379 },
4380 },
4380 },
4381 'merge': merge_data,
4381 'merge': merge_data,
4382 'author': pull_request.author.get_api_data(include_secrets=False,
4382 'author': pull_request.author.get_api_data(include_secrets=False,
4383 details='basic'),
4383 details='basic'),
4384 'reviewers': [
4384 'reviewers': [
4385 {
4385 {
4386 'user': reviewer.get_api_data(include_secrets=False,
4386 'user': reviewer.get_api_data(include_secrets=False,
4387 details='basic'),
4387 details='basic'),
4388 'reasons': reasons,
4388 'reasons': reasons,
4389 'review_status': st[0][1].status if st else 'not_reviewed',
4389 'review_status': st[0][1].status if st else 'not_reviewed',
4390 }
4390 }
4391 for obj, reviewer, reasons, mandatory, st in
4391 for obj, reviewer, reasons, mandatory, st in
4392 pull_request.reviewers_statuses()
4392 pull_request.reviewers_statuses()
4393 ]
4393 ]
4394 }
4394 }
4395
4395
4396 return data
4396 return data
4397
4397
4398 def set_state(self, pull_request_state, final_state=None):
4398 def set_state(self, pull_request_state, final_state=None):
4399 """
4399 """
4400 # goes from initial state to updating to initial state.
4400 # goes from initial state to updating to initial state.
4401 # initial state can be changed by specifying back_state=
4401 # initial state can be changed by specifying back_state=
4402 with pull_request_obj.set_state(PullRequest.STATE_UPDATING):
4402 with pull_request_obj.set_state(PullRequest.STATE_UPDATING):
4403 pull_request.merge()
4403 pull_request.merge()
4404
4404
4405 :param pull_request_state:
4405 :param pull_request_state:
4406 :param final_state:
4406 :param final_state:
4407
4407
4408 """
4408 """
4409
4409
4410 return _SetState(self, pull_request_state, back_state=final_state)
4410 return _SetState(self, pull_request_state, back_state=final_state)
4411
4411
4412
4412
4413 class PullRequest(Base, _PullRequestBase):
4413 class PullRequest(Base, _PullRequestBase):
4414 __tablename__ = 'pull_requests'
4414 __tablename__ = 'pull_requests'
4415 __table_args__ = (
4415 __table_args__ = (
4416 base_table_args,
4416 base_table_args,
4417 )
4417 )
4418 LATEST_VER = 'latest'
4418 LATEST_VER = 'latest'
4419
4419
4420 pull_request_id = Column(
4420 pull_request_id = Column(
4421 'pull_request_id', Integer(), nullable=False, primary_key=True)
4421 'pull_request_id', Integer(), nullable=False, primary_key=True)
4422
4422
4423 def __repr__(self):
4423 def __repr__(self):
4424 if self.pull_request_id:
4424 if self.pull_request_id:
4425 return f'<DB:PullRequest #{self.pull_request_id}>'
4425 return f'<DB:PullRequest #{self.pull_request_id}>'
4426 else:
4426 else:
4427 return f'<DB:PullRequest at {id(self)!r}>'
4427 return f'<DB:PullRequest at {id(self)!r}>'
4428
4428
4429 reviewers = relationship('PullRequestReviewers', cascade="all, delete-orphan", back_populates='pull_request')
4429 reviewers = relationship('PullRequestReviewers', cascade="all, delete-orphan", back_populates='pull_request')
4430 statuses = relationship('ChangesetStatus', cascade="all, delete-orphan", back_populates='pull_request')
4430 statuses = relationship('ChangesetStatus', cascade="all, delete-orphan", back_populates='pull_request')
4431 comments = relationship('ChangesetComment', cascade="all, delete-orphan", back_populates='pull_request')
4431 comments = relationship('ChangesetComment', cascade="all, delete-orphan", back_populates='pull_request')
4432 versions = relationship('PullRequestVersion', cascade="all, delete-orphan", lazy='dynamic', back_populates='pull_request')
4432 versions = relationship('PullRequestVersion', cascade="all, delete-orphan", lazy='dynamic', back_populates='pull_request')
4433
4433
4434 @classmethod
4434 @classmethod
4435 def get_pr_display_object(cls, pull_request_obj, org_pull_request_obj,
4435 def get_pr_display_object(cls, pull_request_obj, org_pull_request_obj,
4436 internal_methods=None):
4436 internal_methods=None):
4437
4437
4438 class PullRequestDisplay(object):
4438 class PullRequestDisplay(object):
4439 """
4439 """
4440 Special object wrapper for showing PullRequest data via Versions
4440 Special object wrapper for showing PullRequest data via Versions
4441 It mimics PR object as close as possible. This is read only object
4441 It mimics PR object as close as possible. This is read only object
4442 just for display
4442 just for display
4443 """
4443 """
4444
4444
4445 def __init__(self, attrs, internal=None):
4445 def __init__(self, attrs, internal=None):
4446 self.attrs = attrs
4446 self.attrs = attrs
4447 # internal have priority over the given ones via attrs
4447 # internal have priority over the given ones via attrs
4448 self.internal = internal or ['versions']
4448 self.internal = internal or ['versions']
4449
4449
4450 def __getattr__(self, item):
4450 def __getattr__(self, item):
4451 if item in self.internal:
4451 if item in self.internal:
4452 return getattr(self, item)
4452 return getattr(self, item)
4453 try:
4453 try:
4454 return self.attrs[item]
4454 return self.attrs[item]
4455 except KeyError:
4455 except KeyError:
4456 raise AttributeError(
4456 raise AttributeError(
4457 '%s object has no attribute %s' % (self, item))
4457 '%s object has no attribute %s' % (self, item))
4458
4458
4459 def __repr__(self):
4459 def __repr__(self):
4460 pr_id = self.attrs.get('pull_request_id')
4460 pr_id = self.attrs.get('pull_request_id')
4461 return f'<DB:PullRequestDisplay #{pr_id}>'
4461 return f'<DB:PullRequestDisplay #{pr_id}>'
4462
4462
4463 def versions(self):
4463 def versions(self):
4464 return pull_request_obj.versions.order_by(
4464 return pull_request_obj.versions.order_by(
4465 PullRequestVersion.pull_request_version_id).all()
4465 PullRequestVersion.pull_request_version_id).all()
4466
4466
4467 def is_closed(self):
4467 def is_closed(self):
4468 return pull_request_obj.is_closed()
4468 return pull_request_obj.is_closed()
4469
4469
4470 def is_state_changing(self):
4470 def is_state_changing(self):
4471 return pull_request_obj.is_state_changing()
4471 return pull_request_obj.is_state_changing()
4472
4472
4473 @property
4473 @property
4474 def pull_request_version_id(self):
4474 def pull_request_version_id(self):
4475 return getattr(pull_request_obj, 'pull_request_version_id', None)
4475 return getattr(pull_request_obj, 'pull_request_version_id', None)
4476
4476
4477 @property
4477 @property
4478 def pull_request_last_version(self):
4478 def pull_request_last_version(self):
4479 return pull_request_obj.pull_request_last_version
4479 return pull_request_obj.pull_request_last_version
4480
4480
4481 attrs = StrictAttributeDict(pull_request_obj.get_api_data(with_merge_state=False))
4481 attrs = StrictAttributeDict(pull_request_obj.get_api_data(with_merge_state=False))
4482
4482
4483 attrs.author = StrictAttributeDict(
4483 attrs.author = StrictAttributeDict(
4484 pull_request_obj.author.get_api_data())
4484 pull_request_obj.author.get_api_data())
4485 if pull_request_obj.target_repo:
4485 if pull_request_obj.target_repo:
4486 attrs.target_repo = StrictAttributeDict(
4486 attrs.target_repo = StrictAttributeDict(
4487 pull_request_obj.target_repo.get_api_data())
4487 pull_request_obj.target_repo.get_api_data())
4488 attrs.target_repo.clone_url = pull_request_obj.target_repo.clone_url
4488 attrs.target_repo.clone_url = pull_request_obj.target_repo.clone_url
4489
4489
4490 if pull_request_obj.source_repo:
4490 if pull_request_obj.source_repo:
4491 attrs.source_repo = StrictAttributeDict(
4491 attrs.source_repo = StrictAttributeDict(
4492 pull_request_obj.source_repo.get_api_data())
4492 pull_request_obj.source_repo.get_api_data())
4493 attrs.source_repo.clone_url = pull_request_obj.source_repo.clone_url
4493 attrs.source_repo.clone_url = pull_request_obj.source_repo.clone_url
4494
4494
4495 attrs.source_ref_parts = pull_request_obj.source_ref_parts
4495 attrs.source_ref_parts = pull_request_obj.source_ref_parts
4496 attrs.target_ref_parts = pull_request_obj.target_ref_parts
4496 attrs.target_ref_parts = pull_request_obj.target_ref_parts
4497 attrs.revisions = pull_request_obj.revisions
4497 attrs.revisions = pull_request_obj.revisions
4498 attrs.common_ancestor_id = pull_request_obj.common_ancestor_id
4498 attrs.common_ancestor_id = pull_request_obj.common_ancestor_id
4499 attrs.shadow_merge_ref = org_pull_request_obj.shadow_merge_ref
4499 attrs.shadow_merge_ref = org_pull_request_obj.shadow_merge_ref
4500 attrs.reviewer_data = org_pull_request_obj.reviewer_data
4500 attrs.reviewer_data = org_pull_request_obj.reviewer_data
4501 attrs.reviewer_data_json = org_pull_request_obj.reviewer_data_json
4501 attrs.reviewer_data_json = org_pull_request_obj.reviewer_data_json
4502
4502
4503 return PullRequestDisplay(attrs, internal=internal_methods)
4503 return PullRequestDisplay(attrs, internal=internal_methods)
4504
4504
4505 def is_closed(self):
4505 def is_closed(self):
4506 return self.status == self.STATUS_CLOSED
4506 return self.status == self.STATUS_CLOSED
4507
4507
4508 def is_state_changing(self):
4508 def is_state_changing(self):
4509 return self.pull_request_state != PullRequest.STATE_CREATED
4509 return self.pull_request_state != PullRequest.STATE_CREATED
4510
4510
4511 def __json__(self):
4511 def __json__(self):
4512 return {
4512 return {
4513 'revisions': self.revisions,
4513 'revisions': self.revisions,
4514 'versions': self.versions_count
4514 'versions': self.versions_count
4515 }
4515 }
4516
4516
4517 def calculated_review_status(self):
4517 def calculated_review_status(self):
4518 from rhodecode.model.changeset_status import ChangesetStatusModel
4518 from rhodecode.model.changeset_status import ChangesetStatusModel
4519 return ChangesetStatusModel().calculated_review_status(self)
4519 return ChangesetStatusModel().calculated_review_status(self)
4520
4520
4521 def reviewers_statuses(self, user=None):
4521 def reviewers_statuses(self, user=None):
4522 from rhodecode.model.changeset_status import ChangesetStatusModel
4522 from rhodecode.model.changeset_status import ChangesetStatusModel
4523 return ChangesetStatusModel().reviewers_statuses(self, user=user)
4523 return ChangesetStatusModel().reviewers_statuses(self, user=user)
4524
4524
4525 def get_pull_request_reviewers(self, role=None):
4525 def get_pull_request_reviewers(self, role=None):
4526 qry = PullRequestReviewers.query()\
4526 qry = PullRequestReviewers.query()\
4527 .filter(PullRequestReviewers.pull_request_id == self.pull_request_id)
4527 .filter(PullRequestReviewers.pull_request_id == self.pull_request_id)
4528 if role:
4528 if role:
4529 qry = qry.filter(PullRequestReviewers.role == role)
4529 qry = qry.filter(PullRequestReviewers.role == role)
4530
4530
4531 return qry.all()
4531 return qry.all()
4532
4532
4533 @property
4533 @property
4534 def reviewers_count(self):
4534 def reviewers_count(self):
4535 qry = PullRequestReviewers.query()\
4535 qry = PullRequestReviewers.query()\
4536 .filter(PullRequestReviewers.pull_request_id == self.pull_request_id)\
4536 .filter(PullRequestReviewers.pull_request_id == self.pull_request_id)\
4537 .filter(PullRequestReviewers.role == PullRequestReviewers.ROLE_REVIEWER)
4537 .filter(PullRequestReviewers.role == PullRequestReviewers.ROLE_REVIEWER)
4538 return qry.count()
4538 return qry.count()
4539
4539
4540 @property
4540 @property
4541 def observers_count(self):
4541 def observers_count(self):
4542 qry = PullRequestReviewers.query()\
4542 qry = PullRequestReviewers.query()\
4543 .filter(PullRequestReviewers.pull_request_id == self.pull_request_id)\
4543 .filter(PullRequestReviewers.pull_request_id == self.pull_request_id)\
4544 .filter(PullRequestReviewers.role == PullRequestReviewers.ROLE_OBSERVER)
4544 .filter(PullRequestReviewers.role == PullRequestReviewers.ROLE_OBSERVER)
4545 return qry.count()
4545 return qry.count()
4546
4546
4547 def observers(self):
4547 def observers(self):
4548 qry = PullRequestReviewers.query()\
4548 qry = PullRequestReviewers.query()\
4549 .filter(PullRequestReviewers.pull_request_id == self.pull_request_id)\
4549 .filter(PullRequestReviewers.pull_request_id == self.pull_request_id)\
4550 .filter(PullRequestReviewers.role == PullRequestReviewers.ROLE_OBSERVER)\
4550 .filter(PullRequestReviewers.role == PullRequestReviewers.ROLE_OBSERVER)\
4551 .all()
4551 .all()
4552
4552
4553 for entry in qry:
4553 for entry in qry:
4554 yield entry, entry.user
4554 yield entry, entry.user
4555
4555
4556 @property
4556 @property
4557 def workspace_id(self):
4557 def workspace_id(self):
4558 from rhodecode.model.pull_request import PullRequestModel
4558 from rhodecode.model.pull_request import PullRequestModel
4559 return PullRequestModel()._workspace_id(self)
4559 return PullRequestModel()._workspace_id(self)
4560
4560
4561 def get_shadow_repo(self):
4561 def get_shadow_repo(self):
4562 workspace_id = self.workspace_id
4562 workspace_id = self.workspace_id
4563 shadow_repository_path = self.target_repo.get_shadow_repository_path(workspace_id)
4563 shadow_repository_path = self.target_repo.get_shadow_repository_path(workspace_id)
4564 if os.path.isdir(shadow_repository_path):
4564 if os.path.isdir(shadow_repository_path):
4565 vcs_obj = self.target_repo.scm_instance()
4565 vcs_obj = self.target_repo.scm_instance()
4566 return vcs_obj.get_shadow_instance(shadow_repository_path)
4566 return vcs_obj.get_shadow_instance(shadow_repository_path)
4567
4567
4568 @property
4568 @property
4569 def versions_count(self):
4569 def versions_count(self):
4570 """
4570 """
4571 return number of versions this PR have, e.g a PR that once been
4571 return number of versions this PR have, e.g a PR that once been
4572 updated will have 2 versions
4572 updated will have 2 versions
4573 """
4573 """
4574 return self.versions.count() + 1
4574 return self.versions.count() + 1
4575
4575
4576 @property
4576 @property
4577 def pull_request_last_version(self):
4577 def pull_request_last_version(self):
4578 return self.versions_count
4578 return self.versions_count
4579
4579
4580
4580
4581 class PullRequestVersion(Base, _PullRequestBase):
4581 class PullRequestVersion(Base, _PullRequestBase):
4582 __tablename__ = 'pull_request_versions'
4582 __tablename__ = 'pull_request_versions'
4583 __table_args__ = (
4583 __table_args__ = (
4584 base_table_args,
4584 base_table_args,
4585 )
4585 )
4586
4586
4587 pull_request_version_id = Column('pull_request_version_id', Integer(), nullable=False, primary_key=True)
4587 pull_request_version_id = Column('pull_request_version_id', Integer(), nullable=False, primary_key=True)
4588 pull_request_id = Column('pull_request_id', Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=False)
4588 pull_request_id = Column('pull_request_id', Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=False)
4589 pull_request = relationship('PullRequest', back_populates='versions')
4589 pull_request = relationship('PullRequest', back_populates='versions')
4590
4590
4591 def __repr__(self):
4591 def __repr__(self):
4592 if self.pull_request_version_id:
4592 if self.pull_request_version_id:
4593 return f'<DB:PullRequestVersion #{self.pull_request_version_id}>'
4593 return f'<DB:PullRequestVersion #{self.pull_request_version_id}>'
4594 else:
4594 else:
4595 return f'<DB:PullRequestVersion at {id(self)!r}>'
4595 return f'<DB:PullRequestVersion at {id(self)!r}>'
4596
4596
4597 @property
4597 @property
4598 def reviewers(self):
4598 def reviewers(self):
4599 return self.pull_request.reviewers
4599 return self.pull_request.reviewers
4600
4600
4601 @property
4601 @property
4602 def versions(self):
4602 def versions(self):
4603 return self.pull_request.versions
4603 return self.pull_request.versions
4604
4604
4605 def is_closed(self):
4605 def is_closed(self):
4606 # calculate from original
4606 # calculate from original
4607 return self.pull_request.status == self.STATUS_CLOSED
4607 return self.pull_request.status == self.STATUS_CLOSED
4608
4608
4609 def is_state_changing(self):
4609 def is_state_changing(self):
4610 return self.pull_request.pull_request_state != PullRequest.STATE_CREATED
4610 return self.pull_request.pull_request_state != PullRequest.STATE_CREATED
4611
4611
4612 def calculated_review_status(self):
4612 def calculated_review_status(self):
4613 return self.pull_request.calculated_review_status()
4613 return self.pull_request.calculated_review_status()
4614
4614
4615 def reviewers_statuses(self):
4615 def reviewers_statuses(self):
4616 return self.pull_request.reviewers_statuses()
4616 return self.pull_request.reviewers_statuses()
4617
4617
4618 def observers(self):
4618 def observers(self):
4619 return self.pull_request.observers()
4619 return self.pull_request.observers()
4620
4620
4621
4621
4622 class PullRequestReviewers(Base, BaseModel):
4622 class PullRequestReviewers(Base, BaseModel):
4623 __tablename__ = 'pull_request_reviewers'
4623 __tablename__ = 'pull_request_reviewers'
4624 __table_args__ = (
4624 __table_args__ = (
4625 base_table_args,
4625 base_table_args,
4626 )
4626 )
4627 ROLE_REVIEWER = 'reviewer'
4627 ROLE_REVIEWER = 'reviewer'
4628 ROLE_OBSERVER = 'observer'
4628 ROLE_OBSERVER = 'observer'
4629 ROLES = [ROLE_REVIEWER, ROLE_OBSERVER]
4629 ROLES = [ROLE_REVIEWER, ROLE_OBSERVER]
4630
4630
4631 @hybrid_property
4631 @hybrid_property
4632 def reasons(self):
4632 def reasons(self):
4633 if not self._reasons:
4633 if not self._reasons:
4634 return []
4634 return []
4635 return self._reasons
4635 return self._reasons
4636
4636
4637 @reasons.setter
4637 @reasons.setter
4638 def reasons(self, val):
4638 def reasons(self, val):
4639 val = val or []
4639 val = val or []
4640 if any(not isinstance(x, str) for x in val):
4640 if any(not isinstance(x, str) for x in val):
4641 raise Exception('invalid reasons type, must be list of strings')
4641 raise Exception('invalid reasons type, must be list of strings')
4642 self._reasons = val
4642 self._reasons = val
4643
4643
4644 pull_requests_reviewers_id = Column(
4644 pull_requests_reviewers_id = Column(
4645 'pull_requests_reviewers_id', Integer(), nullable=False,
4645 'pull_requests_reviewers_id', Integer(), nullable=False,
4646 primary_key=True)
4646 primary_key=True)
4647 pull_request_id = Column(
4647 pull_request_id = Column(
4648 "pull_request_id", Integer(),
4648 "pull_request_id", Integer(),
4649 ForeignKey('pull_requests.pull_request_id'), nullable=False)
4649 ForeignKey('pull_requests.pull_request_id'), nullable=False)
4650 user_id = Column(
4650 user_id = Column(
4651 "user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
4651 "user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
4652 _reasons = Column(
4652 _reasons = Column(
4653 'reason', MutationList.as_mutable(
4653 'reason', MutationList.as_mutable(
4654 JsonType('list', dialect_map=dict(mysql=UnicodeText(16384)))))
4654 JsonType('list', dialect_map=dict(mysql=UnicodeText(16384)))))
4655
4655
4656 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4656 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4657 role = Column('role', Unicode(255), nullable=True, default=ROLE_REVIEWER)
4657 role = Column('role', Unicode(255), nullable=True, default=ROLE_REVIEWER)
4658
4658
4659 user = relationship('User')
4659 user = relationship('User')
4660 pull_request = relationship('PullRequest', back_populates='reviewers')
4660 pull_request = relationship('PullRequest', back_populates='reviewers')
4661
4661
4662 rule_data = Column(
4662 rule_data = Column(
4663 'rule_data_json',
4663 'rule_data_json',
4664 JsonType(dialect_map=dict(mysql=UnicodeText(16384))))
4664 JsonType(dialect_map=dict(mysql=UnicodeText(16384))))
4665
4665
4666 def rule_user_group_data(self):
4666 def rule_user_group_data(self):
4667 """
4667 """
4668 Returns the voting user group rule data for this reviewer
4668 Returns the voting user group rule data for this reviewer
4669 """
4669 """
4670
4670
4671 if self.rule_data and 'vote_rule' in self.rule_data:
4671 if self.rule_data and 'vote_rule' in self.rule_data:
4672 user_group_data = {}
4672 user_group_data = {}
4673 if 'rule_user_group_entry_id' in self.rule_data:
4673 if 'rule_user_group_entry_id' in self.rule_data:
4674 # means a group with voting rules !
4674 # means a group with voting rules !
4675 user_group_data['id'] = self.rule_data['rule_user_group_entry_id']
4675 user_group_data['id'] = self.rule_data['rule_user_group_entry_id']
4676 user_group_data['name'] = self.rule_data['rule_name']
4676 user_group_data['name'] = self.rule_data['rule_name']
4677 user_group_data['vote_rule'] = self.rule_data['vote_rule']
4677 user_group_data['vote_rule'] = self.rule_data['vote_rule']
4678
4678
4679 return user_group_data
4679 return user_group_data
4680
4680
4681 @classmethod
4681 @classmethod
4682 def get_pull_request_reviewers(cls, pull_request_id, role=None):
4682 def get_pull_request_reviewers(cls, pull_request_id, role=None):
4683 qry = PullRequestReviewers.query()\
4683 qry = PullRequestReviewers.query()\
4684 .filter(PullRequestReviewers.pull_request_id == pull_request_id)
4684 .filter(PullRequestReviewers.pull_request_id == pull_request_id)
4685 if role:
4685 if role:
4686 qry = qry.filter(PullRequestReviewers.role == role)
4686 qry = qry.filter(PullRequestReviewers.role == role)
4687
4687
4688 return qry.all()
4688 return qry.all()
4689
4689
4690 def __repr__(self):
4690 def __repr__(self):
4691 return f"<{self.cls_name}('id:{self.pull_requests_reviewers_id}')>"
4691 return f"<{self.cls_name}('id:{self.pull_requests_reviewers_id}')>"
4692
4692
4693
4693
4694 class Notification(Base, BaseModel):
4694 class Notification(Base, BaseModel):
4695 __tablename__ = 'notifications'
4695 __tablename__ = 'notifications'
4696 __table_args__ = (
4696 __table_args__ = (
4697 Index('notification_type_idx', 'type'),
4697 Index('notification_type_idx', 'type'),
4698 base_table_args,
4698 base_table_args,
4699 )
4699 )
4700
4700
4701 TYPE_CHANGESET_COMMENT = 'cs_comment'
4701 TYPE_CHANGESET_COMMENT = 'cs_comment'
4702 TYPE_MESSAGE = 'message'
4702 TYPE_MESSAGE = 'message'
4703 TYPE_MENTION = 'mention'
4703 TYPE_MENTION = 'mention'
4704 TYPE_REGISTRATION = 'registration'
4704 TYPE_REGISTRATION = 'registration'
4705 TYPE_PULL_REQUEST = 'pull_request'
4705 TYPE_PULL_REQUEST = 'pull_request'
4706 TYPE_PULL_REQUEST_COMMENT = 'pull_request_comment'
4706 TYPE_PULL_REQUEST_COMMENT = 'pull_request_comment'
4707 TYPE_PULL_REQUEST_UPDATE = 'pull_request_update'
4707 TYPE_PULL_REQUEST_UPDATE = 'pull_request_update'
4708
4708
4709 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
4709 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
4710 subject = Column('subject', Unicode(512), nullable=True)
4710 subject = Column('subject', Unicode(512), nullable=True)
4711 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
4711 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
4712 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
4712 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
4713 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4713 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4714 type_ = Column('type', Unicode(255))
4714 type_ = Column('type', Unicode(255))
4715
4715
4716 created_by_user = relationship('User', back_populates='user_created_notifications')
4716 created_by_user = relationship('User', back_populates='user_created_notifications')
4717 notifications_to_users = relationship('UserNotification', lazy='joined', cascade="all, delete-orphan", back_populates='notification')
4717 notifications_to_users = relationship('UserNotification', lazy='joined', cascade="all, delete-orphan", back_populates='notification')
4718
4718
4719 @property
4719 @property
4720 def recipients(self):
4720 def recipients(self):
4721 return [x.user for x in UserNotification.query()\
4721 return [x.user for x in UserNotification.query()\
4722 .filter(UserNotification.notification == self)\
4722 .filter(UserNotification.notification == self)\
4723 .order_by(UserNotification.user_id.asc()).all()]
4723 .order_by(UserNotification.user_id.asc()).all()]
4724
4724
4725 @classmethod
4725 @classmethod
4726 def create(cls, created_by, subject, body, recipients, type_=None):
4726 def create(cls, created_by, subject, body, recipients, type_=None):
4727 if type_ is None:
4727 if type_ is None:
4728 type_ = Notification.TYPE_MESSAGE
4728 type_ = Notification.TYPE_MESSAGE
4729
4729
4730 notification = cls()
4730 notification = cls()
4731 notification.created_by_user = created_by
4731 notification.created_by_user = created_by
4732 notification.subject = subject
4732 notification.subject = subject
4733 notification.body = body
4733 notification.body = body
4734 notification.type_ = type_
4734 notification.type_ = type_
4735 notification.created_on = datetime.datetime.now()
4735 notification.created_on = datetime.datetime.now()
4736
4736
4737 # For each recipient link the created notification to his account
4737 # For each recipient link the created notification to his account
4738 for u in recipients:
4738 for u in recipients:
4739 assoc = UserNotification()
4739 assoc = UserNotification()
4740 assoc.user_id = u.user_id
4740 assoc.user_id = u.user_id
4741 assoc.notification = notification
4741 assoc.notification = notification
4742
4742
4743 # if created_by is inside recipients mark his notification
4743 # if created_by is inside recipients mark his notification
4744 # as read
4744 # as read
4745 if u.user_id == created_by.user_id:
4745 if u.user_id == created_by.user_id:
4746 assoc.read = True
4746 assoc.read = True
4747 Session().add(assoc)
4747 Session().add(assoc)
4748
4748
4749 Session().add(notification)
4749 Session().add(notification)
4750
4750
4751 return notification
4751 return notification
4752
4752
4753
4753
4754 class UserNotification(Base, BaseModel):
4754 class UserNotification(Base, BaseModel):
4755 __tablename__ = 'user_to_notification'
4755 __tablename__ = 'user_to_notification'
4756 __table_args__ = (
4756 __table_args__ = (
4757 UniqueConstraint('user_id', 'notification_id'),
4757 UniqueConstraint('user_id', 'notification_id'),
4758 base_table_args
4758 base_table_args
4759 )
4759 )
4760
4760
4761 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
4761 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
4762 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
4762 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
4763 read = Column('read', Boolean, default=False)
4763 read = Column('read', Boolean, default=False)
4764 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
4764 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
4765
4765
4766 user = relationship('User', lazy="joined", back_populates='notifications')
4766 user = relationship('User', lazy="joined", back_populates='notifications')
4767 notification = relationship('Notification', lazy="joined", order_by=lambda: Notification.created_on.desc(), back_populates='notifications_to_users')
4767 notification = relationship('Notification', lazy="joined", order_by=lambda: Notification.created_on.desc(), back_populates='notifications_to_users')
4768
4768
4769 def mark_as_read(self):
4769 def mark_as_read(self):
4770 self.read = True
4770 self.read = True
4771 Session().add(self)
4771 Session().add(self)
4772
4772
4773
4773
4774 class UserNotice(Base, BaseModel):
4774 class UserNotice(Base, BaseModel):
4775 __tablename__ = 'user_notices'
4775 __tablename__ = 'user_notices'
4776 __table_args__ = (
4776 __table_args__ = (
4777 base_table_args
4777 base_table_args
4778 )
4778 )
4779
4779
4780 NOTIFICATION_TYPE_MESSAGE = 'message'
4780 NOTIFICATION_TYPE_MESSAGE = 'message'
4781 NOTIFICATION_TYPE_NOTICE = 'notice'
4781 NOTIFICATION_TYPE_NOTICE = 'notice'
4782
4782
4783 NOTIFICATION_LEVEL_INFO = 'info'
4783 NOTIFICATION_LEVEL_INFO = 'info'
4784 NOTIFICATION_LEVEL_WARNING = 'warning'
4784 NOTIFICATION_LEVEL_WARNING = 'warning'
4785 NOTIFICATION_LEVEL_ERROR = 'error'
4785 NOTIFICATION_LEVEL_ERROR = 'error'
4786
4786
4787 user_notice_id = Column('gist_id', Integer(), primary_key=True)
4787 user_notice_id = Column('gist_id', Integer(), primary_key=True)
4788
4788
4789 notice_subject = Column('notice_subject', Unicode(512), nullable=True)
4789 notice_subject = Column('notice_subject', Unicode(512), nullable=True)
4790 notice_body = Column('notice_body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
4790 notice_body = Column('notice_body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
4791
4791
4792 notice_read = Column('notice_read', Boolean, default=False)
4792 notice_read = Column('notice_read', Boolean, default=False)
4793
4793
4794 notification_level = Column('notification_level', String(1024), default=NOTIFICATION_LEVEL_INFO)
4794 notification_level = Column('notification_level', String(1024), default=NOTIFICATION_LEVEL_INFO)
4795 notification_type = Column('notification_type', String(1024), default=NOTIFICATION_TYPE_NOTICE)
4795 notification_type = Column('notification_type', String(1024), default=NOTIFICATION_TYPE_NOTICE)
4796
4796
4797 notice_created_by = Column('notice_created_by', Integer(), ForeignKey('users.user_id'), nullable=True)
4797 notice_created_by = Column('notice_created_by', Integer(), ForeignKey('users.user_id'), nullable=True)
4798 notice_created_on = Column('notice_created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4798 notice_created_on = Column('notice_created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4799
4799
4800 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'))
4800 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'))
4801 user = relationship('User', lazy="joined", primaryjoin='User.user_id==UserNotice.user_id')
4801 user = relationship('User', lazy="joined", primaryjoin='User.user_id==UserNotice.user_id')
4802
4802
4803 @classmethod
4803 @classmethod
4804 def create_for_user(cls, user, subject, body, notice_level=NOTIFICATION_LEVEL_INFO, allow_duplicate=False):
4804 def create_for_user(cls, user, subject, body, notice_level=NOTIFICATION_LEVEL_INFO, allow_duplicate=False):
4805
4805
4806 if notice_level not in [cls.NOTIFICATION_LEVEL_ERROR,
4806 if notice_level not in [cls.NOTIFICATION_LEVEL_ERROR,
4807 cls.NOTIFICATION_LEVEL_WARNING,
4807 cls.NOTIFICATION_LEVEL_WARNING,
4808 cls.NOTIFICATION_LEVEL_INFO]:
4808 cls.NOTIFICATION_LEVEL_INFO]:
4809 return
4809 return
4810
4810
4811 from rhodecode.model.user import UserModel
4811 from rhodecode.model.user import UserModel
4812 user = UserModel().get_user(user)
4812 user = UserModel().get_user(user)
4813
4813
4814 new_notice = UserNotice()
4814 new_notice = UserNotice()
4815 if not allow_duplicate:
4815 if not allow_duplicate:
4816 existing_msg = UserNotice().query() \
4816 existing_msg = UserNotice().query() \
4817 .filter(UserNotice.user == user) \
4817 .filter(UserNotice.user == user) \
4818 .filter(UserNotice.notice_body == body) \
4818 .filter(UserNotice.notice_body == body) \
4819 .filter(UserNotice.notice_read == false()) \
4819 .filter(UserNotice.notice_read == false()) \
4820 .scalar()
4820 .scalar()
4821 if existing_msg:
4821 if existing_msg:
4822 log.warning('Ignoring duplicate notice for user %s', user)
4822 log.warning('Ignoring duplicate notice for user %s', user)
4823 return
4823 return
4824
4824
4825 new_notice.user = user
4825 new_notice.user = user
4826 new_notice.notice_subject = subject
4826 new_notice.notice_subject = subject
4827 new_notice.notice_body = body
4827 new_notice.notice_body = body
4828 new_notice.notification_level = notice_level
4828 new_notice.notification_level = notice_level
4829 Session().add(new_notice)
4829 Session().add(new_notice)
4830 Session().commit()
4830 Session().commit()
4831
4831
4832
4832
4833 class Gist(Base, BaseModel):
4833 class Gist(Base, BaseModel):
4834 __tablename__ = 'gists'
4834 __tablename__ = 'gists'
4835 __table_args__ = (
4835 __table_args__ = (
4836 Index('g_gist_access_id_idx', 'gist_access_id'),
4836 Index('g_gist_access_id_idx', 'gist_access_id'),
4837 Index('g_created_on_idx', 'created_on'),
4837 Index('g_created_on_idx', 'created_on'),
4838 base_table_args
4838 base_table_args
4839 )
4839 )
4840
4840
4841 GIST_PUBLIC = 'public'
4841 GIST_PUBLIC = 'public'
4842 GIST_PRIVATE = 'private'
4842 GIST_PRIVATE = 'private'
4843 DEFAULT_FILENAME = 'gistfile1.txt'
4843 DEFAULT_FILENAME = 'gistfile1.txt'
4844
4844
4845 ACL_LEVEL_PUBLIC = 'acl_public'
4845 ACL_LEVEL_PUBLIC = 'acl_public'
4846 ACL_LEVEL_PRIVATE = 'acl_private'
4846 ACL_LEVEL_PRIVATE = 'acl_private'
4847
4847
4848 gist_id = Column('gist_id', Integer(), primary_key=True)
4848 gist_id = Column('gist_id', Integer(), primary_key=True)
4849 gist_access_id = Column('gist_access_id', Unicode(250))
4849 gist_access_id = Column('gist_access_id', Unicode(250))
4850 gist_description = Column('gist_description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
4850 gist_description = Column('gist_description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
4851 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
4851 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
4852 gist_expires = Column('gist_expires', Float(53), nullable=False)
4852 gist_expires = Column('gist_expires', Float(53), nullable=False)
4853 gist_type = Column('gist_type', Unicode(128), nullable=False)
4853 gist_type = Column('gist_type', Unicode(128), nullable=False)
4854 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4854 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4855 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4855 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4856 acl_level = Column('acl_level', Unicode(128), nullable=True)
4856 acl_level = Column('acl_level', Unicode(128), nullable=True)
4857
4857
4858 owner = relationship('User', back_populates='user_gists')
4858 owner = relationship('User', back_populates='user_gists')
4859
4859
4860 def __repr__(self):
4860 def __repr__(self):
4861 return f'<Gist:[{self.gist_type}]{self.gist_access_id}>'
4861 return f'<Gist:[{self.gist_type}]{self.gist_access_id}>'
4862
4862
4863 @hybrid_property
4863 @hybrid_property
4864 def description_safe(self):
4864 def description_safe(self):
4865 from rhodecode.lib import helpers as h
4865 from rhodecode.lib import helpers as h
4866 return h.escape(self.gist_description)
4866 return h.escape(self.gist_description)
4867
4867
4868 @classmethod
4868 @classmethod
4869 def get_or_404(cls, id_):
4869 def get_or_404(cls, id_):
4870 from pyramid.httpexceptions import HTTPNotFound
4870 from pyramid.httpexceptions import HTTPNotFound
4871
4871
4872 res = cls.query().filter(cls.gist_access_id == id_).scalar()
4872 res = cls.query().filter(cls.gist_access_id == id_).scalar()
4873 if not res:
4873 if not res:
4874 log.debug('WARN: No DB entry with id %s', id_)
4874 log.debug('WARN: No DB entry with id %s', id_)
4875 raise HTTPNotFound()
4875 raise HTTPNotFound()
4876 return res
4876 return res
4877
4877
4878 @classmethod
4878 @classmethod
4879 def get_by_access_id(cls, gist_access_id):
4879 def get_by_access_id(cls, gist_access_id):
4880 return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
4880 return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
4881
4881
4882 def gist_url(self):
4882 def gist_url(self):
4883 from rhodecode.model.gist import GistModel
4883 from rhodecode.model.gist import GistModel
4884 return GistModel().get_url(self)
4884 return GistModel().get_url(self)
4885
4885
4886 @classmethod
4886 @classmethod
4887 def base_path(cls):
4887 def base_path(cls):
4888 """
4888 """
4889 Returns base path when all gists are stored
4889 Returns base path when all gists are stored
4890
4890
4891 :param cls:
4891 :param cls:
4892 """
4892 """
4893 from rhodecode.model.gist import GIST_STORE_LOC
4893 from rhodecode.model.gist import GIST_STORE_LOC
4894 q = Session().query(RhodeCodeUi)\
4894 q = Session().query(RhodeCodeUi)\
4895 .filter(RhodeCodeUi.ui_key == URL_SEP)
4895 .filter(RhodeCodeUi.ui_key == URL_SEP)
4896 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
4896 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
4897 return os.path.join(q.one().ui_value, GIST_STORE_LOC)
4897 return os.path.join(q.one().ui_value, GIST_STORE_LOC)
4898
4898
4899 def get_api_data(self):
4899 def get_api_data(self):
4900 """
4900 """
4901 Common function for generating gist related data for API
4901 Common function for generating gist related data for API
4902 """
4902 """
4903 gist = self
4903 gist = self
4904 data = {
4904 data = {
4905 'gist_id': gist.gist_id,
4905 'gist_id': gist.gist_id,
4906 'type': gist.gist_type,
4906 'type': gist.gist_type,
4907 'access_id': gist.gist_access_id,
4907 'access_id': gist.gist_access_id,
4908 'description': gist.gist_description,
4908 'description': gist.gist_description,
4909 'url': gist.gist_url(),
4909 'url': gist.gist_url(),
4910 'expires': gist.gist_expires,
4910 'expires': gist.gist_expires,
4911 'created_on': gist.created_on,
4911 'created_on': gist.created_on,
4912 'modified_at': gist.modified_at,
4912 'modified_at': gist.modified_at,
4913 'content': None,
4913 'content': None,
4914 'acl_level': gist.acl_level,
4914 'acl_level': gist.acl_level,
4915 }
4915 }
4916 return data
4916 return data
4917
4917
4918 def __json__(self):
4918 def __json__(self):
4919 data = dict(
4919 data = dict(
4920 )
4920 )
4921 data.update(self.get_api_data())
4921 data.update(self.get_api_data())
4922 return data
4922 return data
4923 # SCM functions
4923 # SCM functions
4924
4924
4925 def scm_instance(self, **kwargs):
4925 def scm_instance(self, **kwargs):
4926 """
4926 """
4927 Get an instance of VCS Repository
4927 Get an instance of VCS Repository
4928
4928
4929 :param kwargs:
4929 :param kwargs:
4930 """
4930 """
4931 from rhodecode.model.gist import GistModel
4931 from rhodecode.model.gist import GistModel
4932 full_repo_path = os.path.join(self.base_path(), self.gist_access_id)
4932 full_repo_path = os.path.join(self.base_path(), self.gist_access_id)
4933 return get_vcs_instance(
4933 return get_vcs_instance(
4934 repo_path=safe_str(full_repo_path), create=False,
4934 repo_path=safe_str(full_repo_path), create=False,
4935 _vcs_alias=GistModel.vcs_backend)
4935 _vcs_alias=GistModel.vcs_backend)
4936
4936
4937
4937
4938 class ExternalIdentity(Base, BaseModel):
4938 class ExternalIdentity(Base, BaseModel):
4939 __tablename__ = 'external_identities'
4939 __tablename__ = 'external_identities'
4940 __table_args__ = (
4940 __table_args__ = (
4941 Index('local_user_id_idx', 'local_user_id'),
4941 Index('local_user_id_idx', 'local_user_id'),
4942 Index('external_id_idx', 'external_id'),
4942 Index('external_id_idx', 'external_id'),
4943 base_table_args
4943 base_table_args
4944 )
4944 )
4945
4945
4946 external_id = Column('external_id', Unicode(255), default='', primary_key=True)
4946 external_id = Column('external_id', Unicode(255), default='', primary_key=True)
4947 external_username = Column('external_username', Unicode(1024), default='')
4947 external_username = Column('external_username', Unicode(1024), default='')
4948 local_user_id = Column('local_user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
4948 local_user_id = Column('local_user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
4949 provider_name = Column('provider_name', Unicode(255), default='', primary_key=True)
4949 provider_name = Column('provider_name', Unicode(255), default='', primary_key=True)
4950 access_token = Column('access_token', String(1024), default='')
4950 access_token = Column('access_token', String(1024), default='')
4951 alt_token = Column('alt_token', String(1024), default='')
4951 alt_token = Column('alt_token', String(1024), default='')
4952 token_secret = Column('token_secret', String(1024), default='')
4952 token_secret = Column('token_secret', String(1024), default='')
4953
4953
4954 @classmethod
4954 @classmethod
4955 def by_external_id_and_provider(cls, external_id, provider_name, local_user_id=None):
4955 def by_external_id_and_provider(cls, external_id, provider_name, local_user_id=None):
4956 """
4956 """
4957 Returns ExternalIdentity instance based on search params
4957 Returns ExternalIdentity instance based on search params
4958
4958
4959 :param external_id:
4959 :param external_id:
4960 :param provider_name:
4960 :param provider_name:
4961 :return: ExternalIdentity
4961 :return: ExternalIdentity
4962 """
4962 """
4963 query = cls.query()
4963 query = cls.query()
4964 query = query.filter(cls.external_id == external_id)
4964 query = query.filter(cls.external_id == external_id)
4965 query = query.filter(cls.provider_name == provider_name)
4965 query = query.filter(cls.provider_name == provider_name)
4966 if local_user_id:
4966 if local_user_id:
4967 query = query.filter(cls.local_user_id == local_user_id)
4967 query = query.filter(cls.local_user_id == local_user_id)
4968 return query.first()
4968 return query.first()
4969
4969
4970 @classmethod
4970 @classmethod
4971 def user_by_external_id_and_provider(cls, external_id, provider_name):
4971 def user_by_external_id_and_provider(cls, external_id, provider_name):
4972 """
4972 """
4973 Returns User instance based on search params
4973 Returns User instance based on search params
4974
4974
4975 :param external_id:
4975 :param external_id:
4976 :param provider_name:
4976 :param provider_name:
4977 :return: User
4977 :return: User
4978 """
4978 """
4979 query = User.query()
4979 query = User.query()
4980 query = query.filter(cls.external_id == external_id)
4980 query = query.filter(cls.external_id == external_id)
4981 query = query.filter(cls.provider_name == provider_name)
4981 query = query.filter(cls.provider_name == provider_name)
4982 query = query.filter(User.user_id == cls.local_user_id)
4982 query = query.filter(User.user_id == cls.local_user_id)
4983 return query.first()
4983 return query.first()
4984
4984
4985 @classmethod
4985 @classmethod
4986 def by_local_user_id(cls, local_user_id):
4986 def by_local_user_id(cls, local_user_id):
4987 """
4987 """
4988 Returns all tokens for user
4988 Returns all tokens for user
4989
4989
4990 :param local_user_id:
4990 :param local_user_id:
4991 :return: ExternalIdentity
4991 :return: ExternalIdentity
4992 """
4992 """
4993 query = cls.query()
4993 query = cls.query()
4994 query = query.filter(cls.local_user_id == local_user_id)
4994 query = query.filter(cls.local_user_id == local_user_id)
4995 return query
4995 return query
4996
4996
4997 @classmethod
4997 @classmethod
4998 def load_provider_plugin(cls, plugin_id):
4998 def load_provider_plugin(cls, plugin_id):
4999 from rhodecode.authentication.base import loadplugin
4999 from rhodecode.authentication.base import loadplugin
5000 _plugin_id = 'egg:rhodecode-enterprise-ee#{}'.format(plugin_id)
5000 _plugin_id = 'egg:rhodecode-enterprise-ee#{}'.format(plugin_id)
5001 auth_plugin = loadplugin(_plugin_id)
5001 auth_plugin = loadplugin(_plugin_id)
5002 return auth_plugin
5002 return auth_plugin
5003
5003
5004
5004
5005 class Integration(Base, BaseModel):
5005 class Integration(Base, BaseModel):
5006 __tablename__ = 'integrations'
5006 __tablename__ = 'integrations'
5007 __table_args__ = (
5007 __table_args__ = (
5008 base_table_args
5008 base_table_args
5009 )
5009 )
5010
5010
5011 integration_id = Column('integration_id', Integer(), primary_key=True)
5011 integration_id = Column('integration_id', Integer(), primary_key=True)
5012 integration_type = Column('integration_type', String(255))
5012 integration_type = Column('integration_type', String(255))
5013 enabled = Column('enabled', Boolean(), nullable=False)
5013 enabled = Column('enabled', Boolean(), nullable=False)
5014 name = Column('name', String(255), nullable=False)
5014 name = Column('name', String(255), nullable=False)
5015 child_repos_only = Column('child_repos_only', Boolean(), nullable=False, default=False)
5015 child_repos_only = Column('child_repos_only', Boolean(), nullable=False, default=False)
5016
5016
5017 settings = Column(
5017 settings = Column(
5018 'settings_json', MutationObj.as_mutable(
5018 'settings_json', MutationObj.as_mutable(
5019 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
5019 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
5020 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
5020 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
5021 repo = relationship('Repository', lazy='joined', back_populates='integrations')
5021 repo = relationship('Repository', lazy='joined', back_populates='integrations')
5022
5022
5023 repo_group_id = Column('repo_group_id', Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
5023 repo_group_id = Column('repo_group_id', Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
5024 repo_group = relationship('RepoGroup', lazy='joined', back_populates='integrations')
5024 repo_group = relationship('RepoGroup', lazy='joined', back_populates='integrations')
5025
5025
5026 @property
5026 @property
5027 def scope(self):
5027 def scope(self):
5028 if self.repo:
5028 if self.repo:
5029 return repr(self.repo)
5029 return repr(self.repo)
5030 if self.repo_group:
5030 if self.repo_group:
5031 if self.child_repos_only:
5031 if self.child_repos_only:
5032 return repr(self.repo_group) + ' (child repos only)'
5032 return repr(self.repo_group) + ' (child repos only)'
5033 else:
5033 else:
5034 return repr(self.repo_group) + ' (recursive)'
5034 return repr(self.repo_group) + ' (recursive)'
5035 if self.child_repos_only:
5035 if self.child_repos_only:
5036 return 'root_repos'
5036 return 'root_repos'
5037 return 'global'
5037 return 'global'
5038
5038
5039 def __repr__(self):
5039 def __repr__(self):
5040 return '<Integration(%r, %r)>' % (self.integration_type, self.scope)
5040 return '<Integration(%r, %r)>' % (self.integration_type, self.scope)
5041
5041
5042
5042
5043 class RepoReviewRuleUser(Base, BaseModel):
5043 class RepoReviewRuleUser(Base, BaseModel):
5044 __tablename__ = 'repo_review_rules_users'
5044 __tablename__ = 'repo_review_rules_users'
5045 __table_args__ = (
5045 __table_args__ = (
5046 base_table_args
5046 base_table_args
5047 )
5047 )
5048 ROLE_REVIEWER = 'reviewer'
5048 ROLE_REVIEWER = 'reviewer'
5049 ROLE_OBSERVER = 'observer'
5049 ROLE_OBSERVER = 'observer'
5050 ROLES = [ROLE_REVIEWER, ROLE_OBSERVER]
5050 ROLES = [ROLE_REVIEWER, ROLE_OBSERVER]
5051
5051
5052 repo_review_rule_user_id = Column('repo_review_rule_user_id', Integer(), primary_key=True)
5052 repo_review_rule_user_id = Column('repo_review_rule_user_id', Integer(), primary_key=True)
5053 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
5053 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
5054 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False)
5054 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False)
5055 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
5055 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
5056 role = Column('role', Unicode(255), nullable=True, default=ROLE_REVIEWER)
5056 role = Column('role', Unicode(255), nullable=True, default=ROLE_REVIEWER)
5057 user = relationship('User', back_populates='user_review_rules')
5057 user = relationship('User', back_populates='user_review_rules')
5058
5058
5059 def rule_data(self):
5059 def rule_data(self):
5060 return {
5060 return {
5061 'mandatory': self.mandatory,
5061 'mandatory': self.mandatory,
5062 'role': self.role,
5062 'role': self.role,
5063 }
5063 }
5064
5064
5065
5065
5066 class RepoReviewRuleUserGroup(Base, BaseModel):
5066 class RepoReviewRuleUserGroup(Base, BaseModel):
5067 __tablename__ = 'repo_review_rules_users_groups'
5067 __tablename__ = 'repo_review_rules_users_groups'
5068 __table_args__ = (
5068 __table_args__ = (
5069 base_table_args
5069 base_table_args
5070 )
5070 )
5071
5071
5072 VOTE_RULE_ALL = -1
5072 VOTE_RULE_ALL = -1
5073 ROLE_REVIEWER = 'reviewer'
5073 ROLE_REVIEWER = 'reviewer'
5074 ROLE_OBSERVER = 'observer'
5074 ROLE_OBSERVER = 'observer'
5075 ROLES = [ROLE_REVIEWER, ROLE_OBSERVER]
5075 ROLES = [ROLE_REVIEWER, ROLE_OBSERVER]
5076
5076
5077 repo_review_rule_users_group_id = Column('repo_review_rule_users_group_id', Integer(), primary_key=True)
5077 repo_review_rule_users_group_id = Column('repo_review_rule_users_group_id', Integer(), primary_key=True)
5078 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
5078 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
5079 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False)
5079 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False)
5080 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
5080 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
5081 role = Column('role', Unicode(255), nullable=True, default=ROLE_REVIEWER)
5081 role = Column('role', Unicode(255), nullable=True, default=ROLE_REVIEWER)
5082 vote_rule = Column("vote_rule", Integer(), nullable=True, default=VOTE_RULE_ALL)
5082 vote_rule = Column("vote_rule", Integer(), nullable=True, default=VOTE_RULE_ALL)
5083 users_group = relationship('UserGroup')
5083 users_group = relationship('UserGroup')
5084
5084
5085 def rule_data(self):
5085 def rule_data(self):
5086 return {
5086 return {
5087 'mandatory': self.mandatory,
5087 'mandatory': self.mandatory,
5088 'role': self.role,
5088 'role': self.role,
5089 'vote_rule': self.vote_rule
5089 'vote_rule': self.vote_rule
5090 }
5090 }
5091
5091
5092 @property
5092 @property
5093 def vote_rule_label(self):
5093 def vote_rule_label(self):
5094 if not self.vote_rule or self.vote_rule == self.VOTE_RULE_ALL:
5094 if not self.vote_rule or self.vote_rule == self.VOTE_RULE_ALL:
5095 return 'all must vote'
5095 return 'all must vote'
5096 else:
5096 else:
5097 return 'min. vote {}'.format(self.vote_rule)
5097 return 'min. vote {}'.format(self.vote_rule)
5098
5098
5099
5099
5100 class RepoReviewRule(Base, BaseModel):
5100 class RepoReviewRule(Base, BaseModel):
5101 __tablename__ = 'repo_review_rules'
5101 __tablename__ = 'repo_review_rules'
5102 __table_args__ = (
5102 __table_args__ = (
5103 base_table_args
5103 base_table_args
5104 )
5104 )
5105
5105
5106 repo_review_rule_id = Column(
5106 repo_review_rule_id = Column(
5107 'repo_review_rule_id', Integer(), primary_key=True)
5107 'repo_review_rule_id', Integer(), primary_key=True)
5108 repo_id = Column(
5108 repo_id = Column(
5109 "repo_id", Integer(), ForeignKey('repositories.repo_id'))
5109 "repo_id", Integer(), ForeignKey('repositories.repo_id'))
5110 repo = relationship('Repository', back_populates='review_rules')
5110 repo = relationship('Repository', back_populates='review_rules')
5111
5111
5112 review_rule_name = Column('review_rule_name', String(255))
5112 review_rule_name = Column('review_rule_name', String(255))
5113 _branch_pattern = Column("branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default='*') # glob
5113 _branch_pattern = Column("branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default='*') # glob
5114 _target_branch_pattern = Column("target_branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default='*') # glob
5114 _target_branch_pattern = Column("target_branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default='*') # glob
5115 _file_pattern = Column("file_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default='*') # glob
5115 _file_pattern = Column("file_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default='*') # glob
5116
5116
5117 use_authors_for_review = Column("use_authors_for_review", Boolean(), nullable=False, default=False)
5117 use_authors_for_review = Column("use_authors_for_review", Boolean(), nullable=False, default=False)
5118
5118
5119 # Legacy fields, just for backward compat
5119 # Legacy fields, just for backward compat
5120 _forbid_author_to_review = Column("forbid_author_to_review", Boolean(), nullable=False, default=False)
5120 _forbid_author_to_review = Column("forbid_author_to_review", Boolean(), nullable=False, default=False)
5121 _forbid_commit_author_to_review = Column("forbid_commit_author_to_review", Boolean(), nullable=False, default=False)
5121 _forbid_commit_author_to_review = Column("forbid_commit_author_to_review", Boolean(), nullable=False, default=False)
5122
5122
5123 pr_author = Column("pr_author", UnicodeText().with_variant(UnicodeText(255), 'mysql'), nullable=True)
5123 pr_author = Column("pr_author", UnicodeText().with_variant(UnicodeText(255), 'mysql'), nullable=True)
5124 commit_author = Column("commit_author", UnicodeText().with_variant(UnicodeText(255), 'mysql'), nullable=True)
5124 commit_author = Column("commit_author", UnicodeText().with_variant(UnicodeText(255), 'mysql'), nullable=True)
5125
5125
5126 forbid_adding_reviewers = Column("forbid_adding_reviewers", Boolean(), nullable=False, default=False)
5126 forbid_adding_reviewers = Column("forbid_adding_reviewers", Boolean(), nullable=False, default=False)
5127
5127
5128 rule_users = relationship('RepoReviewRuleUser')
5128 rule_users = relationship('RepoReviewRuleUser')
5129 rule_user_groups = relationship('RepoReviewRuleUserGroup')
5129 rule_user_groups = relationship('RepoReviewRuleUserGroup')
5130
5130
5131 def _validate_pattern(self, value):
5131 def _validate_pattern(self, value):
5132 re.compile('^' + glob2re(value) + '$')
5132 re.compile('^' + glob2re(value) + '$')
5133
5133
5134 @hybrid_property
5134 @hybrid_property
5135 def source_branch_pattern(self):
5135 def source_branch_pattern(self):
5136 return self._branch_pattern or '*'
5136 return self._branch_pattern or '*'
5137
5137
5138 @source_branch_pattern.setter
5138 @source_branch_pattern.setter
5139 def source_branch_pattern(self, value):
5139 def source_branch_pattern(self, value):
5140 self._validate_pattern(value)
5140 self._validate_pattern(value)
5141 self._branch_pattern = value or '*'
5141 self._branch_pattern = value or '*'
5142
5142
5143 @hybrid_property
5143 @hybrid_property
5144 def target_branch_pattern(self):
5144 def target_branch_pattern(self):
5145 return self._target_branch_pattern or '*'
5145 return self._target_branch_pattern or '*'
5146
5146
5147 @target_branch_pattern.setter
5147 @target_branch_pattern.setter
5148 def target_branch_pattern(self, value):
5148 def target_branch_pattern(self, value):
5149 self._validate_pattern(value)
5149 self._validate_pattern(value)
5150 self._target_branch_pattern = value or '*'
5150 self._target_branch_pattern = value or '*'
5151
5151
5152 @hybrid_property
5152 @hybrid_property
5153 def file_pattern(self):
5153 def file_pattern(self):
5154 return self._file_pattern or '*'
5154 return self._file_pattern or '*'
5155
5155
5156 @file_pattern.setter
5156 @file_pattern.setter
5157 def file_pattern(self, value):
5157 def file_pattern(self, value):
5158 self._validate_pattern(value)
5158 self._validate_pattern(value)
5159 self._file_pattern = value or '*'
5159 self._file_pattern = value or '*'
5160
5160
5161 @hybrid_property
5161 @hybrid_property
5162 def forbid_pr_author_to_review(self):
5162 def forbid_pr_author_to_review(self):
5163 return self.pr_author == 'forbid_pr_author'
5163 return self.pr_author == 'forbid_pr_author'
5164
5164
5165 @hybrid_property
5165 @hybrid_property
5166 def include_pr_author_to_review(self):
5166 def include_pr_author_to_review(self):
5167 return self.pr_author == 'include_pr_author'
5167 return self.pr_author == 'include_pr_author'
5168
5168
5169 @hybrid_property
5169 @hybrid_property
5170 def forbid_commit_author_to_review(self):
5170 def forbid_commit_author_to_review(self):
5171 return self.commit_author == 'forbid_commit_author'
5171 return self.commit_author == 'forbid_commit_author'
5172
5172
5173 @hybrid_property
5173 @hybrid_property
5174 def include_commit_author_to_review(self):
5174 def include_commit_author_to_review(self):
5175 return self.commit_author == 'include_commit_author'
5175 return self.commit_author == 'include_commit_author'
5176
5176
5177 def matches(self, source_branch, target_branch, files_changed):
5177 def matches(self, source_branch, target_branch, files_changed):
5178 """
5178 """
5179 Check if this review rule matches a branch/files in a pull request
5179 Check if this review rule matches a branch/files in a pull request
5180
5180
5181 :param source_branch: source branch name for the commit
5181 :param source_branch: source branch name for the commit
5182 :param target_branch: target branch name for the commit
5182 :param target_branch: target branch name for the commit
5183 :param files_changed: list of file paths changed in the pull request
5183 :param files_changed: list of file paths changed in the pull request
5184 """
5184 """
5185
5185
5186 source_branch = source_branch or ''
5186 source_branch = source_branch or ''
5187 target_branch = target_branch or ''
5187 target_branch = target_branch or ''
5188 files_changed = files_changed or []
5188 files_changed = files_changed or []
5189
5189
5190 branch_matches = True
5190 branch_matches = True
5191 if source_branch or target_branch:
5191 if source_branch or target_branch:
5192 if self.source_branch_pattern == '*':
5192 if self.source_branch_pattern == '*':
5193 source_branch_match = True
5193 source_branch_match = True
5194 else:
5194 else:
5195 if self.source_branch_pattern.startswith('re:'):
5195 if self.source_branch_pattern.startswith('re:'):
5196 source_pattern = self.source_branch_pattern[3:]
5196 source_pattern = self.source_branch_pattern[3:]
5197 else:
5197 else:
5198 source_pattern = '^' + glob2re(self.source_branch_pattern) + '$'
5198 source_pattern = '^' + glob2re(self.source_branch_pattern) + '$'
5199 source_branch_regex = re.compile(source_pattern)
5199 source_branch_regex = re.compile(source_pattern)
5200 source_branch_match = bool(source_branch_regex.search(source_branch))
5200 source_branch_match = bool(source_branch_regex.search(source_branch))
5201 if self.target_branch_pattern == '*':
5201 if self.target_branch_pattern == '*':
5202 target_branch_match = True
5202 target_branch_match = True
5203 else:
5203 else:
5204 if self.target_branch_pattern.startswith('re:'):
5204 if self.target_branch_pattern.startswith('re:'):
5205 target_pattern = self.target_branch_pattern[3:]
5205 target_pattern = self.target_branch_pattern[3:]
5206 else:
5206 else:
5207 target_pattern = '^' + glob2re(self.target_branch_pattern) + '$'
5207 target_pattern = '^' + glob2re(self.target_branch_pattern) + '$'
5208 target_branch_regex = re.compile(target_pattern)
5208 target_branch_regex = re.compile(target_pattern)
5209 target_branch_match = bool(target_branch_regex.search(target_branch))
5209 target_branch_match = bool(target_branch_regex.search(target_branch))
5210
5210
5211 branch_matches = source_branch_match and target_branch_match
5211 branch_matches = source_branch_match and target_branch_match
5212
5212
5213 files_matches = True
5213 files_matches = True
5214 if self.file_pattern != '*':
5214 if self.file_pattern != '*':
5215 files_matches = False
5215 files_matches = False
5216 if self.file_pattern.startswith('re:'):
5216 if self.file_pattern.startswith('re:'):
5217 file_pattern = self.file_pattern[3:]
5217 file_pattern = self.file_pattern[3:]
5218 else:
5218 else:
5219 file_pattern = glob2re(self.file_pattern)
5219 file_pattern = glob2re(self.file_pattern)
5220 file_regex = re.compile(file_pattern)
5220 file_regex = re.compile(file_pattern)
5221 for file_data in files_changed:
5221 for file_data in files_changed:
5222 filename = file_data.get('filename')
5222 filename = file_data.get('filename')
5223
5223
5224 if file_regex.search(filename):
5224 if file_regex.search(filename):
5225 files_matches = True
5225 files_matches = True
5226 break
5226 break
5227
5227
5228 return branch_matches and files_matches
5228 return branch_matches and files_matches
5229
5229
5230 @property
5230 @property
5231 def review_users(self):
5231 def review_users(self):
5232 """ Returns the users which this rule applies to """
5232 """ Returns the users which this rule applies to """
5233
5233
5234 users = collections.OrderedDict()
5234 users = collections.OrderedDict()
5235
5235
5236 for rule_user in self.rule_users:
5236 for rule_user in self.rule_users:
5237 if rule_user.user.active:
5237 if rule_user.user.active:
5238 if rule_user.user not in users:
5238 if rule_user.user not in users:
5239 users[rule_user.user.username] = {
5239 users[rule_user.user.username] = {
5240 'user': rule_user.user,
5240 'user': rule_user.user,
5241 'source': 'user',
5241 'source': 'user',
5242 'source_data': {},
5242 'source_data': {},
5243 'data': rule_user.rule_data()
5243 'data': rule_user.rule_data()
5244 }
5244 }
5245
5245
5246 for rule_user_group in self.rule_user_groups:
5246 for rule_user_group in self.rule_user_groups:
5247 source_data = {
5247 source_data = {
5248 'user_group_id': rule_user_group.users_group.users_group_id,
5248 'user_group_id': rule_user_group.users_group.users_group_id,
5249 'name': rule_user_group.users_group.users_group_name,
5249 'name': rule_user_group.users_group.users_group_name,
5250 'members': len(rule_user_group.users_group.members)
5250 'members': len(rule_user_group.users_group.members)
5251 }
5251 }
5252 for member in rule_user_group.users_group.members:
5252 for member in rule_user_group.users_group.members:
5253 if member.user.active:
5253 if member.user.active:
5254 key = member.user.username
5254 key = member.user.username
5255 if key in users:
5255 if key in users:
5256 # skip this member as we have him already
5256 # skip this member as we have him already
5257 # this prevents from override the "first" matched
5257 # this prevents from override the "first" matched
5258 # users with duplicates in multiple groups
5258 # users with duplicates in multiple groups
5259 continue
5259 continue
5260
5260
5261 users[key] = {
5261 users[key] = {
5262 'user': member.user,
5262 'user': member.user,
5263 'source': 'user_group',
5263 'source': 'user_group',
5264 'source_data': source_data,
5264 'source_data': source_data,
5265 'data': rule_user_group.rule_data()
5265 'data': rule_user_group.rule_data()
5266 }
5266 }
5267
5267
5268 return users
5268 return users
5269
5269
5270 def user_group_vote_rule(self, user_id):
5270 def user_group_vote_rule(self, user_id):
5271
5271
5272 rules = []
5272 rules = []
5273 if not self.rule_user_groups:
5273 if not self.rule_user_groups:
5274 return rules
5274 return rules
5275
5275
5276 for user_group in self.rule_user_groups:
5276 for user_group in self.rule_user_groups:
5277 user_group_members = [x.user_id for x in user_group.users_group.members]
5277 user_group_members = [x.user_id for x in user_group.users_group.members]
5278 if user_id in user_group_members:
5278 if user_id in user_group_members:
5279 rules.append(user_group)
5279 rules.append(user_group)
5280 return rules
5280 return rules
5281
5281
5282 def __repr__(self):
5282 def __repr__(self):
5283 return f'<RepoReviewerRule(id={self.repo_review_rule_id}, repo={self.repo!r})>'
5283 return f'<RepoReviewerRule(id={self.repo_review_rule_id}, repo={self.repo!r})>'
5284
5284
5285
5285
5286 class ScheduleEntry(Base, BaseModel):
5286 class ScheduleEntry(Base, BaseModel):
5287 __tablename__ = 'schedule_entries'
5287 __tablename__ = 'schedule_entries'
5288 __table_args__ = (
5288 __table_args__ = (
5289 UniqueConstraint('schedule_name', name='s_schedule_name_idx'),
5289 UniqueConstraint('schedule_name', name='s_schedule_name_idx'),
5290 UniqueConstraint('task_uid', name='s_task_uid_idx'),
5290 UniqueConstraint('task_uid', name='s_task_uid_idx'),
5291 base_table_args,
5291 base_table_args,
5292 )
5292 )
5293
5293
5294 schedule_types = ['crontab', 'timedelta', 'integer']
5294 schedule_types = ['crontab', 'timedelta', 'integer']
5295 schedule_entry_id = Column('schedule_entry_id', Integer(), primary_key=True)
5295 schedule_entry_id = Column('schedule_entry_id', Integer(), primary_key=True)
5296
5296
5297 schedule_name = Column("schedule_name", String(255), nullable=False, unique=None, default=None)
5297 schedule_name = Column("schedule_name", String(255), nullable=False, unique=None, default=None)
5298 schedule_description = Column("schedule_description", String(10000), nullable=True, unique=None, default=None)
5298 schedule_description = Column("schedule_description", String(10000), nullable=True, unique=None, default=None)
5299 schedule_enabled = Column("schedule_enabled", Boolean(), nullable=False, unique=None, default=True)
5299 schedule_enabled = Column("schedule_enabled", Boolean(), nullable=False, unique=None, default=True)
5300
5300
5301 _schedule_type = Column("schedule_type", String(255), nullable=False, unique=None, default=None)
5301 _schedule_type = Column("schedule_type", String(255), nullable=False, unique=None, default=None)
5302 schedule_definition = Column('schedule_definition_json', MutationObj.as_mutable(JsonType(default=lambda: "", dialect_map=dict(mysql=LONGTEXT()))))
5302 schedule_definition = Column('schedule_definition_json', MutationObj.as_mutable(JsonType(default=lambda: "", dialect_map=dict(mysql=LONGTEXT()))))
5303
5303
5304 schedule_last_run = Column('schedule_last_run', DateTime(timezone=False), nullable=True, unique=None, default=None)
5304 schedule_last_run = Column('schedule_last_run', DateTime(timezone=False), nullable=True, unique=None, default=None)
5305 schedule_total_run_count = Column('schedule_total_run_count', Integer(), nullable=True, unique=None, default=0)
5305 schedule_total_run_count = Column('schedule_total_run_count', Integer(), nullable=True, unique=None, default=0)
5306
5306
5307 # task
5307 # task
5308 task_uid = Column("task_uid", String(255), nullable=False, unique=None, default=None)
5308 task_uid = Column("task_uid", String(255), nullable=False, unique=None, default=None)
5309 task_dot_notation = Column("task_dot_notation", String(4096), nullable=False, unique=None, default=None)
5309 task_dot_notation = Column("task_dot_notation", String(4096), nullable=False, unique=None, default=None)
5310 task_args = Column('task_args_json', MutationObj.as_mutable(JsonType(default=list, dialect_map=dict(mysql=LONGTEXT()))))
5310 task_args = Column('task_args_json', MutationObj.as_mutable(JsonType(default=list, dialect_map=dict(mysql=LONGTEXT()))))
5311 task_kwargs = Column('task_kwargs_json', MutationObj.as_mutable(JsonType(default=dict, dialect_map=dict(mysql=LONGTEXT()))))
5311 task_kwargs = Column('task_kwargs_json', MutationObj.as_mutable(JsonType(default=dict, dialect_map=dict(mysql=LONGTEXT()))))
5312
5312
5313 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
5313 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
5314 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=None)
5314 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=None)
5315
5315
5316 @hybrid_property
5316 @hybrid_property
5317 def schedule_type(self):
5317 def schedule_type(self):
5318 return self._schedule_type
5318 return self._schedule_type
5319
5319
5320 @schedule_type.setter
5320 @schedule_type.setter
5321 def schedule_type(self, val):
5321 def schedule_type(self, val):
5322 if val not in self.schedule_types:
5322 if val not in self.schedule_types:
5323 raise ValueError('Value must be on of `{}` and got `{}`'.format(
5323 raise ValueError('Value must be on of `{}` and got `{}`'.format(
5324 val, self.schedule_type))
5324 val, self.schedule_type))
5325
5325
5326 self._schedule_type = val
5326 self._schedule_type = val
5327
5327
5328 @classmethod
5328 @classmethod
5329 def get_uid(cls, obj):
5329 def get_uid(cls, obj):
5330 args = obj.task_args
5330 args = obj.task_args
5331 kwargs = obj.task_kwargs
5331 kwargs = obj.task_kwargs
5332 if isinstance(args, JsonRaw):
5332 if isinstance(args, JsonRaw):
5333 try:
5333 try:
5334 args = json.loads(args)
5334 args = json.loads(args)
5335 except ValueError:
5335 except ValueError:
5336 args = tuple()
5336 args = tuple()
5337
5337
5338 if isinstance(kwargs, JsonRaw):
5338 if isinstance(kwargs, JsonRaw):
5339 try:
5339 try:
5340 kwargs = json.loads(kwargs)
5340 kwargs = json.loads(kwargs)
5341 except ValueError:
5341 except ValueError:
5342 kwargs = dict()
5342 kwargs = dict()
5343
5343
5344 dot_notation = obj.task_dot_notation
5344 dot_notation = obj.task_dot_notation
5345 val = '.'.join(map(safe_str, [
5345 val = '.'.join(map(safe_str, [
5346 sorted(dot_notation), args, sorted(kwargs.items())]))
5346 sorted(dot_notation), args, sorted(kwargs.items())]))
5347 return sha1(safe_bytes(val))
5347 return sha1(safe_bytes(val))
5348
5348
5349 @classmethod
5349 @classmethod
5350 def get_by_schedule_name(cls, schedule_name):
5350 def get_by_schedule_name(cls, schedule_name):
5351 return cls.query().filter(cls.schedule_name == schedule_name).scalar()
5351 return cls.query().filter(cls.schedule_name == schedule_name).scalar()
5352
5352
5353 @classmethod
5353 @classmethod
5354 def get_by_schedule_id(cls, schedule_id):
5354 def get_by_schedule_id(cls, schedule_id):
5355 return cls.query().filter(cls.schedule_entry_id == schedule_id).scalar()
5355 return cls.query().filter(cls.schedule_entry_id == schedule_id).scalar()
5356
5356
5357 @property
5357 @property
5358 def task(self):
5358 def task(self):
5359 return self.task_dot_notation
5359 return self.task_dot_notation
5360
5360
5361 @property
5361 @property
5362 def schedule(self):
5362 def schedule(self):
5363 from rhodecode.lib.celerylib.utils import raw_2_schedule
5363 from rhodecode.lib.celerylib.utils import raw_2_schedule
5364 schedule = raw_2_schedule(self.schedule_definition, self.schedule_type)
5364 schedule = raw_2_schedule(self.schedule_definition, self.schedule_type)
5365 return schedule
5365 return schedule
5366
5366
5367 @property
5367 @property
5368 def args(self):
5368 def args(self):
5369 try:
5369 try:
5370 return list(self.task_args or [])
5370 return list(self.task_args or [])
5371 except ValueError:
5371 except ValueError:
5372 return list()
5372 return list()
5373
5373
5374 @property
5374 @property
5375 def kwargs(self):
5375 def kwargs(self):
5376 try:
5376 try:
5377 return dict(self.task_kwargs or {})
5377 return dict(self.task_kwargs or {})
5378 except ValueError:
5378 except ValueError:
5379 return dict()
5379 return dict()
5380
5380
5381 def _as_raw(self, val, indent=None):
5381 def _as_raw(self, val, indent=False):
5382 if hasattr(val, 'de_coerce'):
5382 if hasattr(val, 'de_coerce'):
5383 val = val.de_coerce()
5383 val = val.de_coerce()
5384 if val:
5384 if val:
5385 if indent:
5385 if indent:
5386 ext_json.formatted_json(val)
5386 val = ext_json.formatted_str_json(val)
5387 else:
5387 else:
5388 val = ext_json.json.dumps(val)
5388 val = ext_json.str_json(val)
5389
5389
5390 return val
5390 return val
5391
5391
5392 @property
5392 @property
5393 def schedule_definition_raw(self):
5393 def schedule_definition_raw(self):
5394 return self._as_raw(self.schedule_definition)
5394 return self._as_raw(self.schedule_definition)
5395
5395
5396 def args_raw(self, indent=None):
5396 def args_raw(self, indent=False):
5397 return self._as_raw(self.task_args, indent)
5397 return self._as_raw(self.task_args, indent)
5398
5398
5399 def kwargs_raw(self, indent=None):
5399 def kwargs_raw(self, indent=False):
5400 return self._as_raw(self.task_kwargs, indent)
5400 return self._as_raw(self.task_kwargs, indent)
5401
5401
5402 def __repr__(self):
5402 def __repr__(self):
5403 return f'<DB:ScheduleEntry({self.schedule_entry_id}:{self.schedule_name})>'
5403 return f'<DB:ScheduleEntry({self.schedule_entry_id}:{self.schedule_name})>'
5404
5404
5405
5405
5406 @event.listens_for(ScheduleEntry, 'before_update')
5406 @event.listens_for(ScheduleEntry, 'before_update')
5407 def update_task_uid(mapper, connection, target):
5407 def update_task_uid(mapper, connection, target):
5408 target.task_uid = ScheduleEntry.get_uid(target)
5408 target.task_uid = ScheduleEntry.get_uid(target)
5409
5409
5410
5410
5411 @event.listens_for(ScheduleEntry, 'before_insert')
5411 @event.listens_for(ScheduleEntry, 'before_insert')
5412 def set_task_uid(mapper, connection, target):
5412 def set_task_uid(mapper, connection, target):
5413 target.task_uid = ScheduleEntry.get_uid(target)
5413 target.task_uid = ScheduleEntry.get_uid(target)
5414
5414
5415
5415
5416 class _BaseBranchPerms(BaseModel):
5416 class _BaseBranchPerms(BaseModel):
5417 @classmethod
5417 @classmethod
5418 def compute_hash(cls, value):
5418 def compute_hash(cls, value):
5419 return sha1_safe(value)
5419 return sha1_safe(value)
5420
5420
5421 @hybrid_property
5421 @hybrid_property
5422 def branch_pattern(self):
5422 def branch_pattern(self):
5423 return self._branch_pattern or '*'
5423 return self._branch_pattern or '*'
5424
5424
5425 @hybrid_property
5425 @hybrid_property
5426 def branch_hash(self):
5426 def branch_hash(self):
5427 return self._branch_hash
5427 return self._branch_hash
5428
5428
5429 def _validate_glob(self, value):
5429 def _validate_glob(self, value):
5430 re.compile('^' + glob2re(value) + '$')
5430 re.compile('^' + glob2re(value) + '$')
5431
5431
5432 @branch_pattern.setter
5432 @branch_pattern.setter
5433 def branch_pattern(self, value):
5433 def branch_pattern(self, value):
5434 self._validate_glob(value)
5434 self._validate_glob(value)
5435 self._branch_pattern = value or '*'
5435 self._branch_pattern = value or '*'
5436 # set the Hash when setting the branch pattern
5436 # set the Hash when setting the branch pattern
5437 self._branch_hash = self.compute_hash(self._branch_pattern)
5437 self._branch_hash = self.compute_hash(self._branch_pattern)
5438
5438
5439 def matches(self, branch):
5439 def matches(self, branch):
5440 """
5440 """
5441 Check if this the branch matches entry
5441 Check if this the branch matches entry
5442
5442
5443 :param branch: branch name for the commit
5443 :param branch: branch name for the commit
5444 """
5444 """
5445
5445
5446 branch = branch or ''
5446 branch = branch or ''
5447
5447
5448 branch_matches = True
5448 branch_matches = True
5449 if branch:
5449 if branch:
5450 branch_regex = re.compile('^' + glob2re(self.branch_pattern) + '$')
5450 branch_regex = re.compile('^' + glob2re(self.branch_pattern) + '$')
5451 branch_matches = bool(branch_regex.search(branch))
5451 branch_matches = bool(branch_regex.search(branch))
5452
5452
5453 return branch_matches
5453 return branch_matches
5454
5454
5455
5455
5456 class UserToRepoBranchPermission(Base, _BaseBranchPerms):
5456 class UserToRepoBranchPermission(Base, _BaseBranchPerms):
5457 __tablename__ = 'user_to_repo_branch_permissions'
5457 __tablename__ = 'user_to_repo_branch_permissions'
5458 __table_args__ = (
5458 __table_args__ = (
5459 base_table_args
5459 base_table_args
5460 )
5460 )
5461
5461
5462 branch_rule_id = Column('branch_rule_id', Integer(), primary_key=True)
5462 branch_rule_id = Column('branch_rule_id', Integer(), primary_key=True)
5463
5463
5464 repository_id = Column('repository_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
5464 repository_id = Column('repository_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
5465 repo = relationship('Repository', back_populates='user_branch_perms')
5465 repo = relationship('Repository', back_populates='user_branch_perms')
5466
5466
5467 permission_id = Column('permission_id', Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
5467 permission_id = Column('permission_id', Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
5468 permission = relationship('Permission')
5468 permission = relationship('Permission')
5469
5469
5470 rule_to_perm_id = Column('rule_to_perm_id', Integer(), ForeignKey('repo_to_perm.repo_to_perm_id'), nullable=False, unique=None, default=None)
5470 rule_to_perm_id = Column('rule_to_perm_id', Integer(), ForeignKey('repo_to_perm.repo_to_perm_id'), nullable=False, unique=None, default=None)
5471 user_repo_to_perm = relationship('UserRepoToPerm', back_populates='branch_perm_entry')
5471 user_repo_to_perm = relationship('UserRepoToPerm', back_populates='branch_perm_entry')
5472
5472
5473 rule_order = Column('rule_order', Integer(), nullable=False)
5473 rule_order = Column('rule_order', Integer(), nullable=False)
5474 _branch_pattern = Column('branch_pattern', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), default='*') # glob
5474 _branch_pattern = Column('branch_pattern', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), default='*') # glob
5475 _branch_hash = Column('branch_hash', UnicodeText().with_variant(UnicodeText(2048), 'mysql'))
5475 _branch_hash = Column('branch_hash', UnicodeText().with_variant(UnicodeText(2048), 'mysql'))
5476
5476
5477 def __repr__(self):
5477 def __repr__(self):
5478 return f'<UserBranchPermission({self.user_repo_to_perm} => {self.branch_pattern!r})>'
5478 return f'<UserBranchPermission({self.user_repo_to_perm} => {self.branch_pattern!r})>'
5479
5479
5480
5480
5481 class UserGroupToRepoBranchPermission(Base, _BaseBranchPerms):
5481 class UserGroupToRepoBranchPermission(Base, _BaseBranchPerms):
5482 __tablename__ = 'user_group_to_repo_branch_permissions'
5482 __tablename__ = 'user_group_to_repo_branch_permissions'
5483 __table_args__ = (
5483 __table_args__ = (
5484 base_table_args
5484 base_table_args
5485 )
5485 )
5486
5486
5487 branch_rule_id = Column('branch_rule_id', Integer(), primary_key=True)
5487 branch_rule_id = Column('branch_rule_id', Integer(), primary_key=True)
5488
5488
5489 repository_id = Column('repository_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
5489 repository_id = Column('repository_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
5490 repo = relationship('Repository', back_populates='user_group_branch_perms')
5490 repo = relationship('Repository', back_populates='user_group_branch_perms')
5491
5491
5492 permission_id = Column('permission_id', Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
5492 permission_id = Column('permission_id', Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
5493 permission = relationship('Permission')
5493 permission = relationship('Permission')
5494
5494
5495 rule_to_perm_id = Column('rule_to_perm_id', Integer(), ForeignKey('users_group_repo_to_perm.users_group_to_perm_id'), nullable=False, unique=None, default=None)
5495 rule_to_perm_id = Column('rule_to_perm_id', Integer(), ForeignKey('users_group_repo_to_perm.users_group_to_perm_id'), nullable=False, unique=None, default=None)
5496 user_group_repo_to_perm = relationship('UserGroupRepoToPerm', back_populates='user_group_branch_perms')
5496 user_group_repo_to_perm = relationship('UserGroupRepoToPerm', back_populates='user_group_branch_perms')
5497
5497
5498 rule_order = Column('rule_order', Integer(), nullable=False)
5498 rule_order = Column('rule_order', Integer(), nullable=False)
5499 _branch_pattern = Column('branch_pattern', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), default='*') # glob
5499 _branch_pattern = Column('branch_pattern', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), default='*') # glob
5500 _branch_hash = Column('branch_hash', UnicodeText().with_variant(UnicodeText(2048), 'mysql'))
5500 _branch_hash = Column('branch_hash', UnicodeText().with_variant(UnicodeText(2048), 'mysql'))
5501
5501
5502 def __repr__(self):
5502 def __repr__(self):
5503 return f'<UserBranchPermission({self.user_group_repo_to_perm} => {self.branch_pattern!r})>'
5503 return f'<UserBranchPermission({self.user_group_repo_to_perm} => {self.branch_pattern!r})>'
5504
5504
5505
5505
5506 class UserBookmark(Base, BaseModel):
5506 class UserBookmark(Base, BaseModel):
5507 __tablename__ = 'user_bookmarks'
5507 __tablename__ = 'user_bookmarks'
5508 __table_args__ = (
5508 __table_args__ = (
5509 UniqueConstraint('user_id', 'bookmark_repo_id'),
5509 UniqueConstraint('user_id', 'bookmark_repo_id'),
5510 UniqueConstraint('user_id', 'bookmark_repo_group_id'),
5510 UniqueConstraint('user_id', 'bookmark_repo_group_id'),
5511 UniqueConstraint('user_id', 'bookmark_position'),
5511 UniqueConstraint('user_id', 'bookmark_position'),
5512 base_table_args
5512 base_table_args
5513 )
5513 )
5514
5514
5515 user_bookmark_id = Column("user_bookmark_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
5515 user_bookmark_id = Column("user_bookmark_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
5516 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
5516 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
5517 position = Column("bookmark_position", Integer(), nullable=False)
5517 position = Column("bookmark_position", Integer(), nullable=False)
5518 title = Column("bookmark_title", String(255), nullable=True, unique=None, default=None)
5518 title = Column("bookmark_title", String(255), nullable=True, unique=None, default=None)
5519 redirect_url = Column("bookmark_redirect_url", String(10240), nullable=True, unique=None, default=None)
5519 redirect_url = Column("bookmark_redirect_url", String(10240), nullable=True, unique=None, default=None)
5520 created_on = Column("created_on", DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
5520 created_on = Column("created_on", DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
5521
5521
5522 bookmark_repo_id = Column("bookmark_repo_id", Integer(), ForeignKey("repositories.repo_id"), nullable=True, unique=None, default=None)
5522 bookmark_repo_id = Column("bookmark_repo_id", Integer(), ForeignKey("repositories.repo_id"), nullable=True, unique=None, default=None)
5523 bookmark_repo_group_id = Column("bookmark_repo_group_id", Integer(), ForeignKey("groups.group_id"), nullable=True, unique=None, default=None)
5523 bookmark_repo_group_id = Column("bookmark_repo_group_id", Integer(), ForeignKey("groups.group_id"), nullable=True, unique=None, default=None)
5524
5524
5525 user = relationship("User")
5525 user = relationship("User")
5526
5526
5527 repository = relationship("Repository")
5527 repository = relationship("Repository")
5528 repository_group = relationship("RepoGroup")
5528 repository_group = relationship("RepoGroup")
5529
5529
5530 @classmethod
5530 @classmethod
5531 def get_by_position_for_user(cls, position, user_id):
5531 def get_by_position_for_user(cls, position, user_id):
5532 return cls.query() \
5532 return cls.query() \
5533 .filter(UserBookmark.user_id == user_id) \
5533 .filter(UserBookmark.user_id == user_id) \
5534 .filter(UserBookmark.position == position).scalar()
5534 .filter(UserBookmark.position == position).scalar()
5535
5535
5536 @classmethod
5536 @classmethod
5537 def get_bookmarks_for_user(cls, user_id, cache=True):
5537 def get_bookmarks_for_user(cls, user_id, cache=True):
5538 bookmarks = cls.query() \
5538 bookmarks = cls.query() \
5539 .filter(UserBookmark.user_id == user_id) \
5539 .filter(UserBookmark.user_id == user_id) \
5540 .options(joinedload(UserBookmark.repository)) \
5540 .options(joinedload(UserBookmark.repository)) \
5541 .options(joinedload(UserBookmark.repository_group)) \
5541 .options(joinedload(UserBookmark.repository_group)) \
5542 .order_by(UserBookmark.position.asc())
5542 .order_by(UserBookmark.position.asc())
5543
5543
5544 if cache:
5544 if cache:
5545 bookmarks = bookmarks.options(
5545 bookmarks = bookmarks.options(
5546 FromCache("sql_cache_short", "get_user_{}_bookmarks".format(user_id))
5546 FromCache("sql_cache_short", "get_user_{}_bookmarks".format(user_id))
5547 )
5547 )
5548
5548
5549 return bookmarks.all()
5549 return bookmarks.all()
5550
5550
5551 def __repr__(self):
5551 def __repr__(self):
5552 return f'<UserBookmark({self.position} @ {self.redirect_url!r})>'
5552 return f'<UserBookmark({self.position} @ {self.redirect_url!r})>'
5553
5553
5554
5554
5555 class FileStore(Base, BaseModel):
5555 class FileStore(Base, BaseModel):
5556 __tablename__ = 'file_store'
5556 __tablename__ = 'file_store'
5557 __table_args__ = (
5557 __table_args__ = (
5558 base_table_args
5558 base_table_args
5559 )
5559 )
5560
5560
5561 file_store_id = Column('file_store_id', Integer(), primary_key=True)
5561 file_store_id = Column('file_store_id', Integer(), primary_key=True)
5562 file_uid = Column('file_uid', String(1024), nullable=False)
5562 file_uid = Column('file_uid', String(1024), nullable=False)
5563 file_display_name = Column('file_display_name', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), nullable=True)
5563 file_display_name = Column('file_display_name', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), nullable=True)
5564 file_description = Column('file_description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'), nullable=True)
5564 file_description = Column('file_description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'), nullable=True)
5565 file_org_name = Column('file_org_name', UnicodeText().with_variant(UnicodeText(10240), 'mysql'), nullable=False)
5565 file_org_name = Column('file_org_name', UnicodeText().with_variant(UnicodeText(10240), 'mysql'), nullable=False)
5566
5566
5567 # sha256 hash
5567 # sha256 hash
5568 file_hash = Column('file_hash', String(512), nullable=False)
5568 file_hash = Column('file_hash', String(512), nullable=False)
5569 file_size = Column('file_size', BigInteger(), nullable=False)
5569 file_size = Column('file_size', BigInteger(), nullable=False)
5570
5570
5571 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
5571 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
5572 accessed_on = Column('accessed_on', DateTime(timezone=False), nullable=True)
5572 accessed_on = Column('accessed_on', DateTime(timezone=False), nullable=True)
5573 accessed_count = Column('accessed_count', Integer(), default=0)
5573 accessed_count = Column('accessed_count', Integer(), default=0)
5574
5574
5575 enabled = Column('enabled', Boolean(), nullable=False, default=True)
5575 enabled = Column('enabled', Boolean(), nullable=False, default=True)
5576
5576
5577 # if repo/repo_group reference is set, check for permissions
5577 # if repo/repo_group reference is set, check for permissions
5578 check_acl = Column('check_acl', Boolean(), nullable=False, default=True)
5578 check_acl = Column('check_acl', Boolean(), nullable=False, default=True)
5579
5579
5580 # hidden defines an attachment that should be hidden from showing in artifact listing
5580 # hidden defines an attachment that should be hidden from showing in artifact listing
5581 hidden = Column('hidden', Boolean(), nullable=False, default=False)
5581 hidden = Column('hidden', Boolean(), nullable=False, default=False)
5582
5582
5583 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
5583 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
5584 upload_user = relationship('User', lazy='joined', primaryjoin='User.user_id==FileStore.user_id', back_populates='artifacts')
5584 upload_user = relationship('User', lazy='joined', primaryjoin='User.user_id==FileStore.user_id', back_populates='artifacts')
5585
5585
5586 file_metadata = relationship('FileStoreMetadata', lazy='joined')
5586 file_metadata = relationship('FileStoreMetadata', lazy='joined')
5587
5587
5588 # scope limited to user, which requester have access to
5588 # scope limited to user, which requester have access to
5589 scope_user_id = Column(
5589 scope_user_id = Column(
5590 'scope_user_id', Integer(), ForeignKey('users.user_id'),
5590 'scope_user_id', Integer(), ForeignKey('users.user_id'),
5591 nullable=True, unique=None, default=None)
5591 nullable=True, unique=None, default=None)
5592 user = relationship('User', lazy='joined', primaryjoin='User.user_id==FileStore.scope_user_id', back_populates='scope_artifacts')
5592 user = relationship('User', lazy='joined', primaryjoin='User.user_id==FileStore.scope_user_id', back_populates='scope_artifacts')
5593
5593
5594 # scope limited to user group, which requester have access to
5594 # scope limited to user group, which requester have access to
5595 scope_user_group_id = Column(
5595 scope_user_group_id = Column(
5596 'scope_user_group_id', Integer(), ForeignKey('users_groups.users_group_id'),
5596 'scope_user_group_id', Integer(), ForeignKey('users_groups.users_group_id'),
5597 nullable=True, unique=None, default=None)
5597 nullable=True, unique=None, default=None)
5598 user_group = relationship('UserGroup', lazy='joined')
5598 user_group = relationship('UserGroup', lazy='joined')
5599
5599
5600 # scope limited to repo, which requester have access to
5600 # scope limited to repo, which requester have access to
5601 scope_repo_id = Column(
5601 scope_repo_id = Column(
5602 'scope_repo_id', Integer(), ForeignKey('repositories.repo_id'),
5602 'scope_repo_id', Integer(), ForeignKey('repositories.repo_id'),
5603 nullable=True, unique=None, default=None)
5603 nullable=True, unique=None, default=None)
5604 repo = relationship('Repository', lazy='joined')
5604 repo = relationship('Repository', lazy='joined')
5605
5605
5606 # scope limited to repo group, which requester have access to
5606 # scope limited to repo group, which requester have access to
5607 scope_repo_group_id = Column(
5607 scope_repo_group_id = Column(
5608 'scope_repo_group_id', Integer(), ForeignKey('groups.group_id'),
5608 'scope_repo_group_id', Integer(), ForeignKey('groups.group_id'),
5609 nullable=True, unique=None, default=None)
5609 nullable=True, unique=None, default=None)
5610 repo_group = relationship('RepoGroup', lazy='joined')
5610 repo_group = relationship('RepoGroup', lazy='joined')
5611
5611
5612 @classmethod
5612 @classmethod
5613 def get_scope(cls, scope_type, scope_id):
5614 if scope_type == 'repo':
5615 return f'repo:{scope_id}'
5616 elif scope_type == 'repo-group':
5617 return f'repo-group:{scope_id}'
5618 elif scope_type == 'user':
5619 return f'user:{scope_id}'
5620 elif scope_type == 'user-group':
5621 return f'user-group:{scope_id}'
5622 else:
5623 return scope_type
5624
5625 @classmethod
5613 def get_by_store_uid(cls, file_store_uid, safe=False):
5626 def get_by_store_uid(cls, file_store_uid, safe=False):
5614 if safe:
5627 if safe:
5615 return FileStore.query().filter(FileStore.file_uid == file_store_uid).first()
5628 return FileStore.query().filter(FileStore.file_uid == file_store_uid).first()
5616 else:
5629 else:
5617 return FileStore.query().filter(FileStore.file_uid == file_store_uid).scalar()
5630 return FileStore.query().filter(FileStore.file_uid == file_store_uid).scalar()
5618
5631
5619 @classmethod
5632 @classmethod
5620 def create(cls, file_uid, filename, file_hash, file_size, file_display_name='',
5633 def create(cls, file_uid, filename, file_hash, file_size, file_display_name='',
5621 file_description='', enabled=True, hidden=False, check_acl=True,
5634 file_description='', enabled=True, hidden=False, check_acl=True,
5622 user_id=None, scope_user_id=None, scope_repo_id=None, scope_repo_group_id=None):
5635 user_id=None, scope_user_id=None, scope_repo_id=None, scope_repo_group_id=None):
5623
5636
5624 store_entry = FileStore()
5637 store_entry = FileStore()
5625 store_entry.file_uid = file_uid
5638 store_entry.file_uid = file_uid
5626 store_entry.file_display_name = file_display_name
5639 store_entry.file_display_name = file_display_name
5627 store_entry.file_org_name = filename
5640 store_entry.file_org_name = filename
5628 store_entry.file_size = file_size
5641 store_entry.file_size = file_size
5629 store_entry.file_hash = file_hash
5642 store_entry.file_hash = file_hash
5630 store_entry.file_description = file_description
5643 store_entry.file_description = file_description
5631
5644
5632 store_entry.check_acl = check_acl
5645 store_entry.check_acl = check_acl
5633 store_entry.enabled = enabled
5646 store_entry.enabled = enabled
5634 store_entry.hidden = hidden
5647 store_entry.hidden = hidden
5635
5648
5636 store_entry.user_id = user_id
5649 store_entry.user_id = user_id
5637 store_entry.scope_user_id = scope_user_id
5650 store_entry.scope_user_id = scope_user_id
5638 store_entry.scope_repo_id = scope_repo_id
5651 store_entry.scope_repo_id = scope_repo_id
5639 store_entry.scope_repo_group_id = scope_repo_group_id
5652 store_entry.scope_repo_group_id = scope_repo_group_id
5640
5653
5641 return store_entry
5654 return store_entry
5642
5655
5643 @classmethod
5656 @classmethod
5644 def store_metadata(cls, file_store_id, args, commit=True):
5657 def store_metadata(cls, file_store_id, args, commit=True):
5645 file_store = FileStore.get(file_store_id)
5658 file_store = FileStore.get(file_store_id)
5646 if file_store is None:
5659 if file_store is None:
5647 return
5660 return
5648
5661
5649 for section, key, value, value_type in args:
5662 for section, key, value, value_type in args:
5650 has_key = FileStoreMetadata().query() \
5663 has_key = FileStoreMetadata().query() \
5651 .filter(FileStoreMetadata.file_store_id == file_store.file_store_id) \
5664 .filter(FileStoreMetadata.file_store_id == file_store.file_store_id) \
5652 .filter(FileStoreMetadata.file_store_meta_section == section) \
5665 .filter(FileStoreMetadata.file_store_meta_section == section) \
5653 .filter(FileStoreMetadata.file_store_meta_key == key) \
5666 .filter(FileStoreMetadata.file_store_meta_key == key) \
5654 .scalar()
5667 .scalar()
5655 if has_key:
5668 if has_key:
5656 msg = 'key `{}` already defined under section `{}` for this file.'\
5669 msg = 'key `{}` already defined under section `{}` for this file.'\
5657 .format(key, section)
5670 .format(key, section)
5658 raise ArtifactMetadataDuplicate(msg, err_section=section, err_key=key)
5671 raise ArtifactMetadataDuplicate(msg, err_section=section, err_key=key)
5659
5672
5660 # NOTE(marcink): raises ArtifactMetadataBadValueType
5673 # NOTE(marcink): raises ArtifactMetadataBadValueType
5661 FileStoreMetadata.valid_value_type(value_type)
5674 FileStoreMetadata.valid_value_type(value_type)
5662
5675
5663 meta_entry = FileStoreMetadata()
5676 meta_entry = FileStoreMetadata()
5664 meta_entry.file_store = file_store
5677 meta_entry.file_store = file_store
5665 meta_entry.file_store_meta_section = section
5678 meta_entry.file_store_meta_section = section
5666 meta_entry.file_store_meta_key = key
5679 meta_entry.file_store_meta_key = key
5667 meta_entry.file_store_meta_value_type = value_type
5680 meta_entry.file_store_meta_value_type = value_type
5668 meta_entry.file_store_meta_value = value
5681 meta_entry.file_store_meta_value = value
5669
5682
5670 Session().add(meta_entry)
5683 Session().add(meta_entry)
5671
5684
5672 try:
5685 try:
5673 if commit:
5686 if commit:
5674 Session().commit()
5687 Session().commit()
5675 except IntegrityError:
5688 except IntegrityError:
5676 Session().rollback()
5689 Session().rollback()
5677 raise ArtifactMetadataDuplicate('Duplicate section/key found for this file.')
5690 raise ArtifactMetadataDuplicate('Duplicate section/key found for this file.')
5678
5691
5679 @classmethod
5692 @classmethod
5680 def bump_access_counter(cls, file_uid, commit=True):
5693 def bump_access_counter(cls, file_uid, commit=True):
5681 FileStore().query()\
5694 FileStore().query()\
5682 .filter(FileStore.file_uid == file_uid)\
5695 .filter(FileStore.file_uid == file_uid)\
5683 .update({FileStore.accessed_count: (FileStore.accessed_count + 1),
5696 .update({FileStore.accessed_count: (FileStore.accessed_count + 1),
5684 FileStore.accessed_on: datetime.datetime.now()})
5697 FileStore.accessed_on: datetime.datetime.now()})
5685 if commit:
5698 if commit:
5686 Session().commit()
5699 Session().commit()
5687
5700
5688 def __json__(self):
5701 def __json__(self):
5689 data = {
5702 data = {
5690 'filename': self.file_display_name,
5703 'filename': self.file_display_name,
5691 'filename_org': self.file_org_name,
5704 'filename_org': self.file_org_name,
5692 'file_uid': self.file_uid,
5705 'file_uid': self.file_uid,
5693 'description': self.file_description,
5706 'description': self.file_description,
5694 'hidden': self.hidden,
5707 'hidden': self.hidden,
5695 'size': self.file_size,
5708 'size': self.file_size,
5696 'created_on': self.created_on,
5709 'created_on': self.created_on,
5697 'uploaded_by': self.upload_user.get_api_data(details='basic'),
5710 'uploaded_by': self.upload_user.get_api_data(details='basic'),
5698 'downloaded_times': self.accessed_count,
5711 'downloaded_times': self.accessed_count,
5699 'sha256': self.file_hash,
5712 'sha256': self.file_hash,
5700 'metadata': self.file_metadata,
5713 'metadata': self.file_metadata,
5701 }
5714 }
5702
5715
5703 return data
5716 return data
5704
5717
5705 def __repr__(self):
5718 def __repr__(self):
5706 return f'<FileStore({self.file_store_id})>'
5719 return f'<FileStore({self.file_store_id})>'
5707
5720
5708
5721
5709 class FileStoreMetadata(Base, BaseModel):
5722 class FileStoreMetadata(Base, BaseModel):
5710 __tablename__ = 'file_store_metadata'
5723 __tablename__ = 'file_store_metadata'
5711 __table_args__ = (
5724 __table_args__ = (
5712 UniqueConstraint('file_store_id', 'file_store_meta_section_hash', 'file_store_meta_key_hash'),
5725 UniqueConstraint('file_store_id', 'file_store_meta_section_hash', 'file_store_meta_key_hash'),
5713 Index('file_store_meta_section_idx', 'file_store_meta_section', mysql_length=255),
5726 Index('file_store_meta_section_idx', 'file_store_meta_section', mysql_length=255),
5714 Index('file_store_meta_key_idx', 'file_store_meta_key', mysql_length=255),
5727 Index('file_store_meta_key_idx', 'file_store_meta_key', mysql_length=255),
5715 base_table_args
5728 base_table_args
5716 )
5729 )
5717 SETTINGS_TYPES = {
5730 SETTINGS_TYPES = {
5718 'str': safe_str,
5731 'str': safe_str,
5719 'int': safe_int,
5732 'int': safe_int,
5720 'unicode': safe_str,
5733 'unicode': safe_str,
5721 'bool': str2bool,
5734 'bool': str2bool,
5722 'list': functools.partial(aslist, sep=',')
5735 'list': functools.partial(aslist, sep=',')
5723 }
5736 }
5724
5737
5725 file_store_meta_id = Column(
5738 file_store_meta_id = Column(
5726 "file_store_meta_id", Integer(), nullable=False, unique=True, default=None,
5739 "file_store_meta_id", Integer(), nullable=False, unique=True, default=None,
5727 primary_key=True)
5740 primary_key=True)
5728 _file_store_meta_section = Column(
5741 _file_store_meta_section = Column(
5729 "file_store_meta_section", UnicodeText().with_variant(UnicodeText(1024), 'mysql'),
5742 "file_store_meta_section", UnicodeText().with_variant(UnicodeText(1024), 'mysql'),
5730 nullable=True, unique=None, default=None)
5743 nullable=True, unique=None, default=None)
5731 _file_store_meta_section_hash = Column(
5744 _file_store_meta_section_hash = Column(
5732 "file_store_meta_section_hash", String(255),
5745 "file_store_meta_section_hash", String(255),
5733 nullable=True, unique=None, default=None)
5746 nullable=True, unique=None, default=None)
5734 _file_store_meta_key = Column(
5747 _file_store_meta_key = Column(
5735 "file_store_meta_key", UnicodeText().with_variant(UnicodeText(1024), 'mysql'),
5748 "file_store_meta_key", UnicodeText().with_variant(UnicodeText(1024), 'mysql'),
5736 nullable=True, unique=None, default=None)
5749 nullable=True, unique=None, default=None)
5737 _file_store_meta_key_hash = Column(
5750 _file_store_meta_key_hash = Column(
5738 "file_store_meta_key_hash", String(255), nullable=True, unique=None, default=None)
5751 "file_store_meta_key_hash", String(255), nullable=True, unique=None, default=None)
5739 _file_store_meta_value = Column(
5752 _file_store_meta_value = Column(
5740 "file_store_meta_value", UnicodeText().with_variant(UnicodeText(20480), 'mysql'),
5753 "file_store_meta_value", UnicodeText().with_variant(UnicodeText(20480), 'mysql'),
5741 nullable=True, unique=None, default=None)
5754 nullable=True, unique=None, default=None)
5742 _file_store_meta_value_type = Column(
5755 _file_store_meta_value_type = Column(
5743 "file_store_meta_value_type", String(255), nullable=True, unique=None,
5756 "file_store_meta_value_type", String(255), nullable=True, unique=None,
5744 default='unicode')
5757 default='unicode')
5745
5758
5746 file_store_id = Column(
5759 file_store_id = Column(
5747 'file_store_id', Integer(), ForeignKey('file_store.file_store_id'),
5760 'file_store_id', Integer(), ForeignKey('file_store.file_store_id'),
5748 nullable=True, unique=None, default=None)
5761 nullable=True, unique=None, default=None)
5749
5762
5750 file_store = relationship('FileStore', lazy='joined', viewonly=True)
5763 file_store = relationship('FileStore', lazy='joined', viewonly=True)
5751
5764
5752 @classmethod
5765 @classmethod
5753 def valid_value_type(cls, value):
5766 def valid_value_type(cls, value):
5754 if value.split('.')[0] not in cls.SETTINGS_TYPES:
5767 if value.split('.')[0] not in cls.SETTINGS_TYPES:
5755 raise ArtifactMetadataBadValueType(
5768 raise ArtifactMetadataBadValueType(
5756 'value_type must be one of %s got %s' % (cls.SETTINGS_TYPES.keys(), value))
5769 'value_type must be one of %s got %s' % (cls.SETTINGS_TYPES.keys(), value))
5757
5770
5758 @hybrid_property
5771 @hybrid_property
5759 def file_store_meta_section(self):
5772 def file_store_meta_section(self):
5760 return self._file_store_meta_section
5773 return self._file_store_meta_section
5761
5774
5762 @file_store_meta_section.setter
5775 @file_store_meta_section.setter
5763 def file_store_meta_section(self, value):
5776 def file_store_meta_section(self, value):
5764 self._file_store_meta_section = value
5777 self._file_store_meta_section = value
5765 self._file_store_meta_section_hash = _hash_key(value)
5778 self._file_store_meta_section_hash = _hash_key(value)
5766
5779
5767 @hybrid_property
5780 @hybrid_property
5768 def file_store_meta_key(self):
5781 def file_store_meta_key(self):
5769 return self._file_store_meta_key
5782 return self._file_store_meta_key
5770
5783
5771 @file_store_meta_key.setter
5784 @file_store_meta_key.setter
5772 def file_store_meta_key(self, value):
5785 def file_store_meta_key(self, value):
5773 self._file_store_meta_key = value
5786 self._file_store_meta_key = value
5774 self._file_store_meta_key_hash = _hash_key(value)
5787 self._file_store_meta_key_hash = _hash_key(value)
5775
5788
5776 @hybrid_property
5789 @hybrid_property
5777 def file_store_meta_value(self):
5790 def file_store_meta_value(self):
5778 val = self._file_store_meta_value
5791 val = self._file_store_meta_value
5779
5792
5780 if self._file_store_meta_value_type:
5793 if self._file_store_meta_value_type:
5781 # e.g unicode.encrypted == unicode
5794 # e.g unicode.encrypted == unicode
5782 _type = self._file_store_meta_value_type.split('.')[0]
5795 _type = self._file_store_meta_value_type.split('.')[0]
5783 # decode the encrypted value if it's encrypted field type
5796 # decode the encrypted value if it's encrypted field type
5784 if '.encrypted' in self._file_store_meta_value_type:
5797 if '.encrypted' in self._file_store_meta_value_type:
5785 cipher = EncryptedTextValue()
5798 cipher = EncryptedTextValue()
5786 val = safe_str(cipher.process_result_value(val, None))
5799 val = safe_str(cipher.process_result_value(val, None))
5787 # do final type conversion
5800 # do final type conversion
5788 converter = self.SETTINGS_TYPES.get(_type) or self.SETTINGS_TYPES['unicode']
5801 converter = self.SETTINGS_TYPES.get(_type) or self.SETTINGS_TYPES['unicode']
5789 val = converter(val)
5802 val = converter(val)
5790
5803
5791 return val
5804 return val
5792
5805
5793 @file_store_meta_value.setter
5806 @file_store_meta_value.setter
5794 def file_store_meta_value(self, val):
5807 def file_store_meta_value(self, val):
5795 val = safe_str(val)
5808 val = safe_str(val)
5796 # encode the encrypted value
5809 # encode the encrypted value
5797 if '.encrypted' in self.file_store_meta_value_type:
5810 if '.encrypted' in self.file_store_meta_value_type:
5798 cipher = EncryptedTextValue()
5811 cipher = EncryptedTextValue()
5799 val = safe_str(cipher.process_bind_param(val, None))
5812 val = safe_str(cipher.process_bind_param(val, None))
5800 self._file_store_meta_value = val
5813 self._file_store_meta_value = val
5801
5814
5802 @hybrid_property
5815 @hybrid_property
5803 def file_store_meta_value_type(self):
5816 def file_store_meta_value_type(self):
5804 return self._file_store_meta_value_type
5817 return self._file_store_meta_value_type
5805
5818
5806 @file_store_meta_value_type.setter
5819 @file_store_meta_value_type.setter
5807 def file_store_meta_value_type(self, val):
5820 def file_store_meta_value_type(self, val):
5808 # e.g unicode.encrypted
5821 # e.g unicode.encrypted
5809 self.valid_value_type(val)
5822 self.valid_value_type(val)
5810 self._file_store_meta_value_type = val
5823 self._file_store_meta_value_type = val
5811
5824
5812 def __json__(self):
5825 def __json__(self):
5813 data = {
5826 data = {
5814 'artifact': self.file_store.file_uid,
5827 'artifact': self.file_store.file_uid,
5815 'section': self.file_store_meta_section,
5828 'section': self.file_store_meta_section,
5816 'key': self.file_store_meta_key,
5829 'key': self.file_store_meta_key,
5817 'value': self.file_store_meta_value,
5830 'value': self.file_store_meta_value,
5818 }
5831 }
5819
5832
5820 return data
5833 return data
5821
5834
5822 def __repr__(self):
5835 def __repr__(self):
5823 return '<%s[%s]%s=>%s]>' % (self.cls_name, self.file_store_meta_section,
5836 return '<%s[%s]%s=>%s]>' % (self.cls_name, self.file_store_meta_section,
5824 self.file_store_meta_key, self.file_store_meta_value)
5837 self.file_store_meta_key, self.file_store_meta_value)
5825
5838
5826
5839
5827 class DbMigrateVersion(Base, BaseModel):
5840 class DbMigrateVersion(Base, BaseModel):
5828 __tablename__ = 'db_migrate_version'
5841 __tablename__ = 'db_migrate_version'
5829 __table_args__ = (
5842 __table_args__ = (
5830 base_table_args,
5843 base_table_args,
5831 )
5844 )
5832
5845
5833 repository_id = Column('repository_id', String(250), primary_key=True)
5846 repository_id = Column('repository_id', String(250), primary_key=True)
5834 repository_path = Column('repository_path', Text)
5847 repository_path = Column('repository_path', Text)
5835 version = Column('version', Integer)
5848 version = Column('version', Integer)
5836
5849
5837 @classmethod
5850 @classmethod
5838 def set_version(cls, version):
5851 def set_version(cls, version):
5839 """
5852 """
5840 Helper for forcing a different version, usually for debugging purposes via ishell.
5853 Helper for forcing a different version, usually for debugging purposes via ishell.
5841 """
5854 """
5842 ver = DbMigrateVersion.query().first()
5855 ver = DbMigrateVersion.query().first()
5843 ver.version = version
5856 ver.version = version
5844 Session().commit()
5857 Session().commit()
5845
5858
5846
5859
5847 class DbSession(Base, BaseModel):
5860 class DbSession(Base, BaseModel):
5848 __tablename__ = 'db_session'
5861 __tablename__ = 'db_session'
5849 __table_args__ = (
5862 __table_args__ = (
5850 base_table_args,
5863 base_table_args,
5851 )
5864 )
5852
5865
5853 def __repr__(self):
5866 def __repr__(self):
5854 return f'<DB:DbSession({self.id})>'
5867 return f'<DB:DbSession({self.id})>'
5855
5868
5856 id = Column('id', Integer())
5869 id = Column('id', Integer())
5857 namespace = Column('namespace', String(255), primary_key=True)
5870 namespace = Column('namespace', String(255), primary_key=True)
5858 accessed = Column('accessed', DateTime, nullable=False)
5871 accessed = Column('accessed', DateTime, nullable=False)
5859 created = Column('created', DateTime, nullable=False)
5872 created = Column('created', DateTime, nullable=False)
5860 data = Column('data', PickleType, nullable=False)
5873 data = Column('data', PickleType, nullable=False)
@@ -1,159 +1,160 b''
1 # Copyright (C) 2011-2023 RhodeCode GmbH
1 # Copyright (C) 2011-2023 RhodeCode GmbH
2 #
2 #
3 # This program is free software: you can redistribute it and/or modify
3 # This program is free software: you can redistribute it and/or modify
4 # it under the terms of the GNU Affero General Public License, version 3
4 # it under the terms of the GNU Affero General Public License, version 3
5 # (only), as published by the Free Software Foundation.
5 # (only), as published by the Free Software Foundation.
6 #
6 #
7 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # GNU General Public License for more details.
10 # GNU General Public License for more details.
11 #
11 #
12 # You should have received a copy of the GNU Affero General Public License
12 # You should have received a copy of the GNU Affero General Public License
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 #
14 #
15 # This program is dual-licensed. If you wish to learn more about the
15 # This program is dual-licensed. If you wish to learn more about the
16 # RhodeCode Enterprise Edition, including its added features, Support services,
16 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
18
18
19 import re
19 import re
20 import logging
20 import logging
21
21
22 import ipaddress
22 import ipaddress
23 import colander
23 import colander
24
24
25 from rhodecode.translation import _
25 from rhodecode.translation import _
26 from rhodecode.lib.utils2 import glob2re
26 from rhodecode.lib.utils2 import glob2re
27 from rhodecode.lib.str_utils import safe_str
27 from rhodecode.lib.str_utils import safe_str
28 from rhodecode.lib.ext_json import json
28 from rhodecode.lib.ext_json import json, sjson
29
29
30 log = logging.getLogger(__name__)
30 log = logging.getLogger(__name__)
31
31
32
32
33 def ip_addr_validator(node, value):
33 def ip_addr_validator(node, value):
34 try:
34 try:
35 # this raises an ValueError if address is not IpV4 or IpV6
35 # this raises an ValueError if address is not IpV4 or IpV6
36 ipaddress.ip_network(safe_str(value), strict=False)
36 ipaddress.ip_network(safe_str(value), strict=False)
37 except ValueError:
37 except ValueError:
38 msg = _('Please enter a valid IPv4 or IpV6 address')
38 msg = _('Please enter a valid IPv4 or IpV6 address')
39 raise colander.Invalid(node, msg)
39 raise colander.Invalid(node, msg)
40
40
41
41
42 class IpAddrValidator(object):
42 class IpAddrValidator(object):
43 def __init__(self, strict=True):
43 def __init__(self, strict=True):
44 self.strict = strict
44 self.strict = strict
45
45
46 def __call__(self, node, value):
46 def __call__(self, node, value):
47 try:
47 try:
48 # this raises an ValueError if address is not IpV4 or IpV6
48 # this raises an ValueError if address is not IpV4 or IpV6
49 ipaddress.ip_network(safe_str(value), strict=self.strict)
49 ipaddress.ip_network(safe_str(value), strict=self.strict)
50 except ValueError:
50 except ValueError:
51 msg = _('Please enter a valid IPv4 or IpV6 address')
51 msg = _('Please enter a valid IPv4 or IpV6 address')
52 raise colander.Invalid(node, msg)
52 raise colander.Invalid(node, msg)
53
53
54
54
55 def glob_validator(node, value):
55 def glob_validator(node, value):
56 try:
56 try:
57 re.compile('^' + glob2re(value) + '$')
57 re.compile('^' + glob2re(value) + '$')
58 except Exception:
58 except Exception:
59 msg = _('Invalid glob pattern')
59 msg = _('Invalid glob pattern')
60 raise colander.Invalid(node, msg)
60 raise colander.Invalid(node, msg)
61
61
62
62
63 def valid_name_validator(node, value):
63 def valid_name_validator(node, value):
64 from rhodecode.model.validation_schema import types
64 from rhodecode.model.validation_schema import types
65 if value is types.RootLocation:
65 if value is types.RootLocation:
66 return
66 return
67
67
68 msg = _('Name must start with a letter or number. Got `{}`').format(value)
68 msg = _('Name must start with a letter or number. Got `{}`').format(value)
69 if not re.match(r'^[a-zA-z0-9]{1,}', value):
69 if not re.match(r'^[a-zA-z0-9]{1,}', value):
70 raise colander.Invalid(node, msg)
70 raise colander.Invalid(node, msg)
71
71
72
72
73 class InvalidCloneUrl(Exception):
73 class InvalidCloneUrl(Exception):
74 allowed_prefixes = ()
74 allowed_prefixes = ()
75
75
76
76
77 def url_validator(url, repo_type, config):
77 def url_validator(url, repo_type, config):
78 from rhodecode.lib.vcs.backends.hg import MercurialRepository
78 from rhodecode.lib.vcs.backends.hg import MercurialRepository
79 from rhodecode.lib.vcs.backends.git import GitRepository
79 from rhodecode.lib.vcs.backends.git import GitRepository
80 from rhodecode.lib.vcs.backends.svn import SubversionRepository
80 from rhodecode.lib.vcs.backends.svn import SubversionRepository
81
81
82 if repo_type == 'hg':
82 if repo_type == 'hg':
83 allowed_prefixes = ('http', 'svn+http', 'git+http')
83 allowed_prefixes = ('http', 'svn+http', 'git+http')
84
84
85 if 'http' in url[:4]:
85 if 'http' in url[:4]:
86 # initially check if it's at least the proper URL
86 # initially check if it's at least the proper URL
87 # or does it pass basic auth
87 # or does it pass basic auth
88
88
89 return MercurialRepository.check_url(url, config)
89 return MercurialRepository.check_url(url, config)
90 elif 'svn+http' in url[:8]: # svn->hg import
90 elif 'svn+http' in url[:8]: # svn->hg import
91 SubversionRepository.check_url(url, config)
91 SubversionRepository.check_url(url, config)
92 elif 'git+http' in url[:8]: # git->hg import
92 elif 'git+http' in url[:8]: # git->hg import
93 raise NotImplementedError()
93 raise NotImplementedError()
94 else:
94 else:
95 exc = InvalidCloneUrl('Clone from URI %s not allowed. '
95 exc = InvalidCloneUrl('Clone from URI %s not allowed. '
96 'Allowed url must start with one of %s'
96 'Allowed url must start with one of %s'
97 % (url, ','.join(allowed_prefixes)))
97 % (url, ','.join(allowed_prefixes)))
98 exc.allowed_prefixes = allowed_prefixes
98 exc.allowed_prefixes = allowed_prefixes
99 raise exc
99 raise exc
100
100
101 elif repo_type == 'git':
101 elif repo_type == 'git':
102 allowed_prefixes = ('http', 'svn+http', 'hg+http')
102 allowed_prefixes = ('http', 'svn+http', 'hg+http')
103 if 'http' in url[:4]:
103 if 'http' in url[:4]:
104 # initially check if it's at least the proper URL
104 # initially check if it's at least the proper URL
105 # or does it pass basic auth
105 # or does it pass basic auth
106 return GitRepository.check_url(url, config)
106 return GitRepository.check_url(url, config)
107 elif 'svn+http' in url[:8]: # svn->git import
107 elif 'svn+http' in url[:8]: # svn->git import
108 raise NotImplementedError()
108 raise NotImplementedError()
109 elif 'hg+http' in url[:8]: # hg->git import
109 elif 'hg+http' in url[:8]: # hg->git import
110 raise NotImplementedError()
110 raise NotImplementedError()
111 else:
111 else:
112 exc = InvalidCloneUrl('Clone from URI %s not allowed. '
112 exc = InvalidCloneUrl('Clone from URI %s not allowed. '
113 'Allowed url must start with one of %s'
113 'Allowed url must start with one of %s'
114 % (url, ','.join(allowed_prefixes)))
114 % (url, ','.join(allowed_prefixes)))
115 exc.allowed_prefixes = allowed_prefixes
115 exc.allowed_prefixes = allowed_prefixes
116 raise exc
116 raise exc
117 elif repo_type == 'svn':
117 elif repo_type == 'svn':
118 # no validation for SVN yet
118 # no validation for SVN yet
119 return
119 return
120
120
121 raise InvalidCloneUrl(f'Invalid repo type specified: `{repo_type}`')
121 raise InvalidCloneUrl(f'Invalid repo type specified: `{repo_type}`')
122
122
123
123
124 class CloneUriValidator(object):
124 class CloneUriValidator(object):
125 def __init__(self, repo_type):
125 def __init__(self, repo_type):
126 self.repo_type = repo_type
126 self.repo_type = repo_type
127
127
128 def __call__(self, node, value):
128 def __call__(self, node, value):
129
129
130 from rhodecode.lib.utils import make_db_config
130 from rhodecode.lib.utils import make_db_config
131 try:
131 try:
132 config = make_db_config(clear_session=False)
132 config = make_db_config(clear_session=False)
133 url_validator(value, self.repo_type, config)
133 url_validator(value, self.repo_type, config)
134 except InvalidCloneUrl as e:
134 except InvalidCloneUrl as e:
135 log.warning(e)
135 log.warning(e)
136 raise colander.Invalid(node, str(e))
136 raise colander.Invalid(node, str(e))
137 except Exception as e:
137 except Exception as e:
138 log.exception('Url validation failed')
138 log.exception('Url validation failed')
139 reason = repr(e)
139 reason = repr(e)
140 reason = reason.replace('<', '&lt;').replace('>', '&gt;')
140 reason = reason.replace('<', '&lt;').replace('>', '&gt;')
141 msg = _('invalid clone url or credentials for {repo_type} repository. Reason: {reason}')\
141 msg = _('invalid clone url or credentials for {repo_type} repository. Reason: {reason}')\
142 .format(reason=reason, repo_type=self.repo_type)
142 .format(reason=reason, repo_type=self.repo_type)
143 raise colander.Invalid(node, msg)
143 raise colander.Invalid(node, msg)
144
144
145
145
146 def json_validator(node, value):
146 def json_validator(node, value):
147 try:
147 try:
148 json.loads(value)
148 json.loads(value)
149 except (Exception,):
149 except (Exception,):
150 msg = _('Please enter a valid json object')
150 msg = _('Please enter a valid json object')
151 raise colander.Invalid(node, msg)
151 raise colander.Invalid(node, msg)
152
152
153
153
154 def json_validator_with_exc(node, value):
154 def json_validator_with_exc(node, value):
155
155 try:
156 try:
156 json.loads(value)
157 json.loads(value)
157 except (Exception,) as e:
158 except (Exception,) as e:
158 msg = _(f'Please enter a valid json object: `{e}`')
159 msg = _(f'Please enter a valid json object type={type(value)}: `{e}`')
159 raise colander.Invalid(node, msg)
160 raise colander.Invalid(node, msg)
@@ -1,412 +1,412 b''
1
1
2 /******************************************************************************
2 /******************************************************************************
3 * *
3 * *
4 * DO NOT CHANGE THIS FILE MANUALLY *
4 * DO NOT CHANGE THIS FILE MANUALLY *
5 * *
5 * *
6 * *
6 * *
7 * This file is automatically generated when the app starts up with *
7 * This file is automatically generated when the app starts up with *
8 * generate_js_files = true *
8 * generate_js_files = true *
9 * *
9 * *
10 * To add a route here pass jsroute=True to the route definition in the app *
10 * To add a route here pass jsroute=True to the route definition in the app *
11 * *
11 * *
12 ******************************************************************************/
12 ******************************************************************************/
13 function registerRCRoutes() {
13 function registerRCRoutes() {
14 // routes registration
14 // routes registration
15 pyroutes.register('admin_artifacts', '/_admin/artifacts', []);
15 pyroutes.register('admin_artifacts', '/_admin/artifacts', []);
16 pyroutes.register('admin_artifacts_data', '/_admin/artifacts-data', []);
16 pyroutes.register('admin_artifacts_data', '/_admin/artifacts-data', []);
17 pyroutes.register('admin_artifacts_delete', '/_admin/artifacts/%(uid)s/delete', ['uid']);
17 pyroutes.register('admin_artifacts_delete', '/_admin/artifacts/%(uid)s/delete', ['uid']);
18 pyroutes.register('admin_artifacts_show_all', '/_admin/artifacts', []);
18 pyroutes.register('admin_artifacts_show_all', '/_admin/artifacts', []);
19 pyroutes.register('admin_artifacts_show_info', '/_admin/artifacts/%(uid)s', ['uid']);
19 pyroutes.register('admin_artifacts_show_info', '/_admin/artifacts/%(uid)s', ['uid']);
20 pyroutes.register('admin_artifacts_update', '/_admin/artifacts/%(uid)s/update', ['uid']);
20 pyroutes.register('admin_artifacts_update', '/_admin/artifacts/%(uid)s/update', ['uid']);
21 pyroutes.register('admin_audit_log_entry', '/_admin/audit_logs/%(audit_log_id)s', ['audit_log_id']);
21 pyroutes.register('admin_audit_log_entry', '/_admin/audit_logs/%(audit_log_id)s', ['audit_log_id']);
22 pyroutes.register('admin_audit_logs', '/_admin/audit_logs', []);
22 pyroutes.register('admin_audit_logs', '/_admin/audit_logs', []);
23 pyroutes.register('admin_automation', '/_admin/automation', []);
24 pyroutes.register('admin_automation_update', '/_admin/automation/%(entry_id)s/update', ['entry_id']);
23 pyroutes.register('admin_defaults_repositories', '/_admin/defaults/repositories', []);
25 pyroutes.register('admin_defaults_repositories', '/_admin/defaults/repositories', []);
24 pyroutes.register('admin_defaults_repositories_update', '/_admin/defaults/repositories/update', []);
26 pyroutes.register('admin_defaults_repositories_update', '/_admin/defaults/repositories/update', []);
25 pyroutes.register('admin_home', '/_admin', []);
27 pyroutes.register('admin_home', '/_admin', []);
26 pyroutes.register('admin_permissions_application', '/_admin/permissions/application', []);
28 pyroutes.register('admin_permissions_application', '/_admin/permissions/application', []);
27 pyroutes.register('admin_permissions_application_update', '/_admin/permissions/application/update', []);
29 pyroutes.register('admin_permissions_application_update', '/_admin/permissions/application/update', []);
28 pyroutes.register('admin_permissions_auth_token_access', '/_admin/permissions/auth_token_access', []);
30 pyroutes.register('admin_permissions_auth_token_access', '/_admin/permissions/auth_token_access', []);
29 pyroutes.register('admin_permissions_branch', '/_admin/permissions/branch', []);
31 pyroutes.register('admin_permissions_branch', '/_admin/permissions/branch', []);
30 pyroutes.register('admin_permissions_branch_update', '/_admin/permissions/branch/update', []);
32 pyroutes.register('admin_permissions_branch_update', '/_admin/permissions/branch/update', []);
31 pyroutes.register('admin_permissions_global', '/_admin/permissions/global', []);
33 pyroutes.register('admin_permissions_global', '/_admin/permissions/global', []);
32 pyroutes.register('admin_permissions_global_update', '/_admin/permissions/global/update', []);
34 pyroutes.register('admin_permissions_global_update', '/_admin/permissions/global/update', []);
33 pyroutes.register('admin_permissions_ips', '/_admin/permissions/ips', []);
35 pyroutes.register('admin_permissions_ips', '/_admin/permissions/ips', []);
34 pyroutes.register('admin_permissions_object', '/_admin/permissions/object', []);
36 pyroutes.register('admin_permissions_object', '/_admin/permissions/object', []);
35 pyroutes.register('admin_permissions_object_update', '/_admin/permissions/object/update', []);
37 pyroutes.register('admin_permissions_object_update', '/_admin/permissions/object/update', []);
36 pyroutes.register('admin_permissions_overview', '/_admin/permissions/overview', []);
38 pyroutes.register('admin_permissions_overview', '/_admin/permissions/overview', []);
37 pyroutes.register('admin_permissions_ssh_keys', '/_admin/permissions/ssh_keys', []);
39 pyroutes.register('admin_permissions_ssh_keys', '/_admin/permissions/ssh_keys', []);
38 pyroutes.register('admin_permissions_ssh_keys_data', '/_admin/permissions/ssh_keys/data', []);
40 pyroutes.register('admin_permissions_ssh_keys_data', '/_admin/permissions/ssh_keys/data', []);
39 pyroutes.register('admin_permissions_ssh_keys_update', '/_admin/permissions/ssh_keys/update', []);
41 pyroutes.register('admin_permissions_ssh_keys_update', '/_admin/permissions/ssh_keys/update', []);
42 pyroutes.register('admin_scheduler', '/_admin/scheduler', []);
43 pyroutes.register('admin_scheduler_show_tasks', '/_admin/scheduler/_tasks', []);
40 pyroutes.register('admin_settings', '/_admin/settings', []);
44 pyroutes.register('admin_settings', '/_admin/settings', []);
41 pyroutes.register('admin_settings_automation', '/_admin/settings/automation', []);
42 pyroutes.register('admin_settings_automation_update', '/_admin/settings/automation/%(entry_id)s/update', ['entry_id']);
43 pyroutes.register('admin_settings_email', '/_admin/settings/email', []);
45 pyroutes.register('admin_settings_email', '/_admin/settings/email', []);
44 pyroutes.register('admin_settings_email_update', '/_admin/settings/email/update', []);
46 pyroutes.register('admin_settings_email_update', '/_admin/settings/email/update', []);
45 pyroutes.register('admin_settings_exception_tracker', '/_admin/settings/exceptions', []);
47 pyroutes.register('admin_settings_exception_tracker', '/_admin/settings/exceptions', []);
46 pyroutes.register('admin_settings_exception_tracker_delete', '/_admin/settings/exceptions/%(exception_id)s/delete', ['exception_id']);
48 pyroutes.register('admin_settings_exception_tracker_delete', '/_admin/settings/exceptions/%(exception_id)s/delete', ['exception_id']);
47 pyroutes.register('admin_settings_exception_tracker_delete_all', '/_admin/settings/exceptions_delete_all', []);
49 pyroutes.register('admin_settings_exception_tracker_delete_all', '/_admin/settings/exceptions_delete_all', []);
48 pyroutes.register('admin_settings_exception_tracker_show', '/_admin/settings/exceptions/%(exception_id)s', ['exception_id']);
50 pyroutes.register('admin_settings_exception_tracker_show', '/_admin/settings/exceptions/%(exception_id)s', ['exception_id']);
49 pyroutes.register('admin_settings_global', '/_admin/settings/global', []);
51 pyroutes.register('admin_settings_global', '/_admin/settings/global', []);
50 pyroutes.register('admin_settings_global_update', '/_admin/settings/global/update', []);
52 pyroutes.register('admin_settings_global_update', '/_admin/settings/global/update', []);
51 pyroutes.register('admin_settings_hooks', '/_admin/settings/hooks', []);
53 pyroutes.register('admin_settings_hooks', '/_admin/settings/hooks', []);
52 pyroutes.register('admin_settings_hooks_delete', '/_admin/settings/hooks/delete', []);
54 pyroutes.register('admin_settings_hooks_delete', '/_admin/settings/hooks/delete', []);
53 pyroutes.register('admin_settings_hooks_update', '/_admin/settings/hooks/update', []);
55 pyroutes.register('admin_settings_hooks_update', '/_admin/settings/hooks/update', []);
54 pyroutes.register('admin_settings_issuetracker', '/_admin/settings/issue-tracker', []);
56 pyroutes.register('admin_settings_issuetracker', '/_admin/settings/issue-tracker', []);
55 pyroutes.register('admin_settings_issuetracker_delete', '/_admin/settings/issue-tracker/delete', []);
57 pyroutes.register('admin_settings_issuetracker_delete', '/_admin/settings/issue-tracker/delete', []);
56 pyroutes.register('admin_settings_issuetracker_test', '/_admin/settings/issue-tracker/test', []);
58 pyroutes.register('admin_settings_issuetracker_test', '/_admin/settings/issue-tracker/test', []);
57 pyroutes.register('admin_settings_issuetracker_update', '/_admin/settings/issue-tracker/update', []);
59 pyroutes.register('admin_settings_issuetracker_update', '/_admin/settings/issue-tracker/update', []);
58 pyroutes.register('admin_settings_labs', '/_admin/settings/labs', []);
60 pyroutes.register('admin_settings_labs', '/_admin/settings/labs', []);
59 pyroutes.register('admin_settings_labs_update', '/_admin/settings/labs/update', []);
61 pyroutes.register('admin_settings_labs_update', '/_admin/settings/labs/update', []);
60 pyroutes.register('admin_settings_license', '/_admin/settings/license', []);
62 pyroutes.register('admin_settings_license', '/_admin/settings/license', []);
61 pyroutes.register('admin_settings_license_unlock', '/_admin/settings/license_unlock', []);
63 pyroutes.register('admin_settings_license_unlock', '/_admin/settings/license_unlock', []);
62 pyroutes.register('admin_settings_mapping', '/_admin/settings/mapping', []);
64 pyroutes.register('admin_settings_mapping', '/_admin/settings/mapping', []);
63 pyroutes.register('admin_settings_mapping_update', '/_admin/settings/mapping/update', []);
65 pyroutes.register('admin_settings_mapping_update', '/_admin/settings/mapping/update', []);
64 pyroutes.register('admin_settings_open_source', '/_admin/settings/open_source', []);
66 pyroutes.register('admin_settings_open_source', '/_admin/settings/open_source', []);
65 pyroutes.register('admin_settings_process_management', '/_admin/settings/process_management', []);
67 pyroutes.register('admin_settings_process_management', '/_admin/settings/process_management', []);
66 pyroutes.register('admin_settings_process_management_data', '/_admin/settings/process_management/data', []);
68 pyroutes.register('admin_settings_process_management_data', '/_admin/settings/process_management/data', []);
67 pyroutes.register('admin_settings_process_management_master_signal', '/_admin/settings/process_management/master_signal', []);
69 pyroutes.register('admin_settings_process_management_master_signal', '/_admin/settings/process_management/master_signal', []);
68 pyroutes.register('admin_settings_process_management_signal', '/_admin/settings/process_management/signal', []);
70 pyroutes.register('admin_settings_process_management_signal', '/_admin/settings/process_management/signal', []);
69 pyroutes.register('admin_settings_scheduler_create', '/_admin/settings/scheduler/create', []);
71 pyroutes.register('admin_settings_scheduler_create', '/_admin/scheduler/create', []);
70 pyroutes.register('admin_settings_scheduler_delete', '/_admin/settings/scheduler/%(schedule_id)s/delete', ['schedule_id']);
72 pyroutes.register('admin_settings_scheduler_delete', '/_admin/scheduler/%(schedule_id)s/delete', ['schedule_id']);
71 pyroutes.register('admin_settings_scheduler_edit', '/_admin/settings/scheduler/%(schedule_id)s', ['schedule_id']);
73 pyroutes.register('admin_settings_scheduler_edit', '/_admin/scheduler/%(schedule_id)s', ['schedule_id']);
72 pyroutes.register('admin_settings_scheduler_execute', '/_admin/settings/scheduler/%(schedule_id)s/execute', ['schedule_id']);
74 pyroutes.register('admin_settings_scheduler_execute', '/_admin/scheduler/%(schedule_id)s/execute', ['schedule_id']);
73 pyroutes.register('admin_settings_scheduler_new', '/_admin/settings/scheduler/new', []);
75 pyroutes.register('admin_settings_scheduler_new', '/_admin/scheduler/new', []);
74 pyroutes.register('admin_settings_scheduler_show_all', '/_admin/settings/scheduler', []);
76 pyroutes.register('admin_settings_scheduler_update', '/_admin/scheduler/%(schedule_id)s/update', ['schedule_id']);
75 pyroutes.register('admin_settings_scheduler_show_tasks', '/_admin/settings/scheduler/_tasks', []);
76 pyroutes.register('admin_settings_scheduler_update', '/_admin/settings/scheduler/%(schedule_id)s/update', ['schedule_id']);
77 pyroutes.register('admin_settings_search', '/_admin/settings/search', []);
77 pyroutes.register('admin_settings_search', '/_admin/settings/search', []);
78 pyroutes.register('admin_settings_sessions', '/_admin/settings/sessions', []);
78 pyroutes.register('admin_settings_sessions', '/_admin/settings/sessions', []);
79 pyroutes.register('admin_settings_sessions_cleanup', '/_admin/settings/sessions/cleanup', []);
79 pyroutes.register('admin_settings_sessions_cleanup', '/_admin/settings/sessions/cleanup', []);
80 pyroutes.register('admin_settings_system', '/_admin/settings/system', []);
80 pyroutes.register('admin_settings_system', '/_admin/settings/system', []);
81 pyroutes.register('admin_settings_system_update', '/_admin/settings/system/updates', []);
81 pyroutes.register('admin_settings_system_update', '/_admin/settings/system/updates', []);
82 pyroutes.register('admin_settings_update', '/_admin/settings/update', []);
82 pyroutes.register('admin_settings_update', '/_admin/settings/update', []);
83 pyroutes.register('admin_settings_vcs', '/_admin/settings/vcs', []);
83 pyroutes.register('admin_settings_vcs', '/_admin/settings/vcs', []);
84 pyroutes.register('admin_settings_vcs_svn_generate_cfg', '/_admin/settings/vcs/svn_generate_cfg', []);
84 pyroutes.register('admin_settings_vcs_svn_generate_cfg', '/_admin/settings/vcs/svn_generate_cfg', []);
85 pyroutes.register('admin_settings_vcs_svn_pattern_delete', '/_admin/settings/vcs/svn_pattern_delete', []);
85 pyroutes.register('admin_settings_vcs_svn_pattern_delete', '/_admin/settings/vcs/svn_pattern_delete', []);
86 pyroutes.register('admin_settings_vcs_update', '/_admin/settings/vcs/update', []);
86 pyroutes.register('admin_settings_vcs_update', '/_admin/settings/vcs/update', []);
87 pyroutes.register('admin_settings_visual', '/_admin/settings/visual', []);
87 pyroutes.register('admin_settings_visual', '/_admin/settings/visual', []);
88 pyroutes.register('admin_settings_visual_update', '/_admin/settings/visual/update', []);
88 pyroutes.register('admin_settings_visual_update', '/_admin/settings/visual/update', []);
89 pyroutes.register('apiv2', '/_admin/api', []);
89 pyroutes.register('apiv2', '/_admin/api', []);
90 pyroutes.register('atom_feed_home', '/%(repo_name)s/feed-atom', ['repo_name']);
90 pyroutes.register('atom_feed_home', '/%(repo_name)s/feed-atom', ['repo_name']);
91 pyroutes.register('atom_feed_home_old', '/%(repo_name)s/feed/atom', ['repo_name']);
91 pyroutes.register('atom_feed_home_old', '/%(repo_name)s/feed/atom', ['repo_name']);
92 pyroutes.register('auth_home', '/_admin/auth*traverse', []);
92 pyroutes.register('auth_home', '/_admin/auth*traverse', []);
93 pyroutes.register('bookmarks_home', '/%(repo_name)s/bookmarks', ['repo_name']);
93 pyroutes.register('bookmarks_home', '/%(repo_name)s/bookmarks', ['repo_name']);
94 pyroutes.register('branches_home', '/%(repo_name)s/branches', ['repo_name']);
94 pyroutes.register('branches_home', '/%(repo_name)s/branches', ['repo_name']);
95 pyroutes.register('channelstream_connect', '/_admin/channelstream/connect', []);
95 pyroutes.register('channelstream_connect', '/_admin/channelstream/connect', []);
96 pyroutes.register('channelstream_proxy', '/_channelstream', []);
96 pyroutes.register('channelstream_proxy', '/_channelstream', []);
97 pyroutes.register('channelstream_subscribe', '/_admin/channelstream/subscribe', []);
97 pyroutes.register('channelstream_subscribe', '/_admin/channelstream/subscribe', []);
98 pyroutes.register('commit_draft_comments_submit', '/%(repo_name)s/changeset/%(commit_id)s/draft_comments_submit', ['repo_name', 'commit_id']);
98 pyroutes.register('commit_draft_comments_submit', '/%(repo_name)s/changeset/%(commit_id)s/draft_comments_submit', ['repo_name', 'commit_id']);
99 pyroutes.register('debug_style_email', '/_admin/debug_style/email/%(email_id)s', ['email_id']);
99 pyroutes.register('debug_style_email', '/_admin/debug_style/email/%(email_id)s', ['email_id']);
100 pyroutes.register('debug_style_email_plain_rendered', '/_admin/debug_style/email-rendered/%(email_id)s', ['email_id']);
100 pyroutes.register('debug_style_email_plain_rendered', '/_admin/debug_style/email-rendered/%(email_id)s', ['email_id']);
101 pyroutes.register('debug_style_home', '/_admin/debug_style', []);
101 pyroutes.register('debug_style_home', '/_admin/debug_style', []);
102 pyroutes.register('debug_style_template', '/_admin/debug_style/t/%(t_path)s', ['t_path']);
102 pyroutes.register('debug_style_template', '/_admin/debug_style/t/%(t_path)s', ['t_path']);
103 pyroutes.register('download_file', '/_file_store/download/%(fid)s', ['fid']);
103 pyroutes.register('download_file', '/_file_store/download/%(fid)s', ['fid']);
104 pyroutes.register('download_file_by_token', '/_file_store/token-download/%(_auth_token)s/%(fid)s', ['_auth_token', 'fid']);
104 pyroutes.register('download_file_by_token', '/_file_store/token-download/%(_auth_token)s/%(fid)s', ['_auth_token', 'fid']);
105 pyroutes.register('edit_repo', '/%(repo_name)s/settings', ['repo_name']);
105 pyroutes.register('edit_repo', '/%(repo_name)s/settings', ['repo_name']);
106 pyroutes.register('edit_repo_advanced', '/%(repo_name)s/settings/advanced', ['repo_name']);
106 pyroutes.register('edit_repo_advanced', '/%(repo_name)s/settings/advanced', ['repo_name']);
107 pyroutes.register('edit_repo_advanced_archive', '/%(repo_name)s/settings/advanced/archive', ['repo_name']);
107 pyroutes.register('edit_repo_advanced_archive', '/%(repo_name)s/settings/advanced/archive', ['repo_name']);
108 pyroutes.register('edit_repo_advanced_delete', '/%(repo_name)s/settings/advanced/delete', ['repo_name']);
108 pyroutes.register('edit_repo_advanced_delete', '/%(repo_name)s/settings/advanced/delete', ['repo_name']);
109 pyroutes.register('edit_repo_advanced_fork', '/%(repo_name)s/settings/advanced/fork', ['repo_name']);
109 pyroutes.register('edit_repo_advanced_fork', '/%(repo_name)s/settings/advanced/fork', ['repo_name']);
110 pyroutes.register('edit_repo_advanced_hooks', '/%(repo_name)s/settings/advanced/hooks', ['repo_name']);
110 pyroutes.register('edit_repo_advanced_hooks', '/%(repo_name)s/settings/advanced/hooks', ['repo_name']);
111 pyroutes.register('edit_repo_advanced_journal', '/%(repo_name)s/settings/advanced/journal', ['repo_name']);
111 pyroutes.register('edit_repo_advanced_journal', '/%(repo_name)s/settings/advanced/journal', ['repo_name']);
112 pyroutes.register('edit_repo_advanced_locking', '/%(repo_name)s/settings/advanced/locking', ['repo_name']);
112 pyroutes.register('edit_repo_advanced_locking', '/%(repo_name)s/settings/advanced/locking', ['repo_name']);
113 pyroutes.register('edit_repo_audit_logs', '/%(repo_name)s/settings/audit_logs', ['repo_name']);
113 pyroutes.register('edit_repo_audit_logs', '/%(repo_name)s/settings/audit_logs', ['repo_name']);
114 pyroutes.register('edit_repo_caches', '/%(repo_name)s/settings/caches', ['repo_name']);
114 pyroutes.register('edit_repo_caches', '/%(repo_name)s/settings/caches', ['repo_name']);
115 pyroutes.register('edit_repo_fields', '/%(repo_name)s/settings/fields', ['repo_name']);
115 pyroutes.register('edit_repo_fields', '/%(repo_name)s/settings/fields', ['repo_name']);
116 pyroutes.register('edit_repo_fields_create', '/%(repo_name)s/settings/fields/create', ['repo_name']);
116 pyroutes.register('edit_repo_fields_create', '/%(repo_name)s/settings/fields/create', ['repo_name']);
117 pyroutes.register('edit_repo_fields_delete', '/%(repo_name)s/settings/fields/%(field_id)s/delete', ['repo_name', 'field_id']);
117 pyroutes.register('edit_repo_fields_delete', '/%(repo_name)s/settings/fields/%(field_id)s/delete', ['repo_name', 'field_id']);
118 pyroutes.register('edit_repo_group', '/%(repo_group_name)s/_edit', ['repo_group_name']);
118 pyroutes.register('edit_repo_group', '/%(repo_group_name)s/_edit', ['repo_group_name']);
119 pyroutes.register('edit_repo_group_advanced', '/%(repo_group_name)s/_settings/advanced', ['repo_group_name']);
119 pyroutes.register('edit_repo_group_advanced', '/%(repo_group_name)s/_settings/advanced', ['repo_group_name']);
120 pyroutes.register('edit_repo_group_advanced_delete', '/%(repo_group_name)s/_settings/advanced/delete', ['repo_group_name']);
120 pyroutes.register('edit_repo_group_advanced_delete', '/%(repo_group_name)s/_settings/advanced/delete', ['repo_group_name']);
121 pyroutes.register('edit_repo_group_perms', '/%(repo_group_name)s/_settings/permissions', ['repo_group_name']);
121 pyroutes.register('edit_repo_group_perms', '/%(repo_group_name)s/_settings/permissions', ['repo_group_name']);
122 pyroutes.register('edit_repo_group_perms_update', '/%(repo_group_name)s/_settings/permissions/update', ['repo_group_name']);
122 pyroutes.register('edit_repo_group_perms_update', '/%(repo_group_name)s/_settings/permissions/update', ['repo_group_name']);
123 pyroutes.register('edit_repo_issuetracker', '/%(repo_name)s/settings/issue_trackers', ['repo_name']);
123 pyroutes.register('edit_repo_issuetracker', '/%(repo_name)s/settings/issue_trackers', ['repo_name']);
124 pyroutes.register('edit_repo_issuetracker_delete', '/%(repo_name)s/settings/issue_trackers/delete', ['repo_name']);
124 pyroutes.register('edit_repo_issuetracker_delete', '/%(repo_name)s/settings/issue_trackers/delete', ['repo_name']);
125 pyroutes.register('edit_repo_issuetracker_test', '/%(repo_name)s/settings/issue_trackers/test', ['repo_name']);
125 pyroutes.register('edit_repo_issuetracker_test', '/%(repo_name)s/settings/issue_trackers/test', ['repo_name']);
126 pyroutes.register('edit_repo_issuetracker_update', '/%(repo_name)s/settings/issue_trackers/update', ['repo_name']);
126 pyroutes.register('edit_repo_issuetracker_update', '/%(repo_name)s/settings/issue_trackers/update', ['repo_name']);
127 pyroutes.register('edit_repo_maintenance', '/%(repo_name)s/settings/maintenance', ['repo_name']);
127 pyroutes.register('edit_repo_maintenance', '/%(repo_name)s/settings/maintenance', ['repo_name']);
128 pyroutes.register('edit_repo_maintenance_execute', '/%(repo_name)s/settings/maintenance/execute', ['repo_name']);
128 pyroutes.register('edit_repo_maintenance_execute', '/%(repo_name)s/settings/maintenance/execute', ['repo_name']);
129 pyroutes.register('edit_repo_perms', '/%(repo_name)s/settings/permissions', ['repo_name']);
129 pyroutes.register('edit_repo_perms', '/%(repo_name)s/settings/permissions', ['repo_name']);
130 pyroutes.register('edit_repo_perms_branch', '/%(repo_name)s/settings/branch_permissions', ['repo_name']);
130 pyroutes.register('edit_repo_perms_branch', '/%(repo_name)s/settings/branch_permissions', ['repo_name']);
131 pyroutes.register('edit_repo_perms_branch_delete', '/%(repo_name)s/settings/branch_permissions/%(rule_id)s/delete', ['repo_name', 'rule_id']);
131 pyroutes.register('edit_repo_perms_branch_delete', '/%(repo_name)s/settings/branch_permissions/%(rule_id)s/delete', ['repo_name', 'rule_id']);
132 pyroutes.register('edit_repo_perms_set_private', '/%(repo_name)s/settings/permissions/set_private', ['repo_name']);
132 pyroutes.register('edit_repo_perms_set_private', '/%(repo_name)s/settings/permissions/set_private', ['repo_name']);
133 pyroutes.register('edit_repo_remote', '/%(repo_name)s/settings/remote', ['repo_name']);
133 pyroutes.register('edit_repo_remote', '/%(repo_name)s/settings/remote', ['repo_name']);
134 pyroutes.register('edit_repo_remote_pull', '/%(repo_name)s/settings/remote/pull', ['repo_name']);
134 pyroutes.register('edit_repo_remote_pull', '/%(repo_name)s/settings/remote/pull', ['repo_name']);
135 pyroutes.register('edit_repo_remote_push', '/%(repo_name)s/settings/remote/push', ['repo_name']);
135 pyroutes.register('edit_repo_remote_push', '/%(repo_name)s/settings/remote/push', ['repo_name']);
136 pyroutes.register('edit_repo_statistics', '/%(repo_name)s/settings/statistics', ['repo_name']);
136 pyroutes.register('edit_repo_statistics', '/%(repo_name)s/settings/statistics', ['repo_name']);
137 pyroutes.register('edit_repo_statistics_reset', '/%(repo_name)s/settings/statistics/update', ['repo_name']);
137 pyroutes.register('edit_repo_statistics_reset', '/%(repo_name)s/settings/statistics/update', ['repo_name']);
138 pyroutes.register('edit_repo_strip', '/%(repo_name)s/settings/strip', ['repo_name']);
138 pyroutes.register('edit_repo_strip', '/%(repo_name)s/settings/strip', ['repo_name']);
139 pyroutes.register('edit_repo_vcs', '/%(repo_name)s/settings/vcs', ['repo_name']);
139 pyroutes.register('edit_repo_vcs', '/%(repo_name)s/settings/vcs', ['repo_name']);
140 pyroutes.register('edit_repo_vcs_svn_pattern_delete', '/%(repo_name)s/settings/vcs/svn_pattern/delete', ['repo_name']);
140 pyroutes.register('edit_repo_vcs_svn_pattern_delete', '/%(repo_name)s/settings/vcs/svn_pattern/delete', ['repo_name']);
141 pyroutes.register('edit_repo_vcs_update', '/%(repo_name)s/settings/vcs/update', ['repo_name']);
141 pyroutes.register('edit_repo_vcs_update', '/%(repo_name)s/settings/vcs/update', ['repo_name']);
142 pyroutes.register('edit_user_audit_logs', '/_admin/users/%(user_id)s/edit/audit', ['user_id']);
142 pyroutes.register('edit_user_audit_logs', '/_admin/users/%(user_id)s/edit/audit', ['user_id']);
143 pyroutes.register('edit_user_audit_logs_download', '/_admin/users/%(user_id)s/edit/audit/download', ['user_id']);
143 pyroutes.register('edit_user_audit_logs_download', '/_admin/users/%(user_id)s/edit/audit/download', ['user_id']);
144 pyroutes.register('edit_user_auth_tokens', '/_admin/users/%(user_id)s/edit/auth_tokens', ['user_id']);
144 pyroutes.register('edit_user_auth_tokens', '/_admin/users/%(user_id)s/edit/auth_tokens', ['user_id']);
145 pyroutes.register('edit_user_auth_tokens_add', '/_admin/users/%(user_id)s/edit/auth_tokens/new', ['user_id']);
145 pyroutes.register('edit_user_auth_tokens_add', '/_admin/users/%(user_id)s/edit/auth_tokens/new', ['user_id']);
146 pyroutes.register('edit_user_auth_tokens_delete', '/_admin/users/%(user_id)s/edit/auth_tokens/delete', ['user_id']);
146 pyroutes.register('edit_user_auth_tokens_delete', '/_admin/users/%(user_id)s/edit/auth_tokens/delete', ['user_id']);
147 pyroutes.register('edit_user_auth_tokens_view', '/_admin/users/%(user_id)s/edit/auth_tokens/view', ['user_id']);
147 pyroutes.register('edit_user_auth_tokens_view', '/_admin/users/%(user_id)s/edit/auth_tokens/view', ['user_id']);
148 pyroutes.register('edit_user_caches', '/_admin/users/%(user_id)s/edit/caches', ['user_id']);
148 pyroutes.register('edit_user_caches', '/_admin/users/%(user_id)s/edit/caches', ['user_id']);
149 pyroutes.register('edit_user_caches_update', '/_admin/users/%(user_id)s/edit/caches/update', ['user_id']);
149 pyroutes.register('edit_user_caches_update', '/_admin/users/%(user_id)s/edit/caches/update', ['user_id']);
150 pyroutes.register('edit_user_emails', '/_admin/users/%(user_id)s/edit/emails', ['user_id']);
150 pyroutes.register('edit_user_emails', '/_admin/users/%(user_id)s/edit/emails', ['user_id']);
151 pyroutes.register('edit_user_emails_add', '/_admin/users/%(user_id)s/edit/emails/new', ['user_id']);
151 pyroutes.register('edit_user_emails_add', '/_admin/users/%(user_id)s/edit/emails/new', ['user_id']);
152 pyroutes.register('edit_user_emails_delete', '/_admin/users/%(user_id)s/edit/emails/delete', ['user_id']);
152 pyroutes.register('edit_user_emails_delete', '/_admin/users/%(user_id)s/edit/emails/delete', ['user_id']);
153 pyroutes.register('edit_user_group', '/_admin/user_groups/%(user_group_id)s/edit', ['user_group_id']);
153 pyroutes.register('edit_user_group', '/_admin/user_groups/%(user_group_id)s/edit', ['user_group_id']);
154 pyroutes.register('edit_user_group_advanced', '/_admin/user_groups/%(user_group_id)s/edit/advanced', ['user_group_id']);
154 pyroutes.register('edit_user_group_advanced', '/_admin/user_groups/%(user_group_id)s/edit/advanced', ['user_group_id']);
155 pyroutes.register('edit_user_group_advanced_sync', '/_admin/user_groups/%(user_group_id)s/edit/advanced/sync', ['user_group_id']);
155 pyroutes.register('edit_user_group_advanced_sync', '/_admin/user_groups/%(user_group_id)s/edit/advanced/sync', ['user_group_id']);
156 pyroutes.register('edit_user_group_global_perms', '/_admin/user_groups/%(user_group_id)s/edit/global_permissions', ['user_group_id']);
156 pyroutes.register('edit_user_group_global_perms', '/_admin/user_groups/%(user_group_id)s/edit/global_permissions', ['user_group_id']);
157 pyroutes.register('edit_user_group_global_perms_update', '/_admin/user_groups/%(user_group_id)s/edit/global_permissions/update', ['user_group_id']);
157 pyroutes.register('edit_user_group_global_perms_update', '/_admin/user_groups/%(user_group_id)s/edit/global_permissions/update', ['user_group_id']);
158 pyroutes.register('edit_user_group_perms', '/_admin/user_groups/%(user_group_id)s/edit/permissions', ['user_group_id']);
158 pyroutes.register('edit_user_group_perms', '/_admin/user_groups/%(user_group_id)s/edit/permissions', ['user_group_id']);
159 pyroutes.register('edit_user_group_perms_summary', '/_admin/user_groups/%(user_group_id)s/edit/permissions_summary', ['user_group_id']);
159 pyroutes.register('edit_user_group_perms_summary', '/_admin/user_groups/%(user_group_id)s/edit/permissions_summary', ['user_group_id']);
160 pyroutes.register('edit_user_group_perms_summary_json', '/_admin/user_groups/%(user_group_id)s/edit/permissions_summary/json', ['user_group_id']);
160 pyroutes.register('edit_user_group_perms_summary_json', '/_admin/user_groups/%(user_group_id)s/edit/permissions_summary/json', ['user_group_id']);
161 pyroutes.register('edit_user_group_perms_update', '/_admin/user_groups/%(user_group_id)s/edit/permissions/update', ['user_group_id']);
161 pyroutes.register('edit_user_group_perms_update', '/_admin/user_groups/%(user_group_id)s/edit/permissions/update', ['user_group_id']);
162 pyroutes.register('edit_user_groups_management', '/_admin/users/%(user_id)s/edit/groups_management', ['user_id']);
162 pyroutes.register('edit_user_groups_management', '/_admin/users/%(user_id)s/edit/groups_management', ['user_id']);
163 pyroutes.register('edit_user_groups_management_updates', '/_admin/users/%(user_id)s/edit/edit_user_groups_management/updates', ['user_id']);
163 pyroutes.register('edit_user_groups_management_updates', '/_admin/users/%(user_id)s/edit/edit_user_groups_management/updates', ['user_id']);
164 pyroutes.register('edit_user_ips', '/_admin/users/%(user_id)s/edit/ips', ['user_id']);
164 pyroutes.register('edit_user_ips', '/_admin/users/%(user_id)s/edit/ips', ['user_id']);
165 pyroutes.register('edit_user_ips_add', '/_admin/users/%(user_id)s/edit/ips/new', ['user_id']);
165 pyroutes.register('edit_user_ips_add', '/_admin/users/%(user_id)s/edit/ips/new', ['user_id']);
166 pyroutes.register('edit_user_ips_delete', '/_admin/users/%(user_id)s/edit/ips/delete', ['user_id']);
166 pyroutes.register('edit_user_ips_delete', '/_admin/users/%(user_id)s/edit/ips/delete', ['user_id']);
167 pyroutes.register('edit_user_perms_summary', '/_admin/users/%(user_id)s/edit/permissions_summary', ['user_id']);
167 pyroutes.register('edit_user_perms_summary', '/_admin/users/%(user_id)s/edit/permissions_summary', ['user_id']);
168 pyroutes.register('edit_user_perms_summary_json', '/_admin/users/%(user_id)s/edit/permissions_summary/json', ['user_id']);
168 pyroutes.register('edit_user_perms_summary_json', '/_admin/users/%(user_id)s/edit/permissions_summary/json', ['user_id']);
169 pyroutes.register('edit_user_ssh_keys', '/_admin/users/%(user_id)s/edit/ssh_keys', ['user_id']);
169 pyroutes.register('edit_user_ssh_keys', '/_admin/users/%(user_id)s/edit/ssh_keys', ['user_id']);
170 pyroutes.register('edit_user_ssh_keys_add', '/_admin/users/%(user_id)s/edit/ssh_keys/new', ['user_id']);
170 pyroutes.register('edit_user_ssh_keys_add', '/_admin/users/%(user_id)s/edit/ssh_keys/new', ['user_id']);
171 pyroutes.register('edit_user_ssh_keys_delete', '/_admin/users/%(user_id)s/edit/ssh_keys/delete', ['user_id']);
171 pyroutes.register('edit_user_ssh_keys_delete', '/_admin/users/%(user_id)s/edit/ssh_keys/delete', ['user_id']);
172 pyroutes.register('edit_user_ssh_keys_generate_keypair', '/_admin/users/%(user_id)s/edit/ssh_keys/generate', ['user_id']);
172 pyroutes.register('edit_user_ssh_keys_generate_keypair', '/_admin/users/%(user_id)s/edit/ssh_keys/generate', ['user_id']);
173 pyroutes.register('favicon', '/favicon.ico', []);
173 pyroutes.register('favicon', '/favicon.ico', []);
174 pyroutes.register('file_preview', '/_file_preview', []);
174 pyroutes.register('file_preview', '/_file_preview', []);
175 pyroutes.register('gist_delete', '/_admin/gists/%(gist_id)s/delete', ['gist_id']);
175 pyroutes.register('gist_delete', '/_admin/gists/%(gist_id)s/delete', ['gist_id']);
176 pyroutes.register('gist_edit', '/_admin/gists/%(gist_id)s/edit', ['gist_id']);
176 pyroutes.register('gist_edit', '/_admin/gists/%(gist_id)s/edit', ['gist_id']);
177 pyroutes.register('gist_edit_check_revision', '/_admin/gists/%(gist_id)s/edit/check_revision', ['gist_id']);
177 pyroutes.register('gist_edit_check_revision', '/_admin/gists/%(gist_id)s/edit/check_revision', ['gist_id']);
178 pyroutes.register('gist_show', '/_admin/gists/%(gist_id)s', ['gist_id']);
178 pyroutes.register('gist_show', '/_admin/gists/%(gist_id)s', ['gist_id']);
179 pyroutes.register('gist_show_formatted', '/_admin/gists/%(gist_id)s/rev/%(revision)s/%(format)s', ['gist_id', 'revision', 'format']);
179 pyroutes.register('gist_show_formatted', '/_admin/gists/%(gist_id)s/rev/%(revision)s/%(format)s', ['gist_id', 'revision', 'format']);
180 pyroutes.register('gist_show_formatted_path', '/_admin/gists/%(gist_id)s/rev/%(revision)s/%(format)s/%(f_path)s', ['gist_id', 'revision', 'format', 'f_path']);
180 pyroutes.register('gist_show_formatted_path', '/_admin/gists/%(gist_id)s/rev/%(revision)s/%(format)s/%(f_path)s', ['gist_id', 'revision', 'format', 'f_path']);
181 pyroutes.register('gist_show_rev', '/_admin/gists/%(gist_id)s/rev/%(revision)s', ['gist_id', 'revision']);
181 pyroutes.register('gist_show_rev', '/_admin/gists/%(gist_id)s/rev/%(revision)s', ['gist_id', 'revision']);
182 pyroutes.register('gist_update', '/_admin/gists/%(gist_id)s/update', ['gist_id']);
182 pyroutes.register('gist_update', '/_admin/gists/%(gist_id)s/update', ['gist_id']);
183 pyroutes.register('gists_create', '/_admin/gists/create', []);
183 pyroutes.register('gists_create', '/_admin/gists/create', []);
184 pyroutes.register('gists_new', '/_admin/gists/new', []);
184 pyroutes.register('gists_new', '/_admin/gists/new', []);
185 pyroutes.register('gists_show', '/_admin/gists', []);
185 pyroutes.register('gists_show', '/_admin/gists', []);
186 pyroutes.register('global_integrations_create', '/_admin/integrations/%(integration)s/new', ['integration']);
186 pyroutes.register('global_integrations_create', '/_admin/integrations/%(integration)s/new', ['integration']);
187 pyroutes.register('global_integrations_edit', '/_admin/integrations/%(integration)s/%(integration_id)s', ['integration', 'integration_id']);
187 pyroutes.register('global_integrations_edit', '/_admin/integrations/%(integration)s/%(integration_id)s', ['integration', 'integration_id']);
188 pyroutes.register('global_integrations_home', '/_admin/integrations', []);
188 pyroutes.register('global_integrations_home', '/_admin/integrations', []);
189 pyroutes.register('global_integrations_list', '/_admin/integrations/%(integration)s', ['integration']);
189 pyroutes.register('global_integrations_list', '/_admin/integrations/%(integration)s', ['integration']);
190 pyroutes.register('global_integrations_new', '/_admin/integrations/new', []);
190 pyroutes.register('global_integrations_new', '/_admin/integrations/new', []);
191 pyroutes.register('goto_switcher_data', '/_goto_data', []);
191 pyroutes.register('goto_switcher_data', '/_goto_data', []);
192 pyroutes.register('home', '/', []);
192 pyroutes.register('home', '/', []);
193 pyroutes.register('hovercard_pull_request', '/_hovercard/pull_request/%(pull_request_id)s', ['pull_request_id']);
193 pyroutes.register('hovercard_pull_request', '/_hovercard/pull_request/%(pull_request_id)s', ['pull_request_id']);
194 pyroutes.register('hovercard_repo_commit', '/_hovercard/commit/%(repo_name)s/%(commit_id)s', ['repo_name', 'commit_id']);
194 pyroutes.register('hovercard_repo_commit', '/_hovercard/commit/%(repo_name)s/%(commit_id)s', ['repo_name', 'commit_id']);
195 pyroutes.register('hovercard_user', '/_hovercard/user/%(user_id)s', ['user_id']);
195 pyroutes.register('hovercard_user', '/_hovercard/user/%(user_id)s', ['user_id']);
196 pyroutes.register('hovercard_user_group', '/_hovercard/user_group/%(user_group_id)s', ['user_group_id']);
196 pyroutes.register('hovercard_user_group', '/_hovercard/user_group/%(user_group_id)s', ['user_group_id']);
197 pyroutes.register('hovercard_username', '/_hovercard/username/%(username)s', ['username']);
197 pyroutes.register('hovercard_username', '/_hovercard/username/%(username)s', ['username']);
198 pyroutes.register('journal', '/_admin/journal', []);
198 pyroutes.register('journal', '/_admin/journal', []);
199 pyroutes.register('journal_atom', '/_admin/journal/atom', []);
199 pyroutes.register('journal_atom', '/_admin/journal/atom', []);
200 pyroutes.register('journal_public', '/_admin/public_journal', []);
200 pyroutes.register('journal_public', '/_admin/public_journal', []);
201 pyroutes.register('journal_public_atom', '/_admin/public_journal/atom', []);
201 pyroutes.register('journal_public_atom', '/_admin/public_journal/atom', []);
202 pyroutes.register('journal_public_atom_old', '/_admin/public_journal_atom', []);
202 pyroutes.register('journal_public_atom_old', '/_admin/public_journal_atom', []);
203 pyroutes.register('journal_public_rss', '/_admin/public_journal/rss', []);
203 pyroutes.register('journal_public_rss', '/_admin/public_journal/rss', []);
204 pyroutes.register('journal_public_rss_old', '/_admin/public_journal_rss', []);
204 pyroutes.register('journal_public_rss_old', '/_admin/public_journal_rss', []);
205 pyroutes.register('journal_rss', '/_admin/journal/rss', []);
205 pyroutes.register('journal_rss', '/_admin/journal/rss', []);
206 pyroutes.register('login', '/_admin/login', []);
206 pyroutes.register('login', '/_admin/login', []);
207 pyroutes.register('logout', '/_admin/logout', []);
207 pyroutes.register('logout', '/_admin/logout', []);
208 pyroutes.register('main_page_repo_groups_data', '/_home_repo_groups', []);
208 pyroutes.register('main_page_repo_groups_data', '/_home_repo_groups', []);
209 pyroutes.register('main_page_repos_data', '/_home_repos', []);
209 pyroutes.register('main_page_repos_data', '/_home_repos', []);
210 pyroutes.register('markup_preview', '/_markup_preview', []);
210 pyroutes.register('markup_preview', '/_markup_preview', []);
211 pyroutes.register('my_account_auth_tokens', '/_admin/my_account/auth_tokens', []);
211 pyroutes.register('my_account_auth_tokens', '/_admin/my_account/auth_tokens', []);
212 pyroutes.register('my_account_auth_tokens_add', '/_admin/my_account/auth_tokens/new', []);
212 pyroutes.register('my_account_auth_tokens_add', '/_admin/my_account/auth_tokens/new', []);
213 pyroutes.register('my_account_auth_tokens_delete', '/_admin/my_account/auth_tokens/delete', []);
213 pyroutes.register('my_account_auth_tokens_delete', '/_admin/my_account/auth_tokens/delete', []);
214 pyroutes.register('my_account_auth_tokens_view', '/_admin/my_account/auth_tokens/view', []);
214 pyroutes.register('my_account_auth_tokens_view', '/_admin/my_account/auth_tokens/view', []);
215 pyroutes.register('my_account_bookmarks', '/_admin/my_account/bookmarks', []);
215 pyroutes.register('my_account_bookmarks', '/_admin/my_account/bookmarks', []);
216 pyroutes.register('my_account_bookmarks_update', '/_admin/my_account/bookmarks/update', []);
216 pyroutes.register('my_account_bookmarks_update', '/_admin/my_account/bookmarks/update', []);
217 pyroutes.register('my_account_edit', '/_admin/my_account/edit', []);
217 pyroutes.register('my_account_edit', '/_admin/my_account/edit', []);
218 pyroutes.register('my_account_emails', '/_admin/my_account/emails', []);
218 pyroutes.register('my_account_emails', '/_admin/my_account/emails', []);
219 pyroutes.register('my_account_emails_add', '/_admin/my_account/emails/new', []);
219 pyroutes.register('my_account_emails_add', '/_admin/my_account/emails/new', []);
220 pyroutes.register('my_account_emails_delete', '/_admin/my_account/emails/delete', []);
220 pyroutes.register('my_account_emails_delete', '/_admin/my_account/emails/delete', []);
221 pyroutes.register('my_account_external_identity', '/_admin/my_account/external-identity', []);
221 pyroutes.register('my_account_external_identity', '/_admin/my_account/external-identity', []);
222 pyroutes.register('my_account_external_identity_delete', '/_admin/my_account/external-identity/delete', []);
222 pyroutes.register('my_account_external_identity_delete', '/_admin/my_account/external-identity/delete', []);
223 pyroutes.register('my_account_goto_bookmark', '/_admin/my_account/bookmark/%(bookmark_id)s', ['bookmark_id']);
223 pyroutes.register('my_account_goto_bookmark', '/_admin/my_account/bookmark/%(bookmark_id)s', ['bookmark_id']);
224 pyroutes.register('my_account_notifications', '/_admin/my_account/notifications', []);
224 pyroutes.register('my_account_notifications', '/_admin/my_account/notifications', []);
225 pyroutes.register('my_account_notifications_test_channelstream', '/_admin/my_account/test_channelstream', []);
225 pyroutes.register('my_account_notifications_test_channelstream', '/_admin/my_account/test_channelstream', []);
226 pyroutes.register('my_account_notifications_toggle_visibility', '/_admin/my_account/toggle_visibility', []);
226 pyroutes.register('my_account_notifications_toggle_visibility', '/_admin/my_account/toggle_visibility', []);
227 pyroutes.register('my_account_password', '/_admin/my_account/password', []);
227 pyroutes.register('my_account_password', '/_admin/my_account/password', []);
228 pyroutes.register('my_account_password_update', '/_admin/my_account/password/update', []);
228 pyroutes.register('my_account_password_update', '/_admin/my_account/password/update', []);
229 pyroutes.register('my_account_perms', '/_admin/my_account/perms', []);
229 pyroutes.register('my_account_perms', '/_admin/my_account/perms', []);
230 pyroutes.register('my_account_profile', '/_admin/my_account/profile', []);
230 pyroutes.register('my_account_profile', '/_admin/my_account/profile', []);
231 pyroutes.register('my_account_pullrequests', '/_admin/my_account/pull_requests', []);
231 pyroutes.register('my_account_pullrequests', '/_admin/my_account/pull_requests', []);
232 pyroutes.register('my_account_pullrequests_data', '/_admin/my_account/pull_requests/data', []);
232 pyroutes.register('my_account_pullrequests_data', '/_admin/my_account/pull_requests/data', []);
233 pyroutes.register('my_account_repos', '/_admin/my_account/repos', []);
233 pyroutes.register('my_account_repos', '/_admin/my_account/repos', []);
234 pyroutes.register('my_account_ssh_keys', '/_admin/my_account/ssh_keys', []);
234 pyroutes.register('my_account_ssh_keys', '/_admin/my_account/ssh_keys', []);
235 pyroutes.register('my_account_ssh_keys_add', '/_admin/my_account/ssh_keys/new', []);
235 pyroutes.register('my_account_ssh_keys_add', '/_admin/my_account/ssh_keys/new', []);
236 pyroutes.register('my_account_ssh_keys_delete', '/_admin/my_account/ssh_keys/delete', []);
236 pyroutes.register('my_account_ssh_keys_delete', '/_admin/my_account/ssh_keys/delete', []);
237 pyroutes.register('my_account_ssh_keys_generate', '/_admin/my_account/ssh_keys/generate', []);
237 pyroutes.register('my_account_ssh_keys_generate', '/_admin/my_account/ssh_keys/generate', []);
238 pyroutes.register('my_account_update', '/_admin/my_account/update', []);
238 pyroutes.register('my_account_update', '/_admin/my_account/update', []);
239 pyroutes.register('my_account_user_group_membership', '/_admin/my_account/user_group_membership', []);
239 pyroutes.register('my_account_user_group_membership', '/_admin/my_account/user_group_membership', []);
240 pyroutes.register('my_account_watched', '/_admin/my_account/watched', []);
240 pyroutes.register('my_account_watched', '/_admin/my_account/watched', []);
241 pyroutes.register('notifications_delete', '/_admin/notifications/%(notification_id)s/delete', ['notification_id']);
241 pyroutes.register('notifications_delete', '/_admin/notifications/%(notification_id)s/delete', ['notification_id']);
242 pyroutes.register('notifications_mark_all_read', '/_admin/notifications_mark_all_read', []);
242 pyroutes.register('notifications_mark_all_read', '/_admin/notifications_mark_all_read', []);
243 pyroutes.register('notifications_show', '/_admin/notifications/%(notification_id)s', ['notification_id']);
243 pyroutes.register('notifications_show', '/_admin/notifications/%(notification_id)s', ['notification_id']);
244 pyroutes.register('notifications_show_all', '/_admin/notifications', []);
244 pyroutes.register('notifications_show_all', '/_admin/notifications', []);
245 pyroutes.register('notifications_update', '/_admin/notifications/%(notification_id)s/update', ['notification_id']);
245 pyroutes.register('notifications_update', '/_admin/notifications/%(notification_id)s/update', ['notification_id']);
246 pyroutes.register('ops_error_test', '/_admin/ops/error', []);
246 pyroutes.register('ops_error_test', '/_admin/ops/error', []);
247 pyroutes.register('ops_healthcheck', '/_admin/ops/status', []);
247 pyroutes.register('ops_healthcheck', '/_admin/ops/status', []);
248 pyroutes.register('ops_ping', '/_admin/ops/ping', []);
248 pyroutes.register('ops_ping', '/_admin/ops/ping', []);
249 pyroutes.register('ops_redirect_test', '/_admin/ops/redirect', []);
249 pyroutes.register('ops_redirect_test', '/_admin/ops/redirect', []);
250 pyroutes.register('plugin_admin_chat', '/_admin/plugin_admin_chat/%(action)s', ['action']);
250 pyroutes.register('plugin_admin_chat', '/_admin/plugin_admin_chat/%(action)s', ['action']);
251 pyroutes.register('pull_requests_global', '/_admin/pull-request/%(pull_request_id)s', ['pull_request_id']);
251 pyroutes.register('pull_requests_global', '/_admin/pull-request/%(pull_request_id)s', ['pull_request_id']);
252 pyroutes.register('pull_requests_global_0', '/_admin/pull_requests/%(pull_request_id)s', ['pull_request_id']);
252 pyroutes.register('pull_requests_global_0', '/_admin/pull_requests/%(pull_request_id)s', ['pull_request_id']);
253 pyroutes.register('pull_requests_global_1', '/_admin/pull-requests/%(pull_request_id)s', ['pull_request_id']);
253 pyroutes.register('pull_requests_global_1', '/_admin/pull-requests/%(pull_request_id)s', ['pull_request_id']);
254 pyroutes.register('pullrequest_comment_create', '/%(repo_name)s/pull-request/%(pull_request_id)s/comment', ['repo_name', 'pull_request_id']);
254 pyroutes.register('pullrequest_comment_create', '/%(repo_name)s/pull-request/%(pull_request_id)s/comment', ['repo_name', 'pull_request_id']);
255 pyroutes.register('pullrequest_comment_delete', '/%(repo_name)s/pull-request/%(pull_request_id)s/comment/%(comment_id)s/delete', ['repo_name', 'pull_request_id', 'comment_id']);
255 pyroutes.register('pullrequest_comment_delete', '/%(repo_name)s/pull-request/%(pull_request_id)s/comment/%(comment_id)s/delete', ['repo_name', 'pull_request_id', 'comment_id']);
256 pyroutes.register('pullrequest_comment_edit', '/%(repo_name)s/pull-request/%(pull_request_id)s/comment/%(comment_id)s/edit', ['repo_name', 'pull_request_id', 'comment_id']);
256 pyroutes.register('pullrequest_comment_edit', '/%(repo_name)s/pull-request/%(pull_request_id)s/comment/%(comment_id)s/edit', ['repo_name', 'pull_request_id', 'comment_id']);
257 pyroutes.register('pullrequest_comments', '/%(repo_name)s/pull-request/%(pull_request_id)s/comments', ['repo_name', 'pull_request_id']);
257 pyroutes.register('pullrequest_comments', '/%(repo_name)s/pull-request/%(pull_request_id)s/comments', ['repo_name', 'pull_request_id']);
258 pyroutes.register('pullrequest_create', '/%(repo_name)s/pull-request/create', ['repo_name']);
258 pyroutes.register('pullrequest_create', '/%(repo_name)s/pull-request/create', ['repo_name']);
259 pyroutes.register('pullrequest_delete', '/%(repo_name)s/pull-request/%(pull_request_id)s/delete', ['repo_name', 'pull_request_id']);
259 pyroutes.register('pullrequest_delete', '/%(repo_name)s/pull-request/%(pull_request_id)s/delete', ['repo_name', 'pull_request_id']);
260 pyroutes.register('pullrequest_draft_comments_submit', '/%(repo_name)s/pull-request/%(pull_request_id)s/draft_comments_submit', ['repo_name', 'pull_request_id']);
260 pyroutes.register('pullrequest_draft_comments_submit', '/%(repo_name)s/pull-request/%(pull_request_id)s/draft_comments_submit', ['repo_name', 'pull_request_id']);
261 pyroutes.register('pullrequest_drafts', '/%(repo_name)s/pull-request/%(pull_request_id)s/drafts', ['repo_name', 'pull_request_id']);
261 pyroutes.register('pullrequest_drafts', '/%(repo_name)s/pull-request/%(pull_request_id)s/drafts', ['repo_name', 'pull_request_id']);
262 pyroutes.register('pullrequest_merge', '/%(repo_name)s/pull-request/%(pull_request_id)s/merge', ['repo_name', 'pull_request_id']);
262 pyroutes.register('pullrequest_merge', '/%(repo_name)s/pull-request/%(pull_request_id)s/merge', ['repo_name', 'pull_request_id']);
263 pyroutes.register('pullrequest_new', '/%(repo_name)s/pull-request/new', ['repo_name']);
263 pyroutes.register('pullrequest_new', '/%(repo_name)s/pull-request/new', ['repo_name']);
264 pyroutes.register('pullrequest_repo_refs', '/%(repo_name)s/pull-request/refs/%(target_repo_name)s', ['repo_name', 'target_repo_name']);
264 pyroutes.register('pullrequest_repo_refs', '/%(repo_name)s/pull-request/refs/%(target_repo_name)s', ['repo_name', 'target_repo_name']);
265 pyroutes.register('pullrequest_repo_targets', '/%(repo_name)s/pull-request/repo-targets', ['repo_name']);
265 pyroutes.register('pullrequest_repo_targets', '/%(repo_name)s/pull-request/repo-targets', ['repo_name']);
266 pyroutes.register('pullrequest_show', '/%(repo_name)s/pull-request/%(pull_request_id)s', ['repo_name', 'pull_request_id']);
266 pyroutes.register('pullrequest_show', '/%(repo_name)s/pull-request/%(pull_request_id)s', ['repo_name', 'pull_request_id']);
267 pyroutes.register('pullrequest_show_all', '/%(repo_name)s/pull-request', ['repo_name']);
267 pyroutes.register('pullrequest_show_all', '/%(repo_name)s/pull-request', ['repo_name']);
268 pyroutes.register('pullrequest_show_all_data', '/%(repo_name)s/pull-request-data', ['repo_name']);
268 pyroutes.register('pullrequest_show_all_data', '/%(repo_name)s/pull-request-data', ['repo_name']);
269 pyroutes.register('pullrequest_todos', '/%(repo_name)s/pull-request/%(pull_request_id)s/todos', ['repo_name', 'pull_request_id']);
269 pyroutes.register('pullrequest_todos', '/%(repo_name)s/pull-request/%(pull_request_id)s/todos', ['repo_name', 'pull_request_id']);
270 pyroutes.register('pullrequest_update', '/%(repo_name)s/pull-request/%(pull_request_id)s/update', ['repo_name', 'pull_request_id']);
270 pyroutes.register('pullrequest_update', '/%(repo_name)s/pull-request/%(pull_request_id)s/update', ['repo_name', 'pull_request_id']);
271 pyroutes.register('register', '/_admin/register', []);
271 pyroutes.register('register', '/_admin/register', []);
272 pyroutes.register('repo_archivefile', '/%(repo_name)s/archive/%(fname)s', ['repo_name', 'fname']);
272 pyroutes.register('repo_archivefile', '/%(repo_name)s/archive/%(fname)s', ['repo_name', 'fname']);
273 pyroutes.register('repo_artifacts_data', '/%(repo_name)s/artifacts_data', ['repo_name']);
273 pyroutes.register('repo_artifacts_data', '/%(repo_name)s/artifacts_data', ['repo_name']);
274 pyroutes.register('repo_artifacts_delete', '/%(repo_name)s/artifacts/delete/%(uid)s', ['repo_name', 'uid']);
274 pyroutes.register('repo_artifacts_delete', '/%(repo_name)s/artifacts/delete/%(uid)s', ['repo_name', 'uid']);
275 pyroutes.register('repo_artifacts_get', '/%(repo_name)s/artifacts/download/%(uid)s', ['repo_name', 'uid']);
275 pyroutes.register('repo_artifacts_get', '/%(repo_name)s/artifacts/download/%(uid)s', ['repo_name', 'uid']);
276 pyroutes.register('repo_artifacts_info', '/%(repo_name)s/artifacts/info/%(uid)s', ['repo_name', 'uid']);
276 pyroutes.register('repo_artifacts_info', '/%(repo_name)s/artifacts/info/%(uid)s', ['repo_name', 'uid']);
277 pyroutes.register('repo_artifacts_list', '/%(repo_name)s/artifacts', ['repo_name']);
277 pyroutes.register('repo_artifacts_list', '/%(repo_name)s/artifacts', ['repo_name']);
278 pyroutes.register('repo_artifacts_new', '/%(repo_name)s/artifacts/new', ['repo_name']);
278 pyroutes.register('repo_artifacts_new', '/%(repo_name)s/artifacts/new', ['repo_name']);
279 pyroutes.register('repo_artifacts_store', '/%(repo_name)s/artifacts/store', ['repo_name']);
279 pyroutes.register('repo_artifacts_store', '/%(repo_name)s/artifacts/store', ['repo_name']);
280 pyroutes.register('repo_artifacts_stream_script', '/_file_store/stream-upload-script', []);
280 pyroutes.register('repo_artifacts_stream_script', '/_file_store/stream-upload-script', []);
281 pyroutes.register('repo_artifacts_stream_store', '/_file_store/stream-upload', []);
281 pyroutes.register('repo_artifacts_stream_store', '/_file_store/stream-upload', []);
282 pyroutes.register('repo_artifacts_update', '/%(repo_name)s/artifacts/update/%(uid)s', ['repo_name', 'uid']);
282 pyroutes.register('repo_artifacts_update', '/%(repo_name)s/artifacts/update/%(uid)s', ['repo_name', 'uid']);
283 pyroutes.register('repo_automation', '/%(repo_name)s/settings/automation', ['repo_name']);
283 pyroutes.register('repo_automation', '/%(repo_name)s/settings/automation', ['repo_name']);
284 pyroutes.register('repo_automation_update', '/%(repo_name)s/settings/automation/%(entry_id)s/update', ['repo_name', 'entry_id']);
284 pyroutes.register('repo_automation_update', '/%(repo_name)s/settings/automation/%(entry_id)s/update', ['repo_name', 'entry_id']);
285 pyroutes.register('repo_changelog', '/%(repo_name)s/changelog', ['repo_name']);
285 pyroutes.register('repo_changelog', '/%(repo_name)s/changelog', ['repo_name']);
286 pyroutes.register('repo_changelog_file', '/%(repo_name)s/changelog/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
286 pyroutes.register('repo_changelog_file', '/%(repo_name)s/changelog/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
287 pyroutes.register('repo_commit', '/%(repo_name)s/changeset/%(commit_id)s', ['repo_name', 'commit_id']);
287 pyroutes.register('repo_commit', '/%(repo_name)s/changeset/%(commit_id)s', ['repo_name', 'commit_id']);
288 pyroutes.register('repo_commit_children', '/%(repo_name)s/changeset_children/%(commit_id)s', ['repo_name', 'commit_id']);
288 pyroutes.register('repo_commit_children', '/%(repo_name)s/changeset_children/%(commit_id)s', ['repo_name', 'commit_id']);
289 pyroutes.register('repo_commit_comment_attachment_upload', '/%(repo_name)s/changeset/%(commit_id)s/comment/attachment_upload', ['repo_name', 'commit_id']);
289 pyroutes.register('repo_commit_comment_attachment_upload', '/%(repo_name)s/changeset/%(commit_id)s/comment/attachment_upload', ['repo_name', 'commit_id']);
290 pyroutes.register('repo_commit_comment_create', '/%(repo_name)s/changeset/%(commit_id)s/comment/create', ['repo_name', 'commit_id']);
290 pyroutes.register('repo_commit_comment_create', '/%(repo_name)s/changeset/%(commit_id)s/comment/create', ['repo_name', 'commit_id']);
291 pyroutes.register('repo_commit_comment_delete', '/%(repo_name)s/changeset/%(commit_id)s/comment/%(comment_id)s/delete', ['repo_name', 'commit_id', 'comment_id']);
291 pyroutes.register('repo_commit_comment_delete', '/%(repo_name)s/changeset/%(commit_id)s/comment/%(comment_id)s/delete', ['repo_name', 'commit_id', 'comment_id']);
292 pyroutes.register('repo_commit_comment_edit', '/%(repo_name)s/changeset/%(commit_id)s/comment/%(comment_id)s/edit', ['repo_name', 'commit_id', 'comment_id']);
292 pyroutes.register('repo_commit_comment_edit', '/%(repo_name)s/changeset/%(commit_id)s/comment/%(comment_id)s/edit', ['repo_name', 'commit_id', 'comment_id']);
293 pyroutes.register('repo_commit_comment_history_view', '/%(repo_name)s/changeset/%(commit_id)s/comment/%(comment_id)s/history_view/%(comment_history_id)s', ['repo_name', 'commit_id', 'comment_id', 'comment_history_id']);
293 pyroutes.register('repo_commit_comment_history_view', '/%(repo_name)s/changeset/%(commit_id)s/comment/%(comment_id)s/history_view/%(comment_history_id)s', ['repo_name', 'commit_id', 'comment_id', 'comment_history_id']);
294 pyroutes.register('repo_commit_comment_preview', '/%(repo_name)s/changeset/%(commit_id)s/comment/preview', ['repo_name', 'commit_id']);
294 pyroutes.register('repo_commit_comment_preview', '/%(repo_name)s/changeset/%(commit_id)s/comment/preview', ['repo_name', 'commit_id']);
295 pyroutes.register('repo_commit_data', '/%(repo_name)s/changeset-data/%(commit_id)s', ['repo_name', 'commit_id']);
295 pyroutes.register('repo_commit_data', '/%(repo_name)s/changeset-data/%(commit_id)s', ['repo_name', 'commit_id']);
296 pyroutes.register('repo_commit_download', '/%(repo_name)s/changeset-download/%(commit_id)s', ['repo_name', 'commit_id']);
296 pyroutes.register('repo_commit_download', '/%(repo_name)s/changeset-download/%(commit_id)s', ['repo_name', 'commit_id']);
297 pyroutes.register('repo_commit_parents', '/%(repo_name)s/changeset_parents/%(commit_id)s', ['repo_name', 'commit_id']);
297 pyroutes.register('repo_commit_parents', '/%(repo_name)s/changeset_parents/%(commit_id)s', ['repo_name', 'commit_id']);
298 pyroutes.register('repo_commit_patch', '/%(repo_name)s/changeset-patch/%(commit_id)s', ['repo_name', 'commit_id']);
298 pyroutes.register('repo_commit_patch', '/%(repo_name)s/changeset-patch/%(commit_id)s', ['repo_name', 'commit_id']);
299 pyroutes.register('repo_commit_raw', '/%(repo_name)s/changeset-diff/%(commit_id)s', ['repo_name', 'commit_id']);
299 pyroutes.register('repo_commit_raw', '/%(repo_name)s/changeset-diff/%(commit_id)s', ['repo_name', 'commit_id']);
300 pyroutes.register('repo_commit_raw_deprecated', '/%(repo_name)s/raw-changeset/%(commit_id)s', ['repo_name', 'commit_id']);
300 pyroutes.register('repo_commit_raw_deprecated', '/%(repo_name)s/raw-changeset/%(commit_id)s', ['repo_name', 'commit_id']);
301 pyroutes.register('repo_commits', '/%(repo_name)s/commits', ['repo_name']);
301 pyroutes.register('repo_commits', '/%(repo_name)s/commits', ['repo_name']);
302 pyroutes.register('repo_commits_elements', '/%(repo_name)s/commits_elements', ['repo_name']);
302 pyroutes.register('repo_commits_elements', '/%(repo_name)s/commits_elements', ['repo_name']);
303 pyroutes.register('repo_commits_elements_file', '/%(repo_name)s/commits_elements/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
303 pyroutes.register('repo_commits_elements_file', '/%(repo_name)s/commits_elements/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
304 pyroutes.register('repo_commits_file', '/%(repo_name)s/commits/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
304 pyroutes.register('repo_commits_file', '/%(repo_name)s/commits/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
305 pyroutes.register('repo_compare', '/%(repo_name)s/compare/%(source_ref_type)s@%(source_ref)s...%(target_ref_type)s@%(target_ref)s', ['repo_name', 'source_ref_type', 'source_ref', 'target_ref_type', 'target_ref']);
305 pyroutes.register('repo_compare', '/%(repo_name)s/compare/%(source_ref_type)s@%(source_ref)s...%(target_ref_type)s@%(target_ref)s', ['repo_name', 'source_ref_type', 'source_ref', 'target_ref_type', 'target_ref']);
306 pyroutes.register('repo_compare_select', '/%(repo_name)s/compare', ['repo_name']);
306 pyroutes.register('repo_compare_select', '/%(repo_name)s/compare', ['repo_name']);
307 pyroutes.register('repo_create', '/_admin/repos/create', []);
307 pyroutes.register('repo_create', '/_admin/repos/create', []);
308 pyroutes.register('repo_creating', '/%(repo_name)s/repo_creating', ['repo_name']);
308 pyroutes.register('repo_creating', '/%(repo_name)s/repo_creating', ['repo_name']);
309 pyroutes.register('repo_creating_check', '/%(repo_name)s/repo_creating_check', ['repo_name']);
309 pyroutes.register('repo_creating_check', '/%(repo_name)s/repo_creating_check', ['repo_name']);
310 pyroutes.register('repo_default_reviewers_data', '/%(repo_name)s/settings/review/default-reviewers', ['repo_name']);
310 pyroutes.register('repo_default_reviewers_data', '/%(repo_name)s/settings/review/default-reviewers', ['repo_name']);
311 pyroutes.register('repo_edit_toggle_locking', '/%(repo_name)s/settings/toggle_locking', ['repo_name']);
312 pyroutes.register('repo_file_authors', '/%(repo_name)s/authors/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
311 pyroutes.register('repo_file_authors', '/%(repo_name)s/authors/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
313 pyroutes.register('repo_file_download', '/%(repo_name)s/download/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
312 pyroutes.register('repo_file_download', '/%(repo_name)s/download/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
314 pyroutes.register('repo_file_download:legacy', '/%(repo_name)s/rawfile/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
313 pyroutes.register('repo_file_download:legacy', '/%(repo_name)s/rawfile/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
315 pyroutes.register('repo_file_history', '/%(repo_name)s/history/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
314 pyroutes.register('repo_file_history', '/%(repo_name)s/history/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
316 pyroutes.register('repo_file_raw', '/%(repo_name)s/raw/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
315 pyroutes.register('repo_file_raw', '/%(repo_name)s/raw/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
317 pyroutes.register('repo_files', '/%(repo_name)s/files/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
316 pyroutes.register('repo_files', '/%(repo_name)s/files/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
318 pyroutes.register('repo_files:annotated', '/%(repo_name)s/annotate/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
317 pyroutes.register('repo_files:annotated', '/%(repo_name)s/annotate/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
319 pyroutes.register('repo_files:annotated_previous', '/%(repo_name)s/annotate-previous/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
318 pyroutes.register('repo_files:annotated_previous', '/%(repo_name)s/annotate-previous/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
320 pyroutes.register('repo_files:default_commit', '/%(repo_name)s/files', ['repo_name']);
319 pyroutes.register('repo_files:default_commit', '/%(repo_name)s/files', ['repo_name']);
321 pyroutes.register('repo_files:default_path', '/%(repo_name)s/files/%(commit_id)s/', ['repo_name', 'commit_id']);
320 pyroutes.register('repo_files:default_path', '/%(repo_name)s/files/%(commit_id)s/', ['repo_name', 'commit_id']);
322 pyroutes.register('repo_files:rendered', '/%(repo_name)s/render/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
321 pyroutes.register('repo_files:rendered', '/%(repo_name)s/render/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
323 pyroutes.register('repo_files_add_file', '/%(repo_name)s/add_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
322 pyroutes.register('repo_files_add_file', '/%(repo_name)s/add_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
324 pyroutes.register('repo_files_check_head', '/%(repo_name)s/check_head/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
323 pyroutes.register('repo_files_check_head', '/%(repo_name)s/check_head/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
325 pyroutes.register('repo_files_create_file', '/%(repo_name)s/create_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
324 pyroutes.register('repo_files_create_file', '/%(repo_name)s/create_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
326 pyroutes.register('repo_files_delete_file', '/%(repo_name)s/delete_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
325 pyroutes.register('repo_files_delete_file', '/%(repo_name)s/delete_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
327 pyroutes.register('repo_files_diff', '/%(repo_name)s/diff/%(f_path)s', ['repo_name', 'f_path']);
326 pyroutes.register('repo_files_diff', '/%(repo_name)s/diff/%(f_path)s', ['repo_name', 'f_path']);
328 pyroutes.register('repo_files_diff_2way_redirect', '/%(repo_name)s/diff-2way/%(f_path)s', ['repo_name', 'f_path']);
327 pyroutes.register('repo_files_diff_2way_redirect', '/%(repo_name)s/diff-2way/%(f_path)s', ['repo_name', 'f_path']);
329 pyroutes.register('repo_files_edit_file', '/%(repo_name)s/edit_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
328 pyroutes.register('repo_files_edit_file', '/%(repo_name)s/edit_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
330 pyroutes.register('repo_files_nodelist', '/%(repo_name)s/nodelist/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
329 pyroutes.register('repo_files_nodelist', '/%(repo_name)s/nodelist/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
331 pyroutes.register('repo_files_remove_file', '/%(repo_name)s/remove_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
330 pyroutes.register('repo_files_remove_file', '/%(repo_name)s/remove_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
332 pyroutes.register('repo_files_update_file', '/%(repo_name)s/update_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
331 pyroutes.register('repo_files_update_file', '/%(repo_name)s/update_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
333 pyroutes.register('repo_files_upload_file', '/%(repo_name)s/upload_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
332 pyroutes.register('repo_files_upload_file', '/%(repo_name)s/upload_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
334 pyroutes.register('repo_fork_create', '/%(repo_name)s/fork/create', ['repo_name']);
333 pyroutes.register('repo_fork_create', '/%(repo_name)s/fork/create', ['repo_name']);
335 pyroutes.register('repo_fork_new', '/%(repo_name)s/fork', ['repo_name']);
334 pyroutes.register('repo_fork_new', '/%(repo_name)s/fork', ['repo_name']);
336 pyroutes.register('repo_forks_data', '/%(repo_name)s/forks/data', ['repo_name']);
335 pyroutes.register('repo_forks_data', '/%(repo_name)s/forks/data', ['repo_name']);
337 pyroutes.register('repo_forks_show_all', '/%(repo_name)s/forks', ['repo_name']);
336 pyroutes.register('repo_forks_show_all', '/%(repo_name)s/forks', ['repo_name']);
338 pyroutes.register('repo_group_create', '/_admin/repo_group/create', []);
337 pyroutes.register('repo_group_create', '/_admin/repo_group/create', []);
339 pyroutes.register('repo_group_home', '/%(repo_group_name)s', ['repo_group_name']);
338 pyroutes.register('repo_group_home', '/%(repo_group_name)s', ['repo_group_name']);
340 pyroutes.register('repo_group_home_slash', '/%(repo_group_name)s/', ['repo_group_name']);
339 pyroutes.register('repo_group_home_slash', '/%(repo_group_name)s/', ['repo_group_name']);
341 pyroutes.register('repo_group_integrations_create', '/%(repo_group_name)s/_settings/integrations/%(integration)s/new', ['repo_group_name', 'integration']);
340 pyroutes.register('repo_group_integrations_create', '/%(repo_group_name)s/_settings/integrations/%(integration)s/new', ['repo_group_name', 'integration']);
342 pyroutes.register('repo_group_integrations_edit', '/%(repo_group_name)s/_settings/integrations/%(integration)s/%(integration_id)s', ['repo_group_name', 'integration', 'integration_id']);
341 pyroutes.register('repo_group_integrations_edit', '/%(repo_group_name)s/_settings/integrations/%(integration)s/%(integration_id)s', ['repo_group_name', 'integration', 'integration_id']);
343 pyroutes.register('repo_group_integrations_home', '/%(repo_group_name)s/_settings/integrations', ['repo_group_name']);
342 pyroutes.register('repo_group_integrations_home', '/%(repo_group_name)s/_settings/integrations', ['repo_group_name']);
344 pyroutes.register('repo_group_integrations_list', '/%(repo_group_name)s/_settings/integrations/%(integration)s', ['repo_group_name', 'integration']);
343 pyroutes.register('repo_group_integrations_list', '/%(repo_group_name)s/_settings/integrations/%(integration)s', ['repo_group_name', 'integration']);
345 pyroutes.register('repo_group_integrations_new', '/%(repo_group_name)s/_settings/integrations/new', ['repo_group_name']);
344 pyroutes.register('repo_group_integrations_new', '/%(repo_group_name)s/_settings/integrations/new', ['repo_group_name']);
346 pyroutes.register('repo_group_list_data', '/_repo_groups', []);
345 pyroutes.register('repo_group_list_data', '/_repo_groups', []);
347 pyroutes.register('repo_group_new', '/_admin/repo_group/new', []);
346 pyroutes.register('repo_group_new', '/_admin/repo_group/new', []);
348 pyroutes.register('repo_groups', '/_admin/repo_groups', []);
347 pyroutes.register('repo_groups', '/_admin/repo_groups', []);
349 pyroutes.register('repo_groups_data', '/_admin/repo_groups_data', []);
348 pyroutes.register('repo_groups_data', '/_admin/repo_groups_data', []);
350 pyroutes.register('repo_integrations_create', '/%(repo_name)s/settings/integrations/%(integration)s/new', ['repo_name', 'integration']);
349 pyroutes.register('repo_integrations_create', '/%(repo_name)s/settings/integrations/%(integration)s/new', ['repo_name', 'integration']);
351 pyroutes.register('repo_integrations_edit', '/%(repo_name)s/settings/integrations/%(integration)s/%(integration_id)s', ['repo_name', 'integration', 'integration_id']);
350 pyroutes.register('repo_integrations_edit', '/%(repo_name)s/settings/integrations/%(integration)s/%(integration_id)s', ['repo_name', 'integration', 'integration_id']);
352 pyroutes.register('repo_integrations_home', '/%(repo_name)s/settings/integrations', ['repo_name']);
351 pyroutes.register('repo_integrations_home', '/%(repo_name)s/settings/integrations', ['repo_name']);
353 pyroutes.register('repo_integrations_list', '/%(repo_name)s/settings/integrations/%(integration)s', ['repo_name', 'integration']);
352 pyroutes.register('repo_integrations_list', '/%(repo_name)s/settings/integrations/%(integration)s', ['repo_name', 'integration']);
354 pyroutes.register('repo_integrations_new', '/%(repo_name)s/settings/integrations/new', ['repo_name']);
353 pyroutes.register('repo_integrations_new', '/%(repo_name)s/settings/integrations/new', ['repo_name']);
355 pyroutes.register('repo_list_data', '/_repos', []);
354 pyroutes.register('repo_list_data', '/_repos', []);
356 pyroutes.register('repo_new', '/_admin/repos/new', []);
355 pyroutes.register('repo_new', '/_admin/repos/new', []);
357 pyroutes.register('repo_nodetree_full', '/%(repo_name)s/nodetree_full/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
356 pyroutes.register('repo_nodetree_full', '/%(repo_name)s/nodetree_full/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
358 pyroutes.register('repo_nodetree_full:default_path', '/%(repo_name)s/nodetree_full/%(commit_id)s/', ['repo_name', 'commit_id']);
357 pyroutes.register('repo_nodetree_full:default_path', '/%(repo_name)s/nodetree_full/%(commit_id)s/', ['repo_name', 'commit_id']);
359 pyroutes.register('repo_refs_changelog_data', '/%(repo_name)s/refs-data-changelog', ['repo_name']);
358 pyroutes.register('repo_refs_changelog_data', '/%(repo_name)s/refs-data-changelog', ['repo_name']);
360 pyroutes.register('repo_refs_data', '/%(repo_name)s/refs-data', ['repo_name']);
359 pyroutes.register('repo_refs_data', '/%(repo_name)s/refs-data', ['repo_name']);
361 pyroutes.register('repo_reviewers', '/%(repo_name)s/settings/review/rules', ['repo_name']);
360 pyroutes.register('repo_reviewers', '/%(repo_name)s/settings/review/rules', ['repo_name']);
362 pyroutes.register('repo_reviewers_review_rule_delete', '/%(repo_name)s/settings/review/rules/%(rule_id)s/delete', ['repo_name', 'rule_id']);
361 pyroutes.register('repo_reviewers_review_rule_delete', '/%(repo_name)s/settings/review/rules/%(rule_id)s/delete', ['repo_name', 'rule_id']);
363 pyroutes.register('repo_reviewers_review_rule_edit', '/%(repo_name)s/settings/review/rules/%(rule_id)s', ['repo_name', 'rule_id']);
362 pyroutes.register('repo_reviewers_review_rule_edit', '/%(repo_name)s/settings/review/rules/%(rule_id)s', ['repo_name', 'rule_id']);
364 pyroutes.register('repo_reviewers_review_rule_new', '/%(repo_name)s/settings/review/rules/new', ['repo_name']);
363 pyroutes.register('repo_reviewers_review_rule_new', '/%(repo_name)s/settings/review/rules/new', ['repo_name']);
364 pyroutes.register('repo_settings_quick_actions', '/%(repo_name)s/settings/quick-action', ['repo_name']);
365 pyroutes.register('repo_stats', '/%(repo_name)s/repo_stats/%(commit_id)s', ['repo_name', 'commit_id']);
365 pyroutes.register('repo_stats', '/%(repo_name)s/repo_stats/%(commit_id)s', ['repo_name', 'commit_id']);
366 pyroutes.register('repo_summary', '/%(repo_name)s', ['repo_name']);
366 pyroutes.register('repo_summary', '/%(repo_name)s', ['repo_name']);
367 pyroutes.register('repo_summary_commits', '/%(repo_name)s/summary-commits', ['repo_name']);
367 pyroutes.register('repo_summary_commits', '/%(repo_name)s/summary-commits', ['repo_name']);
368 pyroutes.register('repo_summary_explicit', '/%(repo_name)s/summary', ['repo_name']);
368 pyroutes.register('repo_summary_explicit', '/%(repo_name)s/summary', ['repo_name']);
369 pyroutes.register('repo_summary_slash', '/%(repo_name)s/', ['repo_name']);
369 pyroutes.register('repo_summary_slash', '/%(repo_name)s/', ['repo_name']);
370 pyroutes.register('repos', '/_admin/repos', []);
370 pyroutes.register('repos', '/_admin/repos', []);
371 pyroutes.register('repos_data', '/_admin/repos_data', []);
371 pyroutes.register('repos_data', '/_admin/repos_data', []);
372 pyroutes.register('reset_password', '/_admin/password_reset', []);
372 pyroutes.register('reset_password', '/_admin/password_reset', []);
373 pyroutes.register('reset_password_confirmation', '/_admin/password_reset_confirmation', []);
373 pyroutes.register('reset_password_confirmation', '/_admin/password_reset_confirmation', []);
374 pyroutes.register('robots', '/robots.txt', []);
374 pyroutes.register('robots', '/robots.txt', []);
375 pyroutes.register('rss_feed_home', '/%(repo_name)s/feed-rss', ['repo_name']);
375 pyroutes.register('rss_feed_home', '/%(repo_name)s/feed-rss', ['repo_name']);
376 pyroutes.register('rss_feed_home_old', '/%(repo_name)s/feed/rss', ['repo_name']);
376 pyroutes.register('rss_feed_home_old', '/%(repo_name)s/feed/rss', ['repo_name']);
377 pyroutes.register('search', '/_admin/search', []);
377 pyroutes.register('search', '/_admin/search', []);
378 pyroutes.register('search_repo', '/%(repo_name)s/_search', ['repo_name']);
378 pyroutes.register('search_repo', '/%(repo_name)s/_search', ['repo_name']);
379 pyroutes.register('search_repo_alt', '/%(repo_name)s/search', ['repo_name']);
379 pyroutes.register('search_repo_alt', '/%(repo_name)s/search', ['repo_name']);
380 pyroutes.register('search_repo_group', '/%(repo_group_name)s/_search', ['repo_group_name']);
380 pyroutes.register('search_repo_group', '/%(repo_group_name)s/_search', ['repo_group_name']);
381 pyroutes.register('store_user_session_value', '/_store_session_attr', []);
381 pyroutes.register('store_user_session_value', '/_store_session_attr', []);
382 pyroutes.register('strip_check', '/%(repo_name)s/settings/strip_check', ['repo_name']);
382 pyroutes.register('strip_check', '/%(repo_name)s/settings/strip_check', ['repo_name']);
383 pyroutes.register('strip_execute', '/%(repo_name)s/settings/strip_execute', ['repo_name']);
383 pyroutes.register('strip_execute', '/%(repo_name)s/settings/strip_execute', ['repo_name']);
384 pyroutes.register('tags_home', '/%(repo_name)s/tags', ['repo_name']);
384 pyroutes.register('tags_home', '/%(repo_name)s/tags', ['repo_name']);
385 pyroutes.register('toggle_following', '/_admin/toggle_following', []);
385 pyroutes.register('toggle_following', '/_admin/toggle_following', []);
386 pyroutes.register('upload_file', '/_file_store/upload', []);
386 pyroutes.register('upload_file', '/_file_store/upload', []);
387 pyroutes.register('user_autocomplete_data', '/_users', []);
387 pyroutes.register('user_autocomplete_data', '/_users', []);
388 pyroutes.register('user_create_personal_repo_group', '/_admin/users/%(user_id)s/create_repo_group', ['user_id']);
388 pyroutes.register('user_create_personal_repo_group', '/_admin/users/%(user_id)s/create_repo_group', ['user_id']);
389 pyroutes.register('user_delete', '/_admin/users/%(user_id)s/delete', ['user_id']);
389 pyroutes.register('user_delete', '/_admin/users/%(user_id)s/delete', ['user_id']);
390 pyroutes.register('user_disable_force_password_reset', '/_admin/users/%(user_id)s/password_reset_disable', ['user_id']);
390 pyroutes.register('user_disable_force_password_reset', '/_admin/users/%(user_id)s/password_reset_disable', ['user_id']);
391 pyroutes.register('user_edit', '/_admin/users/%(user_id)s/edit', ['user_id']);
391 pyroutes.register('user_edit', '/_admin/users/%(user_id)s/edit', ['user_id']);
392 pyroutes.register('user_edit_advanced', '/_admin/users/%(user_id)s/edit/advanced', ['user_id']);
392 pyroutes.register('user_edit_advanced', '/_admin/users/%(user_id)s/edit/advanced', ['user_id']);
393 pyroutes.register('user_edit_global_perms', '/_admin/users/%(user_id)s/edit/global_permissions', ['user_id']);
393 pyroutes.register('user_edit_global_perms', '/_admin/users/%(user_id)s/edit/global_permissions', ['user_id']);
394 pyroutes.register('user_edit_global_perms_update', '/_admin/users/%(user_id)s/edit/global_permissions/update', ['user_id']);
394 pyroutes.register('user_edit_global_perms_update', '/_admin/users/%(user_id)s/edit/global_permissions/update', ['user_id']);
395 pyroutes.register('user_enable_force_password_reset', '/_admin/users/%(user_id)s/password_reset_enable', ['user_id']);
395 pyroutes.register('user_enable_force_password_reset', '/_admin/users/%(user_id)s/password_reset_enable', ['user_id']);
396 pyroutes.register('user_group_autocomplete_data', '/_user_groups', []);
396 pyroutes.register('user_group_autocomplete_data', '/_user_groups', []);
397 pyroutes.register('user_group_members_data', '/_admin/user_groups/%(user_group_id)s/members', ['user_group_id']);
397 pyroutes.register('user_group_members_data', '/_admin/user_groups/%(user_group_id)s/members', ['user_group_id']);
398 pyroutes.register('user_group_profile', '/_profile_user_group/%(user_group_name)s', ['user_group_name']);
398 pyroutes.register('user_group_profile', '/_profile_user_group/%(user_group_name)s', ['user_group_name']);
399 pyroutes.register('user_groups', '/_admin/user_groups', []);
399 pyroutes.register('user_groups', '/_admin/user_groups', []);
400 pyroutes.register('user_groups_create', '/_admin/user_groups/create', []);
400 pyroutes.register('user_groups_create', '/_admin/user_groups/create', []);
401 pyroutes.register('user_groups_data', '/_admin/user_groups_data', []);
401 pyroutes.register('user_groups_data', '/_admin/user_groups_data', []);
402 pyroutes.register('user_groups_delete', '/_admin/user_groups/%(user_group_id)s/delete', ['user_group_id']);
402 pyroutes.register('user_groups_delete', '/_admin/user_groups/%(user_group_id)s/delete', ['user_group_id']);
403 pyroutes.register('user_groups_new', '/_admin/user_groups/new', []);
403 pyroutes.register('user_groups_new', '/_admin/user_groups/new', []);
404 pyroutes.register('user_groups_update', '/_admin/user_groups/%(user_group_id)s/update', ['user_group_id']);
404 pyroutes.register('user_groups_update', '/_admin/user_groups/%(user_group_id)s/update', ['user_group_id']);
405 pyroutes.register('user_notice_dismiss', '/_admin/users/%(user_id)s/notice_dismiss', ['user_id']);
405 pyroutes.register('user_notice_dismiss', '/_admin/users/%(user_id)s/notice_dismiss', ['user_id']);
406 pyroutes.register('user_profile', '/_profiles/%(username)s', ['username']);
406 pyroutes.register('user_profile', '/_profiles/%(username)s', ['username']);
407 pyroutes.register('user_update', '/_admin/users/%(user_id)s/update', ['user_id']);
407 pyroutes.register('user_update', '/_admin/users/%(user_id)s/update', ['user_id']);
408 pyroutes.register('users', '/_admin/users', []);
408 pyroutes.register('users', '/_admin/users', []);
409 pyroutes.register('users_create', '/_admin/users/create', []);
409 pyroutes.register('users_create', '/_admin/users/create', []);
410 pyroutes.register('users_data', '/_admin/users_data', []);
410 pyroutes.register('users_data', '/_admin/users_data', []);
411 pyroutes.register('users_new', '/_admin/users/new', []);
411 pyroutes.register('users_new', '/_admin/users/new', []);
412 }
412 }
@@ -1,38 +1,41 b''
1 <%inherit file="/base/base.mako"/>
1 <%inherit file="/base/base.mako"/>
2
2
3 <%def name="title()">
3 <%def name="title()">
4 ${_('Artifacts Admin')}
4 ${_('Artifacts Admin')}
5 %if c.rhodecode_name:
5 %if c.rhodecode_name:
6 &middot; ${h.branding(c.rhodecode_name)}
6 &middot; ${h.branding(c.rhodecode_name)}
7 %endif
7 %endif
8 </%def>
8 </%def>
9
9
10 <%def name="breadcrumbs_links()"></%def>
10 <%def name="breadcrumbs_links()"></%def>
11
11
12 <%def name="menu_bar_nav()">
12 <%def name="menu_bar_nav()">
13 ${self.menu_items(active='admin')}
13 ${self.menu_items(active='admin')}
14 </%def>
14 </%def>
15
15
16 <%def name="menu_bar_subnav()">
16 <%def name="menu_bar_subnav()">
17 ${self.admin_menu(active='artifacts')}
17 ${self.admin_menu(active='artifacts')}
18 </%def>
18 </%def>
19
19
20 <%def name="main()">
20 <%def name="main()">
21
21
22 <div class="box">
22 <div class="box">
23
23
24 <div class="panel panel-default">
24 <div class="panel panel-default">
25 <div class="panel-heading">
25 <div class="panel-heading">
26 <h3 class="panel-title">${_('Artifacts Administration.')}</h3>
26 <h3 class="panel-title">${_('Artifacts Administration.')}</h3>
27 </div>
27 </div>
28 <div class="panel-body">
28 <div class="panel-body">
29 <h4>${_('This feature is available in RhodeCode EE edition only. Contact {sales_email} to obtain a trial license.').format(sales_email='<a href="mailto:sales@rhodecode.com">sales@rhodecode.com</a>')|n}</h4>
29 <h4>${_('This feature is available in RhodeCode EE edition only. Contact {sales_email} to obtain a trial license.').format(sales_email='<a href="mailto:sales@rhodecode.com">sales@rhodecode.com</a>')|n}</h4>
30
30 <p>
31 Artifacts are a binary file storage within RhodeCode that allows asset management next to version control system with fine-grained access control.
32 This functionality allows release builds or other types of binary asset to be stored and managed by RhodeCode.
33 </p>
31 </div>
34 </div>
32 </div>
35 </div>
33
36
34 </div>
37 </div>
35
38
36
39
37 </%def>
40 </%def>
38
41
@@ -1,9 +1,37 b''
1 <div class="panel panel-default">
1 <%inherit file="/base/base.mako"/>
2 <div class="panel-heading">
2
3 <h3 class="panel-title">${_('Admin Automation')}</h3>
3 <%def name="title()">
4 ${_('Artifacts Admin')}
5 %if c.rhodecode_name:
6 &middot; ${h.branding(c.rhodecode_name)}
7 %endif
8 </%def>
9
10 <%def name="breadcrumbs_links()"></%def>
11
12 <%def name="menu_bar_nav()">
13 ${self.menu_items(active='admin')}
14 </%def>
15
16 <%def name="menu_bar_subnav()">
17 ${self.admin_menu(active='automation')}
18 </%def>
19
20 <%def name="main()">
21
22 <div class="box">
23
24 <div class="panel panel-default">
25 <div class="panel-heading">
26 <h3 class="panel-title">${_('Automation Administration.')}</h3>
27 </div>
28 <div class="panel-body">
29 <h4>${_('This feature is available in RhodeCode EE edition only. Contact {sales_email} to obtain a trial license.').format(sales_email='<a href="mailto:sales@rhodecode.com">sales@rhodecode.com</a>')|n}</h4>
30 <img alt="admin-automation" style="width: 100%; height: 100%" src="${h.asset('images/ee_features/admin_automation.png')}"/>
31 </div>
4 </div>
32 </div>
5 <div class="panel-body">
33
6 <h4>${_('This feature is available in RhodeCode EE edition only. Contact {sales_email} to obtain a trial license.').format(sales_email='<a href="mailto:sales@rhodecode.com">sales@rhodecode.com</a>')|n}</h4>
7 <img style="width: 100%; height: 100%" src="${h.asset('images/ee_features/admin_automation.png')}"/>
8 </div>
9 </div>
34 </div>
35
36
37 </%def>
@@ -1,147 +1,147 b''
1 <%inherit file="/base/base.mako"/>
1 <%inherit file="/base/base.mako"/>
2
2
3 <%def name="title()">
3 <%def name="title()">
4 %if c.show_private:
4 %if c.show_private:
5 ${_('Private Gists for user {}').format(c.rhodecode_user.username)}
5 ${_('Private Gists for user {}').format(c.rhodecode_user.username)}
6 %elif c.show_public:
6 %elif c.show_public:
7 ${_('Public Gists for user {}').format(c.rhodecode_user.username)}
7 ${_('Public Gists for user {}').format(c.rhodecode_user.username)}
8 %else:
8 %else:
9 ${_('Public Gists')}
9 ${_('Public Gists')}
10 %endif
10 %endif
11 %if c.rhodecode_name:
11 %if c.rhodecode_name:
12 &middot; ${h.branding(c.rhodecode_name)}
12 &middot; ${h.branding(c.rhodecode_name)}
13 %endif
13 %endif
14 </%def>
14 </%def>
15
15
16 <%def name="breadcrumbs_links()"></%def>
16 <%def name="breadcrumbs_links()"></%def>
17
17
18 <%def name="menu_bar_nav()">
18 <%def name="menu_bar_nav()">
19 ${self.menu_items(active='gists')}
19 ${self.menu_items(active='gists')}
20 </%def>
20 </%def>
21
21
22 <%def name="main()">
22 <%def name="main()">
23
23
24 <div class="box">
24 <div class="box">
25 <div class="title">
25 <div class="title">
26
26
27 <ul class="button-links">
27 <ul class="button-links">
28 % if c.is_super_admin:
28 % if c.is_super_admin:
29 <li><a class="btn ${h.is_active('all', c.active)}" href="${h.route_path('gists_show', _query={'all': 1})}">${_('All gists')}</a></li>
29 <li><a class="btn ${h.is_active('all', c.active)}" href="${h.route_path('gists_show', _query={'all': 1})}">${_('All gists')}</a></li>
30 %endif
30 %endif
31 <li><a class="btn ${h.is_active('public', c.active)}" href="${h.route_path('gists_show')}">${_('All public')}</a></li>
31 <li><a class="btn ${h.is_active('public', c.active)}" href="${h.route_path('gists_show')}">${_('All public')}</a></li>
32 %if c.rhodecode_user.username != h.DEFAULT_USER:
32 %if c.rhodecode_user.username != h.DEFAULT_USER:
33 <li><a class="btn ${h.is_active('my_all', c.active)}" href="${h.route_path('gists_show', _query={'public':1, 'private': 1})}">${_('My gists')}</a></li>
33 <li><a class="btn ${h.is_active('my_all', c.active)}" href="${h.route_path('gists_show', _query={'public':1, 'private': 1})}">${_('My gists')}</a></li>
34 <li><a class="btn ${h.is_active('my_private', c.active)}" href="${h.route_path('gists_show', _query={'private': 1})}">${_('My private')}</a></li>
34 <li><a class="btn ${h.is_active('my_private', c.active)}" href="${h.route_path('gists_show', _query={'private': 1})}">${_('My private')}</a></li>
35 <li><a class="btn ${h.is_active('my_public', c.active)}" href="${h.route_path('gists_show', _query={'public': 1})}">${_('My public')}</a></li>
35 <li><a class="btn ${h.is_active('my_public', c.active)}" href="${h.route_path('gists_show', _query={'public': 1})}">${_('My public')}</a></li>
36 %endif
36 %endif
37 </ul>
37 </ul>
38
38
39 % if c.rhodecode_user.username != h.DEFAULT_USER:
39 % if c.rhodecode_user.username != h.DEFAULT_USER:
40 <div class="pull-right">
40 <div class="pull-right">
41 <a class="btn btn-primary" href="${h.route_path('gists_new')}" >
41 <a class="btn btn-primary" href="${h.route_path('gists_new')}" >
42 ${_(u'Create New Gist')}
42 ${_('Create New Gist')}
43 </a>
43 </a>
44 </div>
44 </div>
45 % endif
45 % endif
46
46
47 <div class="grid-quick-filter">
47 <div class="grid-quick-filter">
48 <ul class="grid-filter-box">
48 <ul class="grid-filter-box">
49 <li class="grid-filter-box-icon">
49 <li class="grid-filter-box-icon">
50 <i class="icon-search"></i>
50 <i class="icon-search"></i>
51 </li>
51 </li>
52 <li class="grid-filter-box-input">
52 <li class="grid-filter-box-input">
53 <input class="q_filter_box" id="q_filter" size="15" type="text" name="filter" placeholder="${_('quick filter...')}" value=""/>
53 <input class="q_filter_box" id="q_filter" size="15" type="text" name="filter" placeholder="${_('quick filter...')}" value=""/>
54 </li>
54 </li>
55 </ul>
55 </ul>
56 </div>
56 </div>
57
57
58 </div>
58 </div>
59
59
60 <div class="main-content-full-width">
60 <div class="main-content-full-width">
61 <div id="repos_list_wrap">
61 <div id="repos_list_wrap">
62 <table id="gist_list_table" class="display"></table>
62 <table id="gist_list_table" class="display"></table>
63 </div>
63 </div>
64 </div>
64 </div>
65
65
66 </div>
66 </div>
67
67
68 <script type="text/javascript">
68 <script type="text/javascript">
69 $(document).ready(function() {
69 $(document).ready(function() {
70
70
71 var get_datatable_count = function(){
71 var get_datatable_count = function(){
72 var api = $('#gist_list_table').dataTable().api();
72 var api = $('#gist_list_table').dataTable().api();
73 $('#gists_count').text(api.page.info().recordsDisplay);
73 $('#gists_count').text(api.page.info().recordsDisplay);
74 };
74 };
75
75
76
76
77 // custom filter that filters by access_id, description or author
77 // custom filter that filters by access_id, description or author
78 $.fn.dataTable.ext.search.push(
78 $.fn.dataTable.ext.search.push(
79 function( settings, data, dataIndex ) {
79 function( settings, data, dataIndex ) {
80 var query = $('#q_filter').val();
80 var query = $('#q_filter').val();
81 var author = data[0].strip();
81 var author = data[0].strip();
82 var access_id = data[2].strip();
82 var access_id = data[2].strip();
83 var description = data[3].strip();
83 var description = data[3].strip();
84
84
85 var query_str = (access_id + " " + author + " " + description).toLowerCase();
85 var query_str = (access_id + " " + author + " " + description).toLowerCase();
86
86
87 if(query_str.indexOf(query.toLowerCase()) !== -1){
87 if(query_str.indexOf(query.toLowerCase()) !== -1){
88 return true;
88 return true;
89 }
89 }
90 return false;
90 return false;
91 }
91 }
92 );
92 );
93
93
94 // gists list
94 // gists list
95 var gist_data = ${c.data|n};
95 var gist_data = ${c.data|n};
96 $('#gist_list_table').DataTable({
96 $('#gist_list_table').DataTable({
97 data: gist_data,
97 data: gist_data,
98 dom: 'rtp',
98 dom: 'rtp',
99 pageLength: ${c.visual.dashboard_items},
99 pageLength: ${c.visual.dashboard_items},
100 order: [[ 4, "desc" ]],
100 order: [[ 4, "desc" ]],
101 columns: [
101 columns: [
102 { data: {"_": "author",
102 { data: {"_": "author",
103 "sort": "author_raw"}, title: "${_("Author")}", width: "250px", className: "td-user" },
103 "sort": "author_raw"}, title: "${_("Author")}", width: "250px", className: "td-user" },
104 { data: {"_": "type",
104 { data: {"_": "type",
105 "sort": "type"}, title: "${_("Type")}", width: "100px", className: "td-gist-type" },
105 "sort": "type"}, title: "${_("Type")}", width: "100px", className: "td-gist-type" },
106 { data: {"_": "access_id",
106 { data: {"_": "access_id",
107 "sort": "access_id"}, title: "${_("Name")}", width:"150px", className: "td-componentname" },
107 "sort": "access_id"}, title: "${_("Name")}", width:"150px", className: "td-componentname" },
108 { data: {"_": "description",
108 { data: {"_": "description",
109 "sort": "description"}, title: "${_("Description")}", width: "250px", className: "td-description" },
109 "sort": "description"}, title: "${_("Description")}", width: "250px", className: "td-description" },
110 { data: {"_": "created_on",
110 { data: {"_": "created_on",
111 "sort": "created_on_raw"}, title: "${_("Created on")}", className: "td-time" },
111 "sort": "created_on_raw"}, title: "${_("Created on")}", className: "td-time" },
112 { data: {"_": "expires",
112 { data: {"_": "expires",
113 "sort": "expires"}, title: "${_("Expires")}", width: "200px", className: "td-expire" }
113 "sort": "expires"}, title: "${_("Expires")}", width: "200px", className: "td-expire" }
114 ],
114 ],
115 language: {
115 language: {
116 paginate: DEFAULT_GRID_PAGINATION,
116 paginate: DEFAULT_GRID_PAGINATION,
117 emptyTable: _gettext("No gists available yet.")
117 emptyTable: _gettext("No gists available yet.")
118 },
118 },
119 "initComplete": function( settings, json ) {
119 "initComplete": function( settings, json ) {
120 timeagoActivate();
120 timeagoActivate();
121 tooltipActivate();
121 tooltipActivate();
122 get_datatable_count();
122 get_datatable_count();
123 }
123 }
124 });
124 });
125
125
126 // update the counter when things change
126 // update the counter when things change
127 $('#gist_list_table').on('draw.dt', function() {
127 $('#gist_list_table').on('draw.dt', function() {
128 timeagoActivate();
128 timeagoActivate();
129 tooltipActivate();
129 tooltipActivate();
130 get_datatable_count();
130 get_datatable_count();
131 });
131 });
132
132
133 // filter, filter both grids
133 // filter, filter both grids
134 $('#q_filter').on( 'keyup', function () {
134 $('#q_filter').on( 'keyup', function () {
135 var repo_api = $('#gist_list_table').dataTable().api();
135 var repo_api = $('#gist_list_table').dataTable().api();
136 repo_api
136 repo_api
137 .draw();
137 .draw();
138 });
138 });
139
139
140 // refilter table if page load via back button
140 // refilter table if page load via back button
141 $("#q_filter").trigger('keyup');
141 $("#q_filter").trigger('keyup');
142
142
143 });
143 });
144
144
145 </script>
145 </script>
146 </%def>
146 </%def>
147
147
@@ -1,219 +1,219 b''
1 <%inherit file="base.mako"/>
1 <%inherit file="base.mako"/>
2
2
3 <%def name="breadcrumbs_links()">
3 <%def name="breadcrumbs_links()">
4 %if c.repo:
4 %if c.repo:
5 ${h.link_to('Settings',h.route_path('edit_repo', repo_name=c.repo.repo_name))}
5 ${h.link_to('Settings',h.route_path('edit_repo', repo_name=c.repo.repo_name))}
6 %elif c.repo_group:
6 %elif c.repo_group:
7 ${h.link_to(_('Admin'),h.route_path('admin_home'))}
7 ${h.link_to(_('Admin'),h.route_path('admin_home'))}
8 &raquo;
8 &raquo;
9 ${h.link_to(_('Repository Groups'),h.route_path('repo_groups'))}
9 ${h.link_to(_('Repository Groups'),h.route_path('repo_groups'))}
10 &raquo;
10 &raquo;
11 ${h.link_to(c.repo_group.group_name,h.route_path('edit_repo_group', repo_group_name=c.repo_group.group_name))}
11 ${h.link_to(c.repo_group.group_name,h.route_path('edit_repo_group', repo_group_name=c.repo_group.group_name))}
12 %else:
12 %else:
13 ${h.link_to(_('Admin'),h.route_path('admin_home'))}
13 ${h.link_to(_('Admin'),h.route_path('admin_home'))}
14 &raquo;
14 &raquo;
15 ${h.link_to(_('Settings'),h.route_path('admin_settings'))}
15 ${h.link_to(_('Settings'),h.route_path('admin_settings'))}
16 %endif
16 %endif
17 %if c.current_IntegrationType:
17 %if c.current_IntegrationType:
18 &raquo;
18 &raquo;
19 %if c.repo:
19 %if c.repo:
20 ${h.link_to(_('Integrations'),
20 ${h.link_to(_('Integrations'),
21 request.route_path(route_name='repo_integrations_home',
21 request.route_path(route_name='repo_integrations_home',
22 repo_name=c.repo.repo_name))}
22 repo_name=c.repo.repo_name))}
23 %elif c.repo_group:
23 %elif c.repo_group:
24 ${h.link_to(_('Integrations'),
24 ${h.link_to(_('Integrations'),
25 request.route_path(route_name='repo_group_integrations_home',
25 request.route_path(route_name='repo_group_integrations_home',
26 repo_group_name=c.repo_group.group_name))}
26 repo_group_name=c.repo_group.group_name))}
27 %else:
27 %else:
28 ${h.link_to(_('Integrations'),
28 ${h.link_to(_('Integrations'),
29 request.route_path(route_name='global_integrations_home'))}
29 request.route_path(route_name='global_integrations_home'))}
30 %endif
30 %endif
31 &raquo;
31 &raquo;
32 ${c.current_IntegrationType.display_name}
32 ${c.current_IntegrationType.display_name}
33 %else:
33 %else:
34 &raquo;
34 &raquo;
35 ${_('Integrations')}
35 ${_('Integrations')}
36 %endif
36 %endif
37 </%def>
37 </%def>
38
38
39 <div class="panel panel-default">
39 <div class="panel panel-default">
40 <div class="panel-heading">
40 <div class="panel-heading">
41 <h3 class="panel-title">
41 <h3 class="panel-title">
42 %if c.repo:
42 %if c.repo:
43 ${_('Current Integrations for Repository: {repo_name}').format(repo_name=c.repo.repo_name)}
43 ${_('Current Integrations for Repository: {repo_name}').format(repo_name=c.repo.repo_name)}
44 %elif c.repo_group:
44 %elif c.repo_group:
45 ${_('Repository Group Integrations: {}').format(c.repo_group.group_name)}</h3>
45 ${_('Repository Group Integrations: {}').format(c.repo_group.group_name)}</h3>
46 %else:
46 %else:
47 ${_('Current Integrations')}
47 ${_('Current Integrations')}
48 %endif
48 %endif
49 </h3>
49 </h3>
50 </div>
50 </div>
51 <div class="panel-body">
51 <div class="panel-body">
52
52
53 <%
53 <%
54 integration_type = c.current_IntegrationType and c.current_IntegrationType.display_name or ''
54 integration_type = c.current_IntegrationType and c.current_IntegrationType.display_name or ''
55
55
56 if c.repo:
56 if c.repo:
57 create_url = h.route_path('repo_integrations_new', repo_name=c.repo.repo_name)
57 create_url = h.route_path('repo_integrations_new', repo_name=c.repo.repo_name)
58 elif c.repo_group:
58 elif c.repo_group:
59 create_url = h.route_path('repo_group_integrations_new', repo_group_name=c.repo_group.group_name)
59 create_url = h.route_path('repo_group_integrations_new', repo_group_name=c.repo_group.group_name)
60 else:
60 else:
61 create_url = h.route_path('global_integrations_new')
61 create_url = h.route_path('global_integrations_new')
62 %>
62 %>
63 <p class="pull-right">
63 <p class="pull-right">
64 <a href="${create_url}" class="btn btn-small btn-success">${_(u'Create new integration')}</a>
64 <a href="${create_url}" class="btn btn-small btn-success">${_('Create new integration')}</a>
65 </p>
65 </p>
66
66
67 <table class="rctable integrations">
67 <table class="rctable integrations">
68 <thead>
68 <thead>
69 <tr>
69 <tr>
70 <th><a href="?sort=enabled:${c.rev_sort_dir}">${_('Enabled')}</a></th>
70 <th><a href="?sort=enabled:${c.rev_sort_dir}">${_('Enabled')}</a></th>
71 <th><a href="?sort=name:${c.rev_sort_dir}">${_('Name')}</a></th>
71 <th><a href="?sort=name:${c.rev_sort_dir}">${_('Name')}</a></th>
72 <th colspan="2"><a href="?sort=integration_type:${c.rev_sort_dir}">${_('Type')}</a></th>
72 <th colspan="2"><a href="?sort=integration_type:${c.rev_sort_dir}">${_('Type')}</a></th>
73 <th><a href="?sort=scope:${c.rev_sort_dir}">${_('Scope')}</a></th>
73 <th><a href="?sort=scope:${c.rev_sort_dir}">${_('Scope')}</a></th>
74 <th>${_('Actions')}</th>
74 <th>${_('Actions')}</th>
75 <th></th>
75 <th></th>
76 </tr>
76 </tr>
77 </thead>
77 </thead>
78 <tbody>
78 <tbody>
79 %if not c.integrations_list:
79 %if not c.integrations_list:
80 <tr>
80 <tr>
81 <td colspan="7">
81 <td colspan="7">
82
82
83 %if c.repo:
83 %if c.repo:
84 ${_('No {type} integrations for repo {repo} exist yet.').format(type=integration_type, repo=c.repo.repo_name)}
84 ${_('No {type} integrations for repo {repo} exist yet.').format(type=integration_type, repo=c.repo.repo_name)}
85 %elif c.repo_group:
85 %elif c.repo_group:
86 ${_('No {type} integrations for repogroup {repogroup} exist yet.').format(type=integration_type, repogroup=c.repo_group.group_name)}
86 ${_('No {type} integrations for repogroup {repogroup} exist yet.').format(type=integration_type, repogroup=c.repo_group.group_name)}
87 %else:
87 %else:
88 ${_('No {type} integrations exist yet.').format(type=integration_type)}
88 ${_('No {type} integrations exist yet.').format(type=integration_type)}
89 %endif
89 %endif
90
90
91 %if c.current_IntegrationType:
91 %if c.current_IntegrationType:
92 <%
92 <%
93 if c.repo:
93 if c.repo:
94 create_url = h.route_path('repo_integrations_create', repo_name=c.repo.repo_name, integration=c.current_IntegrationType.key)
94 create_url = h.route_path('repo_integrations_create', repo_name=c.repo.repo_name, integration=c.current_IntegrationType.key)
95 elif c.repo_group:
95 elif c.repo_group:
96 create_url = h.route_path('repo_group_integrations_create', repo_group_name=c.repo_group.group_name, integration=c.current_IntegrationType.key)
96 create_url = h.route_path('repo_group_integrations_create', repo_group_name=c.repo_group.group_name, integration=c.current_IntegrationType.key)
97 else:
97 else:
98 create_url = h.route_path('global_integrations_create', integration=c.current_IntegrationType.key)
98 create_url = h.route_path('global_integrations_create', integration=c.current_IntegrationType.key)
99 %>
99 %>
100 %endif
100 %endif
101
101
102 <a href="${create_url}">${_(u'Create one')}</a>
102 <a href="${create_url}">${_('Create one')}</a>
103 </td>
103 </td>
104 </tr>
104 </tr>
105 %endif
105 %endif
106 %for IntegrationType, integration in c.integrations_list:
106 %for IntegrationType, integration in c.integrations_list:
107 <tr id="integration_${integration.integration_id}">
107 <tr id="integration_${integration.integration_id}">
108 <td class="td-enabled">
108 <td class="td-enabled">
109 <div class="pull-left">
109 <div class="pull-left">
110 ${h.bool2icon(integration.enabled)}
110 ${h.bool2icon(integration.enabled)}
111 </div>
111 </div>
112 </td>
112 </td>
113 <td class="td-description">
113 <td class="td-description">
114 ${integration.name}
114 ${integration.name}
115 </td>
115 </td>
116 <td class="td-icon">
116 <td class="td-icon">
117 %if integration.integration_type in c.available_integrations:
117 %if integration.integration_type in c.available_integrations:
118 <div class="integration-icon">
118 <div class="integration-icon">
119 ${c.available_integrations[integration.integration_type].icon()|n}
119 ${c.available_integrations[integration.integration_type].icon()|n}
120 </div>
120 </div>
121 %else:
121 %else:
122 ?
122 ?
123 %endif
123 %endif
124 </td>
124 </td>
125 <td class="td-type">
125 <td class="td-type">
126 ${integration.integration_type}
126 ${integration.integration_type}
127 </td>
127 </td>
128 <td class="td-scope">
128 <td class="td-scope">
129 %if integration.repo:
129 %if integration.repo:
130 <a href="${h.route_path('repo_summary', repo_name=integration.repo.repo_name)}">
130 <a href="${h.route_path('repo_summary', repo_name=integration.repo.repo_name)}">
131 ${_('repo')}:${integration.repo.repo_name}
131 ${_('repo')}:${integration.repo.repo_name}
132 </a>
132 </a>
133 %elif integration.repo_group:
133 %elif integration.repo_group:
134 <a href="${h.route_path('repo_group_home', repo_group_name=integration.repo_group.group_name)}">
134 <a href="${h.route_path('repo_group_home', repo_group_name=integration.repo_group.group_name)}">
135 ${_('repogroup')}:${integration.repo_group.group_name}
135 ${_('repogroup')}:${integration.repo_group.group_name}
136 %if integration.child_repos_only:
136 %if integration.child_repos_only:
137 ${_('child repos only')}
137 ${_('child repos only')}
138 %else:
138 %else:
139 ${_('cascade to all')}
139 ${_('cascade to all')}
140 %endif
140 %endif
141 </a>
141 </a>
142 %else:
142 %else:
143 %if integration.child_repos_only:
143 %if integration.child_repos_only:
144 ${_('top level repos only')}
144 ${_('top level repos only')}
145 %else:
145 %else:
146 ${_('global')}
146 ${_('global')}
147 %endif
147 %endif
148 </td>
148 </td>
149 %endif
149 %endif
150 <td class="td-action">
150 <td class="td-action">
151 %if not IntegrationType:
151 %if not IntegrationType:
152 ${_('unknown integration')}
152 ${_('unknown integration')}
153 %else:
153 %else:
154 <%
154 <%
155 if c.repo:
155 if c.repo:
156 edit_url = request.route_path('repo_integrations_edit',
156 edit_url = request.route_path('repo_integrations_edit',
157 repo_name=c.repo.repo_name,
157 repo_name=c.repo.repo_name,
158 integration=integration.integration_type,
158 integration=integration.integration_type,
159 integration_id=integration.integration_id)
159 integration_id=integration.integration_id)
160 elif c.repo_group:
160 elif c.repo_group:
161 edit_url = request.route_path('repo_group_integrations_edit',
161 edit_url = request.route_path('repo_group_integrations_edit',
162 repo_group_name=c.repo_group.group_name,
162 repo_group_name=c.repo_group.group_name,
163 integration=integration.integration_type,
163 integration=integration.integration_type,
164 integration_id=integration.integration_id)
164 integration_id=integration.integration_id)
165 else:
165 else:
166 edit_url = request.route_path('global_integrations_edit',
166 edit_url = request.route_path('global_integrations_edit',
167 integration=integration.integration_type,
167 integration=integration.integration_type,
168 integration_id=integration.integration_id)
168 integration_id=integration.integration_id)
169 %>
169 %>
170 <div class="grid_edit">
170 <div class="grid_edit">
171 <a href="${edit_url}">${_('Edit')}</a>
171 <a href="${edit_url}">${_('Edit')}</a>
172 </div>
172 </div>
173 <div class="grid_delete">
173 <div class="grid_delete">
174 <a href="${edit_url}"
174 <a href="${edit_url}"
175 class="btn btn-link btn-danger delete_integration_entry"
175 class="btn btn-link btn-danger delete_integration_entry"
176 data-desc="${integration.name}"
176 data-desc="${integration.name}"
177 data-uid="${integration.integration_id}">
177 data-uid="${integration.integration_id}">
178 ${_('Delete')}
178 ${_('Delete')}
179 </a>
179 </a>
180 </div>
180 </div>
181 %endif
181 %endif
182 </td>
182 </td>
183 </tr>
183 </tr>
184 %endfor
184 %endfor
185 <tr id="last-row"></tr>
185 <tr id="last-row"></tr>
186 </tbody>
186 </tbody>
187 </table>
187 </table>
188 <div class="integrations-paginator">
188 <div class="integrations-paginator">
189 <div class="pagination-wh pagination-left">
189 <div class="pagination-wh pagination-left">
190 ${c.integrations_list.render()}
190 ${c.integrations_list.render()}
191 </div>
191 </div>
192 </div>
192 </div>
193 </div>
193 </div>
194 </div>
194 </div>
195 <script type="text/javascript">
195 <script type="text/javascript">
196 var delete_integration = function(entry) {
196 var delete_integration = function(entry) {
197 if (confirm("Confirm to remove this integration: "+$(entry).data('desc'))) {
197 if (confirm("Confirm to remove this integration: "+$(entry).data('desc'))) {
198 var request = $.ajax({
198 var request = $.ajax({
199 type: "POST",
199 type: "POST",
200 url: $(entry).attr('href'),
200 url: $(entry).attr('href'),
201 data: {
201 data: {
202 'delete': 'delete',
202 'delete': 'delete',
203 'csrf_token': CSRF_TOKEN
203 'csrf_token': CSRF_TOKEN
204 },
204 },
205 success: function(){
205 success: function(){
206 location.reload();
206 location.reload();
207 },
207 },
208 error: function(data, textStatus, errorThrown){
208 error: function(data, textStatus, errorThrown){
209 alert("Error while deleting entry.\nError code {0} ({1}). URL: {2}".format(data.status,data.statusText,$(entry)[0].url));
209 alert("Error while deleting entry.\nError code {0} ({1}). URL: {2}".format(data.status,data.statusText,$(entry)[0].url));
210 }
210 }
211 });
211 });
212 };
212 };
213 };
213 };
214
214
215 $('.delete_integration_entry').on('click', function(e){
215 $('.delete_integration_entry').on('click', function(e){
216 e.preventDefault();
216 e.preventDefault();
217 delete_integration(this);
217 delete_integration(this);
218 });
218 });
219 </script> No newline at end of file
219 </script>
@@ -1,67 +1,67 b''
1 <%inherit file="/base/base.mako"/>
1 <%inherit file="/base/base.mako"/>
2
2
3 <%def name="title()">
3 <%def name="title()">
4 ${_('Settings administration')}
4 ${_('Settings administration')}
5 %if c.rhodecode_name:
5 %if c.rhodecode_name:
6 &middot; ${h.branding(c.rhodecode_name)}
6 &middot; ${h.branding(c.rhodecode_name)}
7 %endif
7 %endif
8 </%def>
8 </%def>
9
9
10 <%def name="breadcrumbs_links()"></%def>
10 <%def name="breadcrumbs_links()"></%def>
11
11
12 <%def name="menu_bar_nav()">
12 <%def name="menu_bar_nav()">
13 ${self.menu_items(active='admin')}
13 ${self.menu_items(active='admin')}
14 </%def>
14 </%def>
15
15
16 <%def name="menu_bar_subnav()">
16 <%def name="menu_bar_subnav()">
17 ${self.admin_menu()}
17 ${self.admin_menu()}
18 </%def>
18 </%def>
19
19
20 <%def name="side_bar_nav()">
20 <%def name="side_bar_nav()">
21
21
22 </%def>
22 </%def>
23
23
24 <%def name="main_content()">
24 <%def name="main_content()">
25 <h2>${_('Administration area')}</h2>
25 <h2>${_('Administration area')}</h2>
26
26
27 <table class="rctable">
27 <table class="rctable">
28 <tr>
28 <tr>
29 <td>${_('Repositories under administration')}</td>
29 <td>${_('Repositories under administration')}</td>
30 <td class="delegated-admin-repos">${len(c.auth_user.repositories_admin)}</td>
30 <td class="delegated-admin-repos">${len(c.auth_user.repositories_admin)}</td>
31 <td>
31 <td>
32 % if c.can_create_repo:
32 % if c.can_create_repo:
33 <a href="${h.route_path('repo_new')}" class="">${_('Add Repository')}</a>
33 <a href="${h.route_path('repo_new')}" class="">${_('Add Repository')}</a>
34 % endif
34 % endif
35 </td>
35 </td>
36 </tr>
36 </tr>
37 <tr>
37 <tr>
38 <td>${_('Repository groups under administration')}</td>
38 <td>${_('Repository groups under administration')}</td>
39 <td class="delegated-admin-repo-groups">${len(c.auth_user.repository_groups_admin)}</td>
39 <td class="delegated-admin-repo-groups">${len(c.auth_user.repository_groups_admin)}</td>
40 <td>
40 <td>
41 % if c.can_create_repo_group:
41 % if c.can_create_repo_group:
42 <a href="${h.route_path('repo_group_new')}" class="">${_(u'Add Repository Group')}</a>
42 <a href="${h.route_path('repo_group_new')}" class="">${_('Add Repository Group')}</a>
43 % endif
43 % endif
44 </td>
44 </td>
45 </tr>
45 </tr>
46 <tr>
46 <tr>
47 <td>${_('User groups under administration')}</td>
47 <td>${_('User groups under administration')}</td>
48 <td class="delegated-admin-user-groups">${len(c.auth_user.user_groups_admin)}</td>
48 <td class="delegated-admin-user-groups">${len(c.auth_user.user_groups_admin)}</td>
49 <td>
49 <td>
50 % if c.can_create_user_group:
50 % if c.can_create_user_group:
51 <a href="${h.route_path('user_groups_new')}" class="">${_(u'Add User Group')}</a>
51 <a href="${h.route_path('user_groups_new')}" class="">${_('Add User Group')}</a>
52 % endif
52 % endif
53 </td>
53 </td>
54 </tr>
54 </tr>
55 </table>
55 </table>
56 </%def>
56 </%def>
57
57
58 <%def name="main()">
58 <%def name="main()">
59 <div class="box">
59 <div class="box">
60
60
61 ##main
61 ##main
62 <div class="main-content-auto-width">
62 <div class="main-content-auto-width">
63 ${self.main_content()}
63 ${self.main_content()}
64 </div>
64 </div>
65 </div>
65 </div>
66
66
67 </%def> No newline at end of file
67 </%def>
@@ -1,116 +1,116 b''
1 <%inherit file="/base/base.mako"/>
1 <%inherit file="/base/base.mako"/>
2
2
3 <%def name="title()">
3 <%def name="title()">
4 ${_('Repository groups administration')}
4 ${_('Repository groups administration')}
5 %if c.rhodecode_name:
5 %if c.rhodecode_name:
6 &middot; ${h.branding(c.rhodecode_name)}
6 &middot; ${h.branding(c.rhodecode_name)}
7 %endif
7 %endif
8 </%def>
8 </%def>
9
9
10 <%def name="breadcrumbs_links()"></%def>
10 <%def name="breadcrumbs_links()"></%def>
11
11
12 <%def name="menu_bar_nav()">
12 <%def name="menu_bar_nav()">
13 ${self.menu_items(active='admin')}
13 ${self.menu_items(active='admin')}
14 </%def>
14 </%def>
15
15
16 <%def name="menu_bar_subnav()">
16 <%def name="menu_bar_subnav()">
17 ${self.admin_menu(active='repository_groups')}
17 ${self.admin_menu(active='repository_groups')}
18 </%def>
18 </%def>
19
19
20 <%def name="main()">
20 <%def name="main()">
21 <div class="box">
21 <div class="box">
22
22
23 <div class="title">
23 <div class="title">
24 <input class="q_filter_box" id="q_filter" size="15" type="text" name="filter" placeholder="${_('quick filter...')}" value=""/>
24 <input class="q_filter_box" id="q_filter" size="15" type="text" name="filter" placeholder="${_('quick filter...')}" value=""/>
25 <span id="repo_group_count"></span>
25 <span id="repo_group_count"></span>
26
26
27 <ul class="links">
27 <ul class="links">
28 %if c.can_create_repo_group:
28 %if c.can_create_repo_group:
29 <li>
29 <li>
30 <a href="${h.route_path('repo_group_new')}" class="btn btn-small btn-success">${_(u'Add Repository Group')}</a>
30 <a href="${h.route_path('repo_group_new')}" class="btn btn-small btn-success">${_('Add Repository Group')}</a>
31 </li>
31 </li>
32 %endif
32 %endif
33 </ul>
33 </ul>
34 </div>
34 </div>
35 <div id="repos_list_wrap">
35 <div id="repos_list_wrap">
36 <table id="group_list_table" class="display"></table>
36 <table id="group_list_table" class="display"></table>
37 </div>
37 </div>
38 </div>
38 </div>
39
39
40 <script>
40 <script>
41 $(document).ready(function() {
41 $(document).ready(function() {
42 var $repoGroupsListTable = $('#group_list_table');
42 var $repoGroupsListTable = $('#group_list_table');
43
43
44 // repo group list
44 // repo group list
45 $repoGroupsListTable.DataTable({
45 $repoGroupsListTable.DataTable({
46 processing: true,
46 processing: true,
47 serverSide: true,
47 serverSide: true,
48 ajax: {
48 ajax: {
49 "url": "${h.route_path('repo_groups_data')}",
49 "url": "${h.route_path('repo_groups_data')}",
50 "dataSrc": function (json) {
50 "dataSrc": function (json) {
51 var filteredCount = json.recordsFiltered;
51 var filteredCount = json.recordsFiltered;
52 var filteredInactiveCount = json.recordsFilteredInactive;
52 var filteredInactiveCount = json.recordsFilteredInactive;
53 var totalInactive = json.recordsTotalInactive;
53 var totalInactive = json.recordsTotalInactive;
54 var total = json.recordsTotal;
54 var total = json.recordsTotal;
55
55
56 var _text = _gettext(
56 var _text = _gettext(
57 "{0} of {1} repository groups").format(
57 "{0} of {1} repository groups").format(
58 filteredCount, total);
58 filteredCount, total);
59
59
60 if (total === filteredCount) {
60 if (total === filteredCount) {
61 _text = _gettext("{0} repository groups").format(total);
61 _text = _gettext("{0} repository groups").format(total);
62 }
62 }
63 $('#repo_group_count').text(_text);
63 $('#repo_group_count').text(_text);
64 return json.data;
64 return json.data;
65 },
65 },
66 },
66 },
67
67
68 dom: 'rtp',
68 dom: 'rtp',
69 pageLength: ${c.visual.admin_grid_items},
69 pageLength: ${c.visual.admin_grid_items},
70 order: [[ 0, "asc" ]],
70 order: [[ 0, "asc" ]],
71 columns: [
71 columns: [
72 { data: {"_": "name",
72 { data: {"_": "name",
73 "sort": "name"}, title: "${_('Name')}", className: "td-componentname" },
73 "sort": "name"}, title: "${_('Name')}", className: "td-componentname" },
74 { data: 'menu', "bSortable": false, className: "quick_repo_menu" },
74 { data: 'menu', "bSortable": false, className: "quick_repo_menu" },
75 { data: {"_": "desc",
75 { data: {"_": "desc",
76 "sort": "desc"}, title: "${_('Description')}", className: "td-description" },
76 "sort": "desc"}, title: "${_('Description')}", className: "td-description" },
77 { data: {"_": "last_change",
77 { data: {"_": "last_change",
78 "sort": "last_change",
78 "sort": "last_change",
79 "type": Number}, title: "${_('Last Change')}", className: "td-time" },
79 "type": Number}, title: "${_('Last Change')}", className: "td-time" },
80 { data: {"_": "top_level_repos",
80 { data: {"_": "top_level_repos",
81 "sort": "top_level_repos"}, title: "${_('Number of top level repositories')}" },
81 "sort": "top_level_repos"}, title: "${_('Number of top level repositories')}" },
82 { data: {"_": "owner",
82 { data: {"_": "owner",
83 "sort": "owner"}, title: "${_('Owner')}", className: "td-user" },
83 "sort": "owner"}, title: "${_('Owner')}", className: "td-user" },
84 { data: {"_": "action",
84 { data: {"_": "action",
85 "sort": "action"}, title: "${_('Action')}", className: "td-action", orderable: false }
85 "sort": "action"}, title: "${_('Action')}", className: "td-action", orderable: false }
86 ],
86 ],
87 language: {
87 language: {
88 paginate: DEFAULT_GRID_PAGINATION,
88 paginate: DEFAULT_GRID_PAGINATION,
89 sProcessing: _gettext('loading...'),
89 sProcessing: _gettext('loading...'),
90 emptyTable: _gettext("No repository groups available yet.")
90 emptyTable: _gettext("No repository groups available yet.")
91 },
91 },
92 });
92 });
93
93
94 $repoGroupsListTable.on('xhr.dt', function(e, settings, json, xhr){
94 $repoGroupsListTable.on('xhr.dt', function(e, settings, json, xhr){
95 $repoGroupsListTable.css('opacity', 1);
95 $repoGroupsListTable.css('opacity', 1);
96 });
96 });
97
97
98 $repoGroupsListTable.on('preXhr.dt', function(e, settings, data){
98 $repoGroupsListTable.on('preXhr.dt', function(e, settings, data){
99 $repoGroupsListTable.css('opacity', 0.3);
99 $repoGroupsListTable.css('opacity', 0.3);
100 });
100 });
101
101
102 // filter
102 // filter
103 $('#q_filter').on('keyup',
103 $('#q_filter').on('keyup',
104 $.debounce(250, function() {
104 $.debounce(250, function() {
105 $repoGroupsListTable.DataTable().search(
105 $repoGroupsListTable.DataTable().search(
106 $('#q_filter').val()
106 $('#q_filter').val()
107 ).draw();
107 ).draw();
108 })
108 })
109 );
109 );
110
110
111 });
111 });
112
112
113 </script>
113 </script>
114
114
115 </%def>
115 </%def>
116
116
@@ -1,148 +1,148 b''
1 <%inherit file="/base/base.mako"/>
1 <%inherit file="/base/base.mako"/>
2
2
3 <%def name="title()">
3 <%def name="title()">
4 ${_('Repositories administration')}
4 ${_('Repositories administration')}
5 %if c.rhodecode_name:
5 %if c.rhodecode_name:
6 &middot; ${h.branding(c.rhodecode_name)}
6 &middot; ${h.branding(c.rhodecode_name)}
7 %endif
7 %endif
8 </%def>
8 </%def>
9
9
10 <%def name="breadcrumbs_links()"></%def>
10 <%def name="breadcrumbs_links()"></%def>
11
11
12 <%def name="menu_bar_nav()">
12 <%def name="menu_bar_nav()">
13 ${self.menu_items(active='admin')}
13 ${self.menu_items(active='admin')}
14 </%def>
14 </%def>
15
15
16 <%def name="menu_bar_subnav()">
16 <%def name="menu_bar_subnav()">
17 ${self.admin_menu(active='repositories')}
17 ${self.admin_menu(active='repositories')}
18 </%def>
18 </%def>
19
19
20 <%def name="main()">
20 <%def name="main()">
21 <div class="box">
21 <div class="box">
22
22
23 <div class="title">
23 <div class="title">
24 <input class="q_filter_box" id="q_filter" size="15" type="text" name="filter" placeholder="${_('quick filter...')}" value=""/>
24 <input class="q_filter_box" id="q_filter" size="15" type="text" name="filter" placeholder="${_('quick filter...')}" value=""/>
25 <span id="repo_count"></span>
25 <span id="repo_count"></span>
26
26
27 <ul class="links">
27 <ul class="links">
28 %if c.can_create_repo:
28 %if c.can_create_repo:
29 <li>
29 <li>
30 <a href="${h.route_path('repo_new')}" class="btn btn-small btn-success">${_(u'Add Repository')}</a>
30 <a href="${h.route_path('repo_new')}" class="btn btn-small btn-success">${_('Add Repository')}</a>
31 </li>
31 </li>
32 %endif
32 %endif
33 </ul>
33 </ul>
34 </div>
34 </div>
35 <div id="repos_list_wrap">
35 <div id="repos_list_wrap">
36 <table id="repo_list_table" class="display"></table>
36 <table id="repo_list_table" class="display"></table>
37 </div>
37 </div>
38
38
39 </div>
39 </div>
40
40
41 <script>
41 <script>
42 $(document).ready(function() {
42 $(document).ready(function() {
43 var $repoListTable = $('#repo_list_table');
43 var $repoListTable = $('#repo_list_table');
44
44
45 // repo list
45 // repo list
46 $repoListTable.DataTable({
46 $repoListTable.DataTable({
47 processing: true,
47 processing: true,
48 serverSide: true,
48 serverSide: true,
49 ajax: {
49 ajax: {
50 "url": "${h.route_path('repos_data')}",
50 "url": "${h.route_path('repos_data')}",
51 "dataSrc": function (json) {
51 "dataSrc": function (json) {
52 var filteredCount = json.recordsFiltered;
52 var filteredCount = json.recordsFiltered;
53 var total = json.recordsTotal;
53 var total = json.recordsTotal;
54
54
55 var _text = _gettext(
55 var _text = _gettext(
56 "{0} of {1} repositories").format(
56 "{0} of {1} repositories").format(
57 filteredCount, total);
57 filteredCount, total);
58
58
59 if (total === filteredCount) {
59 if (total === filteredCount) {
60 _text = _gettext("{0} repositories").format(total);
60 _text = _gettext("{0} repositories").format(total);
61 }
61 }
62 $('#repo_count').text(_text);
62 $('#repo_count').text(_text);
63
63
64 return json.data;
64 return json.data;
65 },
65 },
66 },
66 },
67 dom: 'rtp',
67 dom: 'rtp',
68 pageLength: ${c.visual.admin_grid_items},
68 pageLength: ${c.visual.admin_grid_items},
69 order: [[ 0, "asc" ]],
69 order: [[ 0, "asc" ]],
70 columns: [
70 columns: [
71 {
71 {
72 data: {
72 data: {
73 "_": "name",
73 "_": "name",
74 "sort": "name"
74 "sort": "name"
75 }, title: "${_('Name')}", className: "td-componentname"
75 }, title: "${_('Name')}", className: "td-componentname"
76 },
76 },
77 {
77 {
78 data: 'menu', "bSortable": false, className: "quick_repo_menu"},
78 data: 'menu', "bSortable": false, className: "quick_repo_menu"},
79 {
79 {
80 data: {
80 data: {
81 "_": "desc",
81 "_": "desc",
82 "sort": "desc"
82 "sort": "desc"
83 }, title: "${_('Description')}", className: "td-description"
83 }, title: "${_('Description')}", className: "td-description"
84 },
84 },
85 {
85 {
86 data: {
86 data: {
87 "_": "last_change",
87 "_": "last_change",
88 "sort": "last_change",
88 "sort": "last_change",
89 "type": Number
89 "type": Number
90 }, title: "${_('Last Change')}", className: "td-time"
90 }, title: "${_('Last Change')}", className: "td-time"
91 },
91 },
92 {
92 {
93 data: {
93 data: {
94 "_": "last_changeset",
94 "_": "last_changeset",
95 "sort": "last_changeset_raw",
95 "sort": "last_changeset_raw",
96 "type": Number
96 "type": Number
97 }, title: "${_('Commit')}", className: "td-commit", orderable: false
97 }, title: "${_('Commit')}", className: "td-commit", orderable: false
98 },
98 },
99 {
99 {
100 data: {
100 data: {
101 "_": "owner",
101 "_": "owner",
102 "sort": "owner"
102 "sort": "owner"
103 }, title: "${_('Owner')}", className: "td-user"
103 }, title: "${_('Owner')}", className: "td-user"
104 },
104 },
105 {
105 {
106 data: {
106 data: {
107 "_": "state",
107 "_": "state",
108 "sort": "state"
108 "sort": "state"
109 }, title: "${_('State')}", className: "td-tags td-state"
109 }, title: "${_('State')}", className: "td-tags td-state"
110 },
110 },
111 {
111 {
112 data: {
112 data: {
113 "_": "action",
113 "_": "action",
114 "sort": "action"
114 "sort": "action"
115 }, title: "${_('Action')}", className: "td-action", orderable: false
115 }, title: "${_('Action')}", className: "td-action", orderable: false
116 }
116 }
117 ],
117 ],
118 language: {
118 language: {
119 paginate: DEFAULT_GRID_PAGINATION,
119 paginate: DEFAULT_GRID_PAGINATION,
120 sProcessing: _gettext('loading...'),
120 sProcessing: _gettext('loading...'),
121 emptyTable:_gettext("No repositories present.")
121 emptyTable:_gettext("No repositories present.")
122 },
122 },
123 "initComplete": function( settings, json ) {
123 "initComplete": function( settings, json ) {
124 quick_repo_menu();
124 quick_repo_menu();
125 }
125 }
126 });
126 });
127
127
128 $repoListTable.on('xhr.dt', function(e, settings, json, xhr){
128 $repoListTable.on('xhr.dt', function(e, settings, json, xhr){
129 $repoListTable.css('opacity', 1);
129 $repoListTable.css('opacity', 1);
130 });
130 });
131
131
132 $repoListTable.on('preXhr.dt', function(e, settings, data){
132 $repoListTable.on('preXhr.dt', function(e, settings, data){
133 $repoListTable.css('opacity', 0.3);
133 $repoListTable.css('opacity', 0.3);
134 });
134 });
135
135
136 $('#q_filter').on('keyup',
136 $('#q_filter').on('keyup',
137 $.debounce(250, function() {
137 $.debounce(250, function() {
138 $repoListTable.DataTable().search(
138 $repoListTable.DataTable().search(
139 $('#q_filter').val()
139 $('#q_filter').val()
140 ).draw();
140 ).draw();
141 })
141 })
142 );
142 );
143
143
144 });
144 });
145
145
146 </script>
146 </script>
147
147
148 </%def>
148 </%def>
@@ -1,117 +1,117 b''
1 <%inherit file="/base/base.mako"/>
1 <%inherit file="/base/base.mako"/>
2
2
3 <%def name="title()">
3 <%def name="title()">
4 ${_('User groups administration')}
4 ${_('User groups administration')}
5 %if c.rhodecode_name:
5 %if c.rhodecode_name:
6 &middot; ${h.branding(c.rhodecode_name)}
6 &middot; ${h.branding(c.rhodecode_name)}
7 %endif
7 %endif
8 </%def>
8 </%def>
9
9
10 <%def name="breadcrumbs_links()"></%def>
10 <%def name="breadcrumbs_links()"></%def>
11
11
12 <%def name="menu_bar_nav()">
12 <%def name="menu_bar_nav()">
13 ${self.menu_items(active='admin')}
13 ${self.menu_items(active='admin')}
14 </%def>
14 </%def>
15
15
16 <%def name="menu_bar_subnav()">
16 <%def name="menu_bar_subnav()">
17 ${self.admin_menu(active='user_groups')}
17 ${self.admin_menu(active='user_groups')}
18 </%def>
18 </%def>
19
19
20 <%def name="main()">
20 <%def name="main()">
21 <div class="box">
21 <div class="box">
22
22
23 <div class="title">
23 <div class="title">
24 <input class="q_filter_box" id="q_filter" size="15" type="text" name="filter" placeholder="${_('quick filter...')}" value=""/>
24 <input class="q_filter_box" id="q_filter" size="15" type="text" name="filter" placeholder="${_('quick filter...')}" value=""/>
25 <span id="user_group_count">0</span>
25 <span id="user_group_count">0</span>
26
26
27 <ul class="links">
27 <ul class="links">
28 %if c.can_create_user_group:
28 %if c.can_create_user_group:
29 <li>
29 <li>
30 <a href="${h.route_path('user_groups_new')}" class="btn btn-small btn-success">${_(u'Add User Group')}</a>
30 <a href="${h.route_path('user_groups_new')}" class="btn btn-small btn-success">${_('Add User Group')}</a>
31 </li>
31 </li>
32 %endif
32 %endif
33 </ul>
33 </ul>
34 </div>
34 </div>
35
35
36 <div id="repos_list_wrap">
36 <div id="repos_list_wrap">
37 <table id="user_group_list_table" class="display"></table>
37 <table id="user_group_list_table" class="display"></table>
38 </div>
38 </div>
39
39
40 </div>
40 </div>
41 <script>
41 <script>
42 $(document).ready(function() {
42 $(document).ready(function() {
43 var $userGroupsListTable = $('#user_group_list_table');
43 var $userGroupsListTable = $('#user_group_list_table');
44
44
45 // user list
45 // user list
46 $userGroupsListTable.DataTable({
46 $userGroupsListTable.DataTable({
47 processing: true,
47 processing: true,
48 serverSide: true,
48 serverSide: true,
49 ajax: {
49 ajax: {
50 "url": "${h.route_path('user_groups_data')}",
50 "url": "${h.route_path('user_groups_data')}",
51 "dataSrc": function (json) {
51 "dataSrc": function (json) {
52 var filteredCount = json.recordsFiltered;
52 var filteredCount = json.recordsFiltered;
53 var filteredInactiveCount = json.recordsFilteredInactive;
53 var filteredInactiveCount = json.recordsFilteredInactive;
54 var totalInactive = json.recordsTotalInactive;
54 var totalInactive = json.recordsTotalInactive;
55 var total = json.recordsTotal;
55 var total = json.recordsTotal;
56
56
57 var _text = _gettext(
57 var _text = _gettext(
58 "{0} ({1} inactive) of {2} user groups ({3} inactive)").format(
58 "{0} ({1} inactive) of {2} user groups ({3} inactive)").format(
59 filteredCount, filteredInactiveCount, total, totalInactive);
59 filteredCount, filteredInactiveCount, total, totalInactive);
60
60
61 if (total === filteredCount) {
61 if (total === filteredCount) {
62 _text = _gettext(
62 _text = _gettext(
63 "{0} user groups ({1} inactive)").format(total, totalInactive);
63 "{0} user groups ({1} inactive)").format(total, totalInactive);
64 }
64 }
65 $('#user_group_count').text(_text);
65 $('#user_group_count').text(_text);
66 return json.data;
66 return json.data;
67 },
67 },
68 },
68 },
69
69
70 dom: 'rtp',
70 dom: 'rtp',
71 pageLength: ${c.visual.admin_grid_items},
71 pageLength: ${c.visual.admin_grid_items},
72 order: [[ 0, "asc" ]],
72 order: [[ 0, "asc" ]],
73 columns: [
73 columns: [
74 { data: {"_": "users_group_name",
74 { data: {"_": "users_group_name",
75 "sort": "users_group_name"}, title: "${_('Name')}", className: "td-componentname" },
75 "sort": "users_group_name"}, title: "${_('Name')}", className: "td-componentname" },
76 { data: {"_": "description",
76 { data: {"_": "description",
77 "sort": "description"}, title: "${_('Description')}", className: "td-description" },
77 "sort": "description"}, title: "${_('Description')}", className: "td-description" },
78 { data: {"_": "members",
78 { data: {"_": "members",
79 "sort": "members"}, title: "${_('Members')}", className: "td-number" },
79 "sort": "members"}, title: "${_('Members')}", className: "td-number" },
80 { data: {"_": "sync",
80 { data: {"_": "sync",
81 "sort": "sync"}, title: "${_('Sync')}", className: "td-sync" },
81 "sort": "sync"}, title: "${_('Sync')}", className: "td-sync" },
82 { data: {"_": "active",
82 { data: {"_": "active",
83 "sort": "active"}, title: "${_('Active')}", className: "td-active" },
83 "sort": "active"}, title: "${_('Active')}", className: "td-active" },
84 { data: {"_": "owner",
84 { data: {"_": "owner",
85 "sort": "owner"}, title: "${_('Owner')}", className: "td-user" },
85 "sort": "owner"}, title: "${_('Owner')}", className: "td-user" },
86 { data: {"_": "action",
86 { data: {"_": "action",
87 "sort": "action"}, title: "${_('Action')}", className: "td-action", orderable: false}
87 "sort": "action"}, title: "${_('Action')}", className: "td-action", orderable: false}
88 ],
88 ],
89 language: {
89 language: {
90 paginate: DEFAULT_GRID_PAGINATION,
90 paginate: DEFAULT_GRID_PAGINATION,
91 sProcessing: _gettext('loading...'),
91 sProcessing: _gettext('loading...'),
92 emptyTable: _gettext("No user groups available yet.")
92 emptyTable: _gettext("No user groups available yet.")
93 }
93 }
94 });
94 });
95
95
96 $userGroupsListTable.on('xhr.dt', function(e, settings, json, xhr){
96 $userGroupsListTable.on('xhr.dt', function(e, settings, json, xhr){
97 $userGroupsListTable.css('opacity', 1);
97 $userGroupsListTable.css('opacity', 1);
98 });
98 });
99
99
100 $userGroupsListTable.on('preXhr.dt', function(e, settings, data){
100 $userGroupsListTable.on('preXhr.dt', function(e, settings, data){
101 $userGroupsListTable.css('opacity', 0.3);
101 $userGroupsListTable.css('opacity', 0.3);
102 });
102 });
103
103
104 // filter
104 // filter
105 $('#q_filter').on('keyup',
105 $('#q_filter').on('keyup',
106 $.debounce(250, function() {
106 $.debounce(250, function() {
107 $userGroupsListTable.DataTable().search(
107 $userGroupsListTable.DataTable().search(
108 $('#q_filter').val()
108 $('#q_filter').val()
109 ).draw();
109 ).draw();
110 })
110 })
111 );
111 );
112
112
113 });
113 });
114
114
115 </script>
115 </script>
116
116
117 </%def>
117 </%def>
@@ -1,125 +1,125 b''
1 <%inherit file="/base/base.mako"/>
1 <%inherit file="/base/base.mako"/>
2
2
3 <%def name="title()">
3 <%def name="title()">
4 ${_('Users administration')}
4 ${_('Users administration')}
5 %if c.rhodecode_name:
5 %if c.rhodecode_name:
6 &middot; ${h.branding(c.rhodecode_name)}
6 &middot; ${h.branding(c.rhodecode_name)}
7 %endif
7 %endif
8 </%def>
8 </%def>
9
9
10 <%def name="breadcrumbs_links()"></%def>
10 <%def name="breadcrumbs_links()"></%def>
11
11
12 <%def name="menu_bar_nav()">
12 <%def name="menu_bar_nav()">
13 ${self.menu_items(active='admin')}
13 ${self.menu_items(active='admin')}
14 </%def>
14 </%def>
15
15
16 <%def name="menu_bar_subnav()">
16 <%def name="menu_bar_subnav()">
17 ${self.admin_menu(active='users')}
17 ${self.admin_menu(active='users')}
18 </%def>
18 </%def>
19
19
20 <%def name="main()">
20 <%def name="main()">
21
21
22 <div class="box">
22 <div class="box">
23
23
24 <div class="title">
24 <div class="title">
25 <input class="q_filter_box" id="q_filter" size="15" type="text" name="filter" placeholder="${_('quick filter...')}" value=""/>
25 <input class="q_filter_box" id="q_filter" size="15" type="text" name="filter" placeholder="${_('quick filter...')}" value=""/>
26 <span id="user_count">0</span>
26 <span id="user_count">0</span>
27
27
28 <ul class="links">
28 <ul class="links">
29 <li>
29 <li>
30 <a href="${h.route_path('users_new')}" class="btn btn-small btn-success">${_(u'Add User')}</a>
30 <a href="${h.route_path('users_new')}" class="btn btn-small btn-success">${_('Add User')}</a>
31 </li>
31 </li>
32 </ul>
32 </ul>
33 </div>
33 </div>
34
34
35 <div id="repos_list_wrap">
35 <div id="repos_list_wrap">
36 <table id="user_list_table" class="display"></table>
36 <table id="user_list_table" class="display"></table>
37 </div>
37 </div>
38 </div>
38 </div>
39
39
40 <script type="text/javascript">
40 <script type="text/javascript">
41
41
42 $(document).ready(function() {
42 $(document).ready(function() {
43 var $userListTable = $('#user_list_table');
43 var $userListTable = $('#user_list_table');
44 // user list
44 // user list
45 $userListTable.DataTable({
45 $userListTable.DataTable({
46 processing: true,
46 processing: true,
47 serverSide: true,
47 serverSide: true,
48 ajax: {
48 ajax: {
49 "url": "${h.route_path('users_data')}",
49 "url": "${h.route_path('users_data')}",
50 "dataSrc": function ( json ) {
50 "dataSrc": function ( json ) {
51 var filteredCount = json.recordsFiltered;
51 var filteredCount = json.recordsFiltered;
52 var filteredInactiveCount = json.recordsFilteredInactive;
52 var filteredInactiveCount = json.recordsFilteredInactive;
53 var totalInactive = json.recordsTotalInactive;
53 var totalInactive = json.recordsTotalInactive;
54 var total = json.recordsTotal;
54 var total = json.recordsTotal;
55
55
56 var _text = _gettext(
56 var _text = _gettext(
57 "{0} ({1} inactive) of {2} users ({3} inactive)").format(
57 "{0} ({1} inactive) of {2} users ({3} inactive)").format(
58 filteredCount, filteredInactiveCount, total, totalInactive);
58 filteredCount, filteredInactiveCount, total, totalInactive);
59
59
60 if(total === filteredCount){
60 if(total === filteredCount){
61 _text = _gettext(
61 _text = _gettext(
62 "{0} users ({1} inactive)").format(total, totalInactive);
62 "{0} users ({1} inactive)").format(total, totalInactive);
63 }
63 }
64 $('#user_count').text(_text);
64 $('#user_count').text(_text);
65 return json.data;
65 return json.data;
66 }
66 }
67 },
67 },
68 dom: 'rtp',
68 dom: 'rtp',
69 pageLength: ${c.visual.admin_grid_items},
69 pageLength: ${c.visual.admin_grid_items},
70 order: [[ 0, "asc" ]],
70 order: [[ 0, "asc" ]],
71 columns: [
71 columns: [
72 { data: {"_": "username",
72 { data: {"_": "username",
73 "sort": "username"}, title: "${_('Username')}", className: "td-user" },
73 "sort": "username"}, title: "${_('Username')}", className: "td-user" },
74 { data: {"_": "email",
74 { data: {"_": "email",
75 "sort": "email"}, title: "${_('Email')}", className: "td-email" },
75 "sort": "email"}, title: "${_('Email')}", className: "td-email" },
76 { data: {"_": "first_name",
76 { data: {"_": "first_name",
77 "sort": "first_name"}, title: "${_('First Name')}", className: "td-user" },
77 "sort": "first_name"}, title: "${_('First Name')}", className: "td-user" },
78 { data: {"_": "last_name",
78 { data: {"_": "last_name",
79 "sort": "last_name"}, title: "${_('Last Name')}", className: "td-user" },
79 "sort": "last_name"}, title: "${_('Last Name')}", className: "td-user" },
80 { data: {"_": "last_activity",
80 { data: {"_": "last_activity",
81 "sort": "last_activity",
81 "sort": "last_activity",
82 "type": Number}, title: "${_('Last activity')}", className: "td-time" },
82 "type": Number}, title: "${_('Last activity')}", className: "td-time" },
83 { data: {"_": "active",
83 { data: {"_": "active",
84 "sort": "active"}, title: "${_('Active')}", className: "td-active" },
84 "sort": "active"}, title: "${_('Active')}", className: "td-active" },
85 { data: {"_": "admin",
85 { data: {"_": "admin",
86 "sort": "admin"}, title: "${_('Super-admin')}", className: "td-admin" },
86 "sort": "admin"}, title: "${_('Super-admin')}", className: "td-admin" },
87 { data: {"_": "extern_type",
87 { data: {"_": "extern_type",
88 "sort": "extern_type"}, title: "${_('Auth type')}", className: "td-type" },
88 "sort": "extern_type"}, title: "${_('Auth type')}", className: "td-type" },
89 { data: {"_": "action",
89 { data: {"_": "action",
90 "sort": "action"}, title: "${_('Action')}", className: "td-action", orderable: false }
90 "sort": "action"}, title: "${_('Action')}", className: "td-action", orderable: false }
91 ],
91 ],
92 language: {
92 language: {
93 paginate: DEFAULT_GRID_PAGINATION,
93 paginate: DEFAULT_GRID_PAGINATION,
94 sProcessing: _gettext('loading...'),
94 sProcessing: _gettext('loading...'),
95 emptyTable: _gettext("No users available yet.")
95 emptyTable: _gettext("No users available yet.")
96 },
96 },
97
97
98 "createdRow": function ( row, data, index ) {
98 "createdRow": function ( row, data, index ) {
99 if (!data['active_raw']){
99 if (!data['active_raw']){
100 $(row).addClass('closed')
100 $(row).addClass('closed')
101 }
101 }
102 }
102 }
103 });
103 });
104
104
105 $userListTable.on('xhr.dt', function(e, settings, json, xhr){
105 $userListTable.on('xhr.dt', function(e, settings, json, xhr){
106 $userListTable.css('opacity', 1);
106 $userListTable.css('opacity', 1);
107 });
107 });
108
108
109 $userListTable.on('preXhr.dt', function(e, settings, data){
109 $userListTable.on('preXhr.dt', function(e, settings, data){
110 $userListTable.css('opacity', 0.3);
110 $userListTable.css('opacity', 0.3);
111 });
111 });
112
112
113 // filter
113 // filter
114 $('#q_filter').on('keyup',
114 $('#q_filter').on('keyup',
115 $.debounce(250, function() {
115 $.debounce(250, function() {
116 $userListTable.DataTable().search(
116 $userListTable.DataTable().search(
117 $('#q_filter').val()
117 $('#q_filter').val()
118 ).draw();
118 ).draw();
119 })
119 })
120 );
120 );
121
121
122 });
122 });
123 </script>
123 </script>
124
124
125 </%def>
125 </%def>
@@ -1,1261 +1,1263 b''
1
1
2 <%!
2 <%!
3 from rhodecode.lib import html_filters
3 from rhodecode.lib import html_filters
4 %>
4 %>
5
5
6 <%inherit file="root.mako"/>
6 <%inherit file="root.mako"/>
7
7
8 <%include file="/ejs_templates/templates.html"/>
8 <%include file="/ejs_templates/templates.html"/>
9
9
10 <div class="outerwrapper">
10 <div class="outerwrapper">
11 <!-- HEADER -->
11 <!-- HEADER -->
12 <div class="header">
12 <div class="header">
13 <div id="header-inner" class="wrapper">
13 <div id="header-inner" class="wrapper">
14 <div id="logo">
14 <div id="logo">
15 <div class="logo-wrapper">
15 <div class="logo-wrapper">
16 <a href="${h.route_path('home')}"><img src="${h.asset('images/rhodecode-logo-white-60x60.png')}" alt="RhodeCode"/></a>
16 <a href="${h.route_path('home')}"><img src="${h.asset('images/rhodecode-logo-white-60x60.png')}" alt="RhodeCode"/></a>
17 </div>
17 </div>
18 % if c.rhodecode_name:
18 % if c.rhodecode_name:
19 <div class="branding">
19 <div class="branding">
20 <a href="${h.route_path('home')}">${h.branding(c.rhodecode_name)}</a>
20 <a href="${h.route_path('home')}">${h.branding(c.rhodecode_name)}</a>
21 </div>
21 </div>
22 % endif
22 % endif
23 </div>
23 </div>
24 <!-- MENU BAR NAV -->
24 <!-- MENU BAR NAV -->
25 ${self.menu_bar_nav()}
25 ${self.menu_bar_nav()}
26 <!-- END MENU BAR NAV -->
26 <!-- END MENU BAR NAV -->
27 </div>
27 </div>
28 </div>
28 </div>
29 ${self.menu_bar_subnav()}
29 ${self.menu_bar_subnav()}
30 <!-- END HEADER -->
30 <!-- END HEADER -->
31
31
32 <!-- CONTENT -->
32 <!-- CONTENT -->
33 <div id="content" class="wrapper">
33 <div id="content" class="wrapper">
34
34
35 <rhodecode-toast id="notifications"></rhodecode-toast>
35 <rhodecode-toast id="notifications"></rhodecode-toast>
36
36
37 <div class="main">
37 <div class="main">
38 ${next.main()}
38 ${next.main()}
39 </div>
39 </div>
40
40
41 </div>
41 </div>
42 <!-- END CONTENT -->
42 <!-- END CONTENT -->
43
43
44 </div>
44 </div>
45
45
46 <!-- FOOTER -->
46 <!-- FOOTER -->
47 <div id="footer">
47 <div id="footer">
48 <div id="footer-inner" class="title wrapper">
48 <div id="footer-inner" class="title wrapper">
49 <div>
49 <div>
50 <% sid = 'block' if request.GET.get('showrcid') else 'none' %>
50 <% sid = 'block' if request.GET.get('showrcid') else 'none' %>
51
51
52 <p class="footer-link-right">
52 <p class="footer-link-right">
53 <a class="grey-link-action" href="${h.route_path('home', _query={'showrcid': 1})}">
53 <a class="grey-link-action" href="${h.route_path('home', _query={'showrcid': 1})}">
54 RhodeCode
54 RhodeCode
55 % if c.visual.show_version:
55 % if c.visual.show_version:
56 ${c.rhodecode_version}
56 ${c.rhodecode_version}
57 % endif
57 % endif
58 ${c.rhodecode_edition}
58 ${c.rhodecode_edition}
59 </a> |
59 </a> |
60
60
61 % if c.visual.rhodecode_support_url:
61 % if c.visual.rhodecode_support_url:
62 <a class="grey-link-action" href="${c.visual.rhodecode_support_url}" target="_blank">${_('Support')}</a> |
62 <a class="grey-link-action" href="${c.visual.rhodecode_support_url}" target="_blank">${_('Support')}</a> |
63 <a class="grey-link-action" href="https://docs.rhodecode.com" target="_blank">${_('Documentation')}</a>
63 <a class="grey-link-action" href="https://docs.rhodecode.com" target="_blank">${_('Documentation')}</a>
64 % endif
64 % endif
65
65
66 </p>
66 </p>
67
67
68 <p class="server-instance" style="display:${sid}">
68 <p class="server-instance" style="display:${sid}">
69 ## display hidden instance ID if specially defined
69 ## display hidden instance ID if specially defined
70 &copy; 2010-${h.datetime.today().year}, <a href="${h.route_url('rhodecode_official')}" target="_blank">RhodeCode GmbH</a>. All rights reserved.
70 &copy; 2010-${h.datetime.today().year}, <a href="${h.route_url('rhodecode_official')}" target="_blank">RhodeCode GmbH</a>. All rights reserved.
71 % if c.rhodecode_instanceid:
71 % if c.rhodecode_instanceid:
72 ${_('RhodeCode instance id: {}').format(c.rhodecode_instanceid)}
72 ${_('RhodeCode instance id: {}').format(c.rhodecode_instanceid)}
73 % endif
73 % endif
74 </p>
74 </p>
75 </div>
75 </div>
76 </div>
76 </div>
77 </div>
77 </div>
78
78
79 <!-- END FOOTER -->
79 <!-- END FOOTER -->
80
80
81 ### MAKO DEFS ###
81 ### MAKO DEFS ###
82
82
83 <%def name="menu_bar_subnav()">
83 <%def name="menu_bar_subnav()">
84 </%def>
84 </%def>
85
85
86 <%def name="breadcrumbs(class_='breadcrumbs')">
86 <%def name="breadcrumbs(class_='breadcrumbs')">
87 <div class="${class_}">
87 <div class="${class_}">
88 ${self.breadcrumbs_links()}
88 ${self.breadcrumbs_links()}
89 </div>
89 </div>
90 </%def>
90 </%def>
91
91
92 <%def name="admin_menu(active=None)">
92 <%def name="admin_menu(active=None)">
93
93
94 <div id="context-bar">
94 <div id="context-bar">
95 <div class="wrapper">
95 <div class="wrapper">
96 <div class="title">
96 <div class="title">
97 <div class="title-content">
97 <div class="title-content">
98 <div class="title-main">
98 <div class="title-main">
99 % if c.is_super_admin:
99 % if c.is_super_admin:
100 ${_('Super-admin Panel')}
100 ${_('Super-admin Panel')}
101 % else:
101 % else:
102 ${_('Delegated Admin Panel')}
102 ${_('Delegated Admin Panel')}
103 % endif
103 % endif
104 </div>
104 </div>
105 </div>
105 </div>
106 </div>
106 </div>
107
107
108 <ul id="context-pages" class="navigation horizontal-list">
108 <ul id="context-pages" class="navigation horizontal-list">
109
109
110 ## super-admin case
110 ## super-admin case (Top Menu)
111 % if c.is_super_admin:
111 % if c.is_super_admin:
112 <li class="${h.is_active('audit_logs', active)}"><a href="${h.route_path('admin_audit_logs')}">${_('Admin audit logs')}</a></li>
112 <li class="${h.is_active('audit_logs', active)}"><a href="${h.route_path('admin_audit_logs')}">${_('Admin audit logs')}</a></li>
113 <li class="${h.is_active('repositories', active)}"><a href="${h.route_path('repos')}">${_('Repositories')}</a></li>
113 <li class="${h.is_active('repositories', active)}"><a href="${h.route_path('repos')}">${_('Repositories')}</a></li>
114 <li class="${h.is_active('repository_groups', active)}"><a href="${h.route_path('repo_groups')}">${_('Repository groups')}</a></li>
114 <li class="${h.is_active('repository_groups', active)}"><a href="${h.route_path('repo_groups')}">${_('Repository groups')}</a></li>
115 <li class="${h.is_active('users', active)}"><a href="${h.route_path('users')}">${_('Users')}</a></li>
115 <li class="${h.is_active('users', active)}"><a href="${h.route_path('users')}">${_('Users')}</a></li>
116 <li class="${h.is_active('user_groups', active)}"><a href="${h.route_path('user_groups')}">${_('User groups')}</a></li>
116 <li class="${h.is_active('user_groups', active)}"><a href="${h.route_path('user_groups')}">${_('User groups')}</a></li>
117 <li class="${h.is_active('artifacts', active)}"><a href="${h.route_path('admin_artifacts')}">${_('Artifacts')}</a></li>
117 <li class="${h.is_active('artifacts', active)}"><a href="${h.route_path('admin_artifacts')}">${_('Artifacts')}</a></li>
118 <li class="${h.is_active('automation', active)}"><a href="${h.route_path('admin_automation')}">${_('Automation')}</a></li>
119 <li class="${h.is_active('scheduler', active)}"><a href="${h.route_path('admin_scheduler')}">${_('Scheduler')}</a></li>
118 <li class="${h.is_active('permissions', active)}"><a href="${h.route_path('admin_permissions_application')}">${_('Permissions')}</a></li>
120 <li class="${h.is_active('permissions', active)}"><a href="${h.route_path('admin_permissions_application')}">${_('Permissions')}</a></li>
119 <li class="${h.is_active('authentication', active)}"><a href="${h.route_path('auth_home', traverse='')}">${_('Authentication')}</a></li>
121 <li class="${h.is_active('authentication', active)}"><a href="${h.route_path('auth_home', traverse='')}">${_('Authentication')}</a></li>
120 <li class="${h.is_active('integrations', active)}"><a href="${h.route_path('global_integrations_home')}">${_('Integrations')}</a></li>
122 <li class="${h.is_active('integrations', active)}"><a href="${h.route_path('global_integrations_home')}">${_('Integrations')}</a></li>
121 <li class="${h.is_active('defaults', active)}"><a href="${h.route_path('admin_defaults_repositories')}">${_('Defaults')}</a></li>
123 <li class="${h.is_active('defaults', active)}"><a href="${h.route_path('admin_defaults_repositories')}">${_('Defaults')}</a></li>
122 <li class="${h.is_active('settings', active)}"><a href="${h.route_path('admin_settings')}">${_('Settings')}</a></li>
124 <li class="${h.is_active('settings', active)}"><a href="${h.route_path('admin_settings')}">${_('Settings')}</a></li>
123
125
124 ## delegated admin
126 ## delegated admin
125 % elif c.is_delegated_admin:
127 % elif c.is_delegated_admin:
126 <%
128 <%
127 repositories=c.auth_user.repositories_admin or c.can_create_repo
129 repositories=c.auth_user.repositories_admin or c.can_create_repo
128 repository_groups=c.auth_user.repository_groups_admin or c.can_create_repo_group
130 repository_groups=c.auth_user.repository_groups_admin or c.can_create_repo_group
129 user_groups=c.auth_user.user_groups_admin or c.can_create_user_group
131 user_groups=c.auth_user.user_groups_admin or c.can_create_user_group
130 %>
132 %>
131
133
132 %if repositories:
134 %if repositories:
133 <li class="${h.is_active('repositories', active)} local-admin-repos"><a href="${h.route_path('repos')}">${_('Repositories')}</a></li>
135 <li class="${h.is_active('repositories', active)} local-admin-repos"><a href="${h.route_path('repos')}">${_('Repositories')}</a></li>
134 %endif
136 %endif
135 %if repository_groups:
137 %if repository_groups:
136 <li class="${h.is_active('repository_groups', active)} local-admin-repo-groups"><a href="${h.route_path('repo_groups')}">${_('Repository groups')}</a></li>
138 <li class="${h.is_active('repository_groups', active)} local-admin-repo-groups"><a href="${h.route_path('repo_groups')}">${_('Repository groups')}</a></li>
137 %endif
139 %endif
138 %if user_groups:
140 %if user_groups:
139 <li class="${h.is_active('user_groups', active)} local-admin-user-groups"><a href="${h.route_path('user_groups')}">${_('User groups')}</a></li>
141 <li class="${h.is_active('user_groups', active)} local-admin-user-groups"><a href="${h.route_path('user_groups')}">${_('User groups')}</a></li>
140 %endif
142 %endif
141 % endif
143 % endif
142 </ul>
144 </ul>
143
145
144 </div>
146 </div>
145 <div class="clear"></div>
147 <div class="clear"></div>
146 </div>
148 </div>
147 </%def>
149 </%def>
148
150
149 <%def name="dt_info_panel(elements)">
151 <%def name="dt_info_panel(elements)">
150 <dl class="dl-horizontal">
152 <dl class="dl-horizontal">
151 %for dt, dd, title, show_items in elements:
153 %for dt, dd, title, show_items in elements:
152 <dt>${dt}:</dt>
154 <dt>${dt}:</dt>
153 <dd title="${h.tooltip(title)}">
155 <dd title="${h.tooltip(title)}">
154 %if callable(dd):
156 %if callable(dd):
155 ## allow lazy evaluation of elements
157 ## allow lazy evaluation of elements
156 ${dd()}
158 ${dd()}
157 %else:
159 %else:
158 ${dd}
160 ${dd}
159 %endif
161 %endif
160 %if show_items:
162 %if show_items:
161 <span class="btn-collapse" data-toggle="item-${h.md5_safe(dt)[:6]}-details">${_('Show More')} </span>
163 <span class="btn-collapse" data-toggle="item-${h.md5_safe(dt)[:6]}-details">${_('Show More')} </span>
162 %endif
164 %endif
163 </dd>
165 </dd>
164
166
165 %if show_items:
167 %if show_items:
166 <div class="collapsable-content" data-toggle="item-${h.md5_safe(dt)[:6]}-details" style="display: none">
168 <div class="collapsable-content" data-toggle="item-${h.md5_safe(dt)[:6]}-details" style="display: none">
167 %for item in show_items:
169 %for item in show_items:
168 <dt></dt>
170 <dt></dt>
169 <dd>${item}</dd>
171 <dd>${item}</dd>
170 %endfor
172 %endfor
171 </div>
173 </div>
172 %endif
174 %endif
173
175
174 %endfor
176 %endfor
175 </dl>
177 </dl>
176 </%def>
178 </%def>
177
179
178 <%def name="tr_info_entry(element)">
180 <%def name="tr_info_entry(element)">
179 <% key, val, title, show_items = element %>
181 <% key, val, title, show_items = element %>
180
182
181 <tr>
183 <tr>
182 <td style="vertical-align: top">${key}</td>
184 <td style="vertical-align: top">${key}</td>
183 <td title="${h.tooltip(title)}">
185 <td title="${h.tooltip(title)}">
184 %if callable(val):
186 %if callable(val):
185 ## allow lazy evaluation of elements
187 ## allow lazy evaluation of elements
186 ${val()}
188 ${val()}
187 %else:
189 %else:
188 ${val}
190 ${val}
189 %endif
191 %endif
190 %if show_items:
192 %if show_items:
191 <div class="collapsable-content" data-toggle="item-${h.md5_safe(h.safe_str(val))[:6]}-details" style="display: none">
193 <div class="collapsable-content" data-toggle="item-${h.md5_safe(h.safe_str(val))[:6]}-details" style="display: none">
192 % for item in show_items:
194 % for item in show_items:
193 <dt></dt>
195 <dt></dt>
194 <dd>${item}</dd>
196 <dd>${item}</dd>
195 % endfor
197 % endfor
196 </div>
198 </div>
197 %endif
199 %endif
198 </td>
200 </td>
199 <td style="vertical-align: top">
201 <td style="vertical-align: top">
200 %if show_items:
202 %if show_items:
201 <span class="btn-collapse" data-toggle="item-${h.md5_safe(h.safe_str(val))[:6]}-details">${_('Show More')} </span>
203 <span class="btn-collapse" data-toggle="item-${h.md5_safe(h.safe_str(val))[:6]}-details">${_('Show More')} </span>
202 %endif
204 %endif
203 </td>
205 </td>
204 </tr>
206 </tr>
205
207
206 </%def>
208 </%def>
207
209
208 <%def name="gravatar(email, size=16, tooltip=False, tooltip_alt=None, user=None, extra_class=None)">
210 <%def name="gravatar(email, size=16, tooltip=False, tooltip_alt=None, user=None, extra_class=None)">
209 <%
211 <%
210 if size > 16:
212 if size > 16:
211 gravatar_class = ['gravatar','gravatar-large']
213 gravatar_class = ['gravatar','gravatar-large']
212 else:
214 else:
213 gravatar_class = ['gravatar']
215 gravatar_class = ['gravatar']
214
216
215 data_hovercard_url = ''
217 data_hovercard_url = ''
216 data_hovercard_alt = tooltip_alt.replace('<', '&lt;').replace('>', '&gt;') if tooltip_alt else ''
218 data_hovercard_alt = tooltip_alt.replace('<', '&lt;').replace('>', '&gt;') if tooltip_alt else ''
217
219
218 if tooltip:
220 if tooltip:
219 gravatar_class += ['tooltip-hovercard']
221 gravatar_class += ['tooltip-hovercard']
220 if extra_class:
222 if extra_class:
221 gravatar_class += extra_class
223 gravatar_class += extra_class
222 if tooltip and user:
224 if tooltip and user:
223 if user.username == h.DEFAULT_USER:
225 if user.username == h.DEFAULT_USER:
224 gravatar_class.pop(-1)
226 gravatar_class.pop(-1)
225 else:
227 else:
226 data_hovercard_url = request.route_path('hovercard_user', user_id=getattr(user, 'user_id', ''))
228 data_hovercard_url = request.route_path('hovercard_user', user_id=getattr(user, 'user_id', ''))
227 gravatar_class = ' '.join(gravatar_class)
229 gravatar_class = ' '.join(gravatar_class)
228
230
229 %>
231 %>
230 <%doc>
232 <%doc>
231 TODO: johbo: For now we serve double size images to make it smooth
233 TODO: johbo: For now we serve double size images to make it smooth
232 for retina. This is how it worked until now. Should be replaced
234 for retina. This is how it worked until now. Should be replaced
233 with a better solution at some point.
235 with a better solution at some point.
234 </%doc>
236 </%doc>
235
237
236 <img class="${gravatar_class}" height="${size}" width="${size}" data-hovercard-url="${data_hovercard_url}" data-hovercard-alt="${data_hovercard_alt}" src="${h.gravatar_url(email, size * 2, request=request)}" />
238 <img class="${gravatar_class}" height="${size}" width="${size}" data-hovercard-url="${data_hovercard_url}" data-hovercard-alt="${data_hovercard_alt}" src="${h.gravatar_url(email, size * 2, request=request)}" />
237 </%def>
239 </%def>
238
240
239
241
240 <%def name="gravatar_with_user(contact, size=16, show_disabled=False, tooltip=False, _class='rc-user')">
242 <%def name="gravatar_with_user(contact, size=16, show_disabled=False, tooltip=False, _class='rc-user')">
241 <%
243 <%
242 email = h.email_or_none(contact)
244 email = h.email_or_none(contact)
243 rc_user = h.discover_user(contact)
245 rc_user = h.discover_user(contact)
244 %>
246 %>
245
247
246 <div class="${_class}">
248 <div class="${_class}">
247 ${self.gravatar(email, size, tooltip=tooltip, tooltip_alt=contact, user=rc_user)}
249 ${self.gravatar(email, size, tooltip=tooltip, tooltip_alt=contact, user=rc_user)}
248 <span class="${('user user-disabled' if show_disabled else 'user')}">
250 <span class="${('user user-disabled' if show_disabled else 'user')}">
249 ${h.link_to_user(rc_user or contact)}
251 ${h.link_to_user(rc_user or contact)}
250 </span>
252 </span>
251 </div>
253 </div>
252 </%def>
254 </%def>
253
255
254
256
255 <%def name="user_group_icon(user_group=None, size=16, tooltip=False)">
257 <%def name="user_group_icon(user_group=None, size=16, tooltip=False)">
256 <%
258 <%
257 if (size > 16):
259 if (size > 16):
258 gravatar_class = 'icon-user-group-alt'
260 gravatar_class = 'icon-user-group-alt'
259 else:
261 else:
260 gravatar_class = 'icon-user-group-alt'
262 gravatar_class = 'icon-user-group-alt'
261
263
262 if tooltip:
264 if tooltip:
263 gravatar_class += ' tooltip-hovercard'
265 gravatar_class += ' tooltip-hovercard'
264
266
265 data_hovercard_url = request.route_path('hovercard_user_group', user_group_id=user_group.users_group_id)
267 data_hovercard_url = request.route_path('hovercard_user_group', user_group_id=user_group.users_group_id)
266 %>
268 %>
267 <%doc>
269 <%doc>
268 TODO: johbo: For now we serve double size images to make it smooth
270 TODO: johbo: For now we serve double size images to make it smooth
269 for retina. This is how it worked until now. Should be replaced
271 for retina. This is how it worked until now. Should be replaced
270 with a better solution at some point.
272 with a better solution at some point.
271 </%doc>
273 </%doc>
272
274
273 <i style="font-size: ${size}px" class="${gravatar_class} x-icon-size-${size}" data-hovercard-url="${data_hovercard_url}"></i>
275 <i style="font-size: ${size}px" class="${gravatar_class} x-icon-size-${size}" data-hovercard-url="${data_hovercard_url}"></i>
274 </%def>
276 </%def>
275
277
276 <%def name="repo_page_title(repo_instance)">
278 <%def name="repo_page_title(repo_instance)">
277 <div class="title-content repo-title">
279 <div class="title-content repo-title">
278
280
279 <div class="title-main">
281 <div class="title-main">
280 ## SVN/HG/GIT icons
282 ## SVN/HG/GIT icons
281 %if h.is_hg(repo_instance):
283 %if h.is_hg(repo_instance):
282 <i class="icon-hg"></i>
284 <i class="icon-hg"></i>
283 %endif
285 %endif
284 %if h.is_git(repo_instance):
286 %if h.is_git(repo_instance):
285 <i class="icon-git"></i>
287 <i class="icon-git"></i>
286 %endif
288 %endif
287 %if h.is_svn(repo_instance):
289 %if h.is_svn(repo_instance):
288 <i class="icon-svn"></i>
290 <i class="icon-svn"></i>
289 %endif
291 %endif
290
292
291 ## public/private
293 ## public/private
292 %if repo_instance.private:
294 %if repo_instance.private:
293 <i class="icon-repo-private"></i>
295 <i class="icon-repo-private"></i>
294 %else:
296 %else:
295 <i class="icon-repo-public"></i>
297 <i class="icon-repo-public"></i>
296 %endif
298 %endif
297
299
298 ## repo name with group name
300 ## repo name with group name
299 ${h.breadcrumb_repo_link(repo_instance)}
301 ${h.breadcrumb_repo_link(repo_instance)}
300
302
301 ## Context Actions
303 ## Context Actions
302 <div class="pull-right">
304 <div class="pull-right">
303 %if c.rhodecode_user.username != h.DEFAULT_USER:
305 %if c.rhodecode_user.username != h.DEFAULT_USER:
304 <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>
306 <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>
305
307
306 <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 '')}">
308 <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 '')}">
307 % if c.repository_is_user_following:
309 % if c.repository_is_user_following:
308 <i class="icon-eye-off"></i>${_('Unwatch')}
310 <i class="icon-eye-off"></i>${_('Unwatch')}
309 % else:
311 % else:
310 <i class="icon-eye"></i>${_('Watch')}
312 <i class="icon-eye"></i>${_('Watch')}
311 % endif
313 % endif
312
314
313 </a>
315 </a>
314 %else:
316 %else:
315 <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>
317 <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>
316 %endif
318 %endif
317 </div>
319 </div>
318
320
319 </div>
321 </div>
320
322
321 ## FORKED
323 ## FORKED
322 %if repo_instance.fork:
324 %if repo_instance.fork:
323 <p class="discreet">
325 <p class="discreet">
324 <i class="icon-code-fork"></i> ${_('Fork of')}
326 <i class="icon-code-fork"></i> ${_('Fork of')}
325 ${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))}
327 ${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))}
326 </p>
328 </p>
327 %endif
329 %endif
328
330
329 ## IMPORTED FROM REMOTE
331 ## IMPORTED FROM REMOTE
330 %if repo_instance.clone_uri:
332 %if repo_instance.clone_uri:
331 <p class="discreet">
333 <p class="discreet">
332 <i class="icon-code-fork"></i> ${_('Clone from')}
334 <i class="icon-code-fork"></i> ${_('Clone from')}
333 <a href="${h.safe_str(h.hide_credentials(repo_instance.clone_uri))}">${h.hide_credentials(repo_instance.clone_uri)}</a>
335 <a href="${h.safe_str(h.hide_credentials(repo_instance.clone_uri))}">${h.hide_credentials(repo_instance.clone_uri)}</a>
334 </p>
336 </p>
335 %endif
337 %endif
336
338
337 ## LOCKING STATUS
339 ## LOCKING STATUS
338 %if repo_instance.locked[0]:
340 %if repo_instance.locked[0]:
339 <p class="locking_locked discreet">
341 <p class="locking_locked discreet">
340 <i class="icon-repo-lock"></i>
342 <i class="icon-repo-lock"></i>
341 ${_('Repository locked by %(user)s') % {'user': h.person_by_id(repo_instance.locked[0])}}
343 ${_('Repository locked by %(user)s') % {'user': h.person_by_id(repo_instance.locked[0])}}
342 </p>
344 </p>
343 %elif repo_instance.enable_locking:
345 %elif repo_instance.enable_locking:
344 <p class="locking_unlocked discreet">
346 <p class="locking_unlocked discreet">
345 ${_('Repository not locked. Pull repository to lock it.')}
347 ${_('Repository not locked. Pull repository to lock it.')}
346 </p>
348 </p>
347 %endif
349 %endif
348
350
349 </div>
351 </div>
350 </%def>
352 </%def>
351
353
352 <%def name="repo_menu(active=None)">
354 <%def name="repo_menu(active=None)">
353 <%
355 <%
354 ## determine if we have "any" option available
356 ## determine if we have "any" option available
355 can_lock = h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name) and c.rhodecode_db_repo.enable_locking
357 can_lock = h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name) and c.rhodecode_db_repo.enable_locking
356 has_actions = can_lock
358 has_actions = can_lock
357
359
358 %>
360 %>
359 % if c.rhodecode_db_repo.archived:
361 % if c.rhodecode_db_repo.archived:
360 <div class="alert alert-warning text-center">
362 <div class="alert alert-warning text-center">
361 <strong>${_('This repository has been archived. It is now read-only.')}</strong>
363 <strong>${_('This repository has been archived. It is now read-only.')}</strong>
362 </div>
364 </div>
363 % endif
365 % endif
364
366
365 <!--- REPO CONTEXT BAR -->
367 <!--- REPO CONTEXT BAR -->
366 <div id="context-bar">
368 <div id="context-bar">
367 <div class="wrapper">
369 <div class="wrapper">
368
370
369 <div class="title">
371 <div class="title">
370 ${self.repo_page_title(c.rhodecode_db_repo)}
372 ${self.repo_page_title(c.rhodecode_db_repo)}
371 </div>
373 </div>
372
374
373 <ul id="context-pages" class="navigation horizontal-list">
375 <ul id="context-pages" class="navigation horizontal-list">
374 <li class="${h.is_active('summary', active)}"><a class="menulink" href="${h.route_path('repo_summary_explicit', repo_name=c.repo_name)}"><div class="menulabel">${_('Summary')}</div></a></li>
376 <li class="${h.is_active('summary', active)}"><a class="menulink" href="${h.route_path('repo_summary_explicit', repo_name=c.repo_name)}"><div class="menulabel">${_('Summary')}</div></a></li>
375 <li class="${h.is_active('commits', active)}"><a class="menulink" href="${h.route_path('repo_commits', repo_name=c.repo_name)}"><div class="menulabel">${_('Commits')}</div></a></li>
377 <li class="${h.is_active('commits', active)}"><a class="menulink" href="${h.route_path('repo_commits', repo_name=c.repo_name)}"><div class="menulabel">${_('Commits')}</div></a></li>
376 <li class="${h.is_active('files', active)}"><a class="menulink" href="${h.repo_files_by_ref_url(c.repo_name, c.rhodecode_db_repo.repo_type, f_path='', ref_name=c.rhodecode_db_repo.landing_ref_name, commit_id='tip', query={'at':c.rhodecode_db_repo.landing_ref_name})}"><div class="menulabel">${_('Files')}</div></a></li>
378 <li class="${h.is_active('files', active)}"><a class="menulink" href="${h.repo_files_by_ref_url(c.repo_name, c.rhodecode_db_repo.repo_type, f_path='', ref_name=c.rhodecode_db_repo.landing_ref_name, commit_id='tip', query={'at':c.rhodecode_db_repo.landing_ref_name})}"><div class="menulabel">${_('Files')}</div></a></li>
377 <li class="${h.is_active('compare', active)}"><a class="menulink" href="${h.route_path('repo_compare_select',repo_name=c.repo_name)}"><div class="menulabel">${_('Compare')}</div></a></li>
379 <li class="${h.is_active('compare', active)}"><a class="menulink" href="${h.route_path('repo_compare_select',repo_name=c.repo_name)}"><div class="menulabel">${_('Compare')}</div></a></li>
378
380
379 ## TODO: anderson: ideally it would have a function on the scm_instance "enable_pullrequest() and enable_fork()"
381 ## TODO: anderson: ideally it would have a function on the scm_instance "enable_pullrequest() and enable_fork()"
380 %if c.rhodecode_db_repo.repo_type in ['git','hg']:
382 %if c.rhodecode_db_repo.repo_type in ['git','hg']:
381 <li class="${h.is_active('showpullrequest', active)}">
383 <li class="${h.is_active('showpullrequest', active)}">
382 <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)}">
384 <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)}">
383 <div class="menulabel">
385 <div class="menulabel">
384 ${_('Pull Requests')} <span class="menulink-counter">${c.repository_pull_requests}</span>
386 ${_('Pull Requests')} <span class="menulink-counter">${c.repository_pull_requests}</span>
385 </div>
387 </div>
386 </a>
388 </a>
387 </li>
389 </li>
388 %endif
390 %endif
389
391
390 <li class="${h.is_active('artifacts', active)}">
392 <li class="${h.is_active('artifacts', active)}">
391 <a class="menulink" href="${h.route_path('repo_artifacts_list',repo_name=c.repo_name)}">
393 <a class="menulink" href="${h.route_path('repo_artifacts_list',repo_name=c.repo_name)}">
392 <div class="menulabel">
394 <div class="menulabel">
393 ${_('Artifacts')} <span class="menulink-counter">${c.repository_artifacts}</span>
395 ${_('Artifacts')} <span class="menulink-counter">${c.repository_artifacts}</span>
394 </div>
396 </div>
395 </a>
397 </a>
396 </li>
398 </li>
397
399
398 %if not c.rhodecode_db_repo.archived and h.HasRepoPermissionAll('repository.admin')(c.repo_name):
400 %if not c.rhodecode_db_repo.archived and h.HasRepoPermissionAll('repository.admin')(c.repo_name):
399 <li class="${h.is_active('settings', active)}"><a class="menulink" href="${h.route_path('edit_repo',repo_name=c.repo_name)}"><div class="menulabel">${_('Repository Settings')}</div></a></li>
401 <li class="${h.is_active('settings', active)}"><a class="menulink" href="${h.route_path('edit_repo',repo_name=c.repo_name)}"><div class="menulabel">${_('Repository Settings')}</div></a></li>
400 %endif
402 %endif
401
403
402 <li class="${h.is_active('options', active)}">
404 <li class="${h.is_active('options', active)}">
403 % if has_actions:
405 % if has_actions:
404 <a class="menulink dropdown">
406 <a class="menulink dropdown">
405 <div class="menulabel">${_('Quick Actions')}<div class="show_more"></div></div>
407 <div class="menulabel">${_('Quick Actions')}<div class="show_more"></div></div>
406 </a>
408 </a>
407 <ul class="submenu">
409 <ul class="submenu">
408 %if can_lock:
410 %if can_lock:
409 %if c.rhodecode_db_repo.locked[0]:
411 %if c.rhodecode_db_repo.locked[0]:
410 <li><a class="locking_del" href="${h.route_path('repo_settings_quick_actions',repo_name=c.repo_name, _query={'action': 'toggle-lock', 'set_unlock': 1})}">${_('Unlock Repository')}</a></li>
412 <li><a class="locking_del" href="${h.route_path('repo_settings_quick_actions',repo_name=c.repo_name, _query={'action': 'toggle-lock', 'set_unlock': 1})}">${_('Unlock Repository')}</a></li>
411 %else:
413 %else:
412 <li><a class="locking_add" href="${h.route_path('repo_settings_quick_actions',repo_name=c.repo_name, _query={'action': 'toggle-lock', 'set_lock': 1})}">${_('Lock Repository')}</a></li>
414 <li><a class="locking_add" href="${h.route_path('repo_settings_quick_actions',repo_name=c.repo_name, _query={'action': 'toggle-lock', 'set_lock': 1})}">${_('Lock Repository')}</a></li>
413 %endif
415 %endif
414 %endif
416 %endif
415 </ul>
417 </ul>
416 % endif
418 % endif
417 </li>
419 </li>
418
420
419 </ul>
421 </ul>
420 </div>
422 </div>
421 <div class="clear"></div>
423 <div class="clear"></div>
422 </div>
424 </div>
423
425
424 <!--- REPO END CONTEXT BAR -->
426 <!--- REPO END CONTEXT BAR -->
425
427
426 </%def>
428 </%def>
427
429
428 <%def name="repo_group_page_title(repo_group_instance)">
430 <%def name="repo_group_page_title(repo_group_instance)">
429 <div class="title-content">
431 <div class="title-content">
430 <div class="title-main">
432 <div class="title-main">
431 ## Repository Group icon
433 ## Repository Group icon
432 <i class="icon-repo-group"></i>
434 <i class="icon-repo-group"></i>
433
435
434 ## repo name with group name
436 ## repo name with group name
435 ${h.breadcrumb_repo_group_link(repo_group_instance)}
437 ${h.breadcrumb_repo_group_link(repo_group_instance)}
436 </div>
438 </div>
437
439
438 <%namespace name="dt" file="/data_table/_dt_elements.mako"/>
440 <%namespace name="dt" file="/data_table/_dt_elements.mako"/>
439 <div class="repo-group-desc discreet">
441 <div class="repo-group-desc discreet">
440 ${dt.repo_group_desc(repo_group_instance.description_safe, repo_group_instance.personal, c.visual.stylify_metatags)}
442 ${dt.repo_group_desc(repo_group_instance.description_safe, repo_group_instance.personal, c.visual.stylify_metatags)}
441 </div>
443 </div>
442
444
443 </div>
445 </div>
444 </%def>
446 </%def>
445
447
446
448
447 <%def name="repo_group_menu(active=None)">
449 <%def name="repo_group_menu(active=None)">
448 <%
450 <%
449 gr_name = c.repo_group.group_name if c.repo_group else None
451 gr_name = c.repo_group.group_name if c.repo_group else None
450 # create repositories with write permission on group is set to true
452 # create repositories with write permission on group is set to true
451 group_admin = h.HasRepoGroupPermissionAny('group.admin')(gr_name, 'group admin index page')
453 group_admin = h.HasRepoGroupPermissionAny('group.admin')(gr_name, 'group admin index page')
452
454
453 %>
455 %>
454
456
455
457
456 <!--- REPO GROUP CONTEXT BAR -->
458 <!--- REPO GROUP CONTEXT BAR -->
457 <div id="context-bar">
459 <div id="context-bar">
458 <div class="wrapper">
460 <div class="wrapper">
459 <div class="title">
461 <div class="title">
460 ${self.repo_group_page_title(c.repo_group)}
462 ${self.repo_group_page_title(c.repo_group)}
461 </div>
463 </div>
462
464
463 <ul id="context-pages" class="navigation horizontal-list">
465 <ul id="context-pages" class="navigation horizontal-list">
464 <li class="${h.is_active('home', active)}">
466 <li class="${h.is_active('home', active)}">
465 <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>
467 <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>
466 </li>
468 </li>
467 % if c.is_super_admin or group_admin:
469 % if c.is_super_admin or group_admin:
468 <li class="${h.is_active('settings', active)}">
470 <li class="${h.is_active('settings', active)}">
469 <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>
471 <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>
470 </li>
472 </li>
471 % endif
473 % endif
472
474
473 </ul>
475 </ul>
474 </div>
476 </div>
475 <div class="clear"></div>
477 <div class="clear"></div>
476 </div>
478 </div>
477
479
478 <!--- REPO GROUP CONTEXT BAR -->
480 <!--- REPO GROUP CONTEXT BAR -->
479
481
480 </%def>
482 </%def>
481
483
482
484
483 <%def name="usermenu(active=False)">
485 <%def name="usermenu(active=False)">
484 <%
486 <%
485 not_anonymous = c.rhodecode_user.username != h.DEFAULT_USER
487 not_anonymous = c.rhodecode_user.username != h.DEFAULT_USER
486
488
487 gr_name = c.repo_group.group_name if (hasattr(c, 'repo_group') and c.repo_group) else None
489 gr_name = c.repo_group.group_name if (hasattr(c, 'repo_group') and c.repo_group) else None
488 # create repositories with write permission on group is set to true
490 # create repositories with write permission on group is set to true
489
491
490 can_fork = c.is_super_admin or h.HasPermissionAny('hg.fork.repository')()
492 can_fork = c.is_super_admin or h.HasPermissionAny('hg.fork.repository')()
491 create_on_write = h.HasPermissionAny('hg.create.write_on_repogroup.true')()
493 create_on_write = h.HasPermissionAny('hg.create.write_on_repogroup.true')()
492 group_write = h.HasRepoGroupPermissionAny('group.write')(gr_name, 'can write into group index page')
494 group_write = h.HasRepoGroupPermissionAny('group.write')(gr_name, 'can write into group index page')
493 group_admin = h.HasRepoGroupPermissionAny('group.admin')(gr_name, 'group admin index page')
495 group_admin = h.HasRepoGroupPermissionAny('group.admin')(gr_name, 'group admin index page')
494
496
495 can_create_repos = c.is_super_admin or c.can_create_repo
497 can_create_repos = c.is_super_admin or c.can_create_repo
496 can_create_repo_groups = c.is_super_admin or c.can_create_repo_group
498 can_create_repo_groups = c.is_super_admin or c.can_create_repo_group
497
499
498 can_create_repos_in_group = c.is_super_admin or group_admin or (group_write and create_on_write)
500 can_create_repos_in_group = c.is_super_admin or group_admin or (group_write and create_on_write)
499 can_create_repo_groups_in_group = c.is_super_admin or group_admin
501 can_create_repo_groups_in_group = c.is_super_admin or group_admin
500 %>
502 %>
501
503
502 % if not_anonymous:
504 % if not_anonymous:
503 <%
505 <%
504 default_target_group = dict()
506 default_target_group = dict()
505 if c.rhodecode_user.personal_repo_group:
507 if c.rhodecode_user.personal_repo_group:
506 default_target_group = dict(parent_group=c.rhodecode_user.personal_repo_group.group_id)
508 default_target_group = dict(parent_group=c.rhodecode_user.personal_repo_group.group_id)
507 %>
509 %>
508
510
509 ## create action
511 ## create action
510 <li>
512 <li>
511 <a href="#create-actions" onclick="return false;" class="menulink childs">
513 <a href="#create-actions" onclick="return false;" class="menulink childs">
512 <i class="icon-plus-circled"></i>
514 <i class="icon-plus-circled"></i>
513 </a>
515 </a>
514
516
515 <div class="action-menu submenu">
517 <div class="action-menu submenu">
516
518
517 <ol>
519 <ol>
518 ## scope of within a repository
520 ## scope of within a repository
519 % if hasattr(c, 'rhodecode_db_repo') and c.rhodecode_db_repo:
521 % if hasattr(c, 'rhodecode_db_repo') and c.rhodecode_db_repo:
520 <li class="submenu-title">${_('This Repository')}</li>
522 <li class="submenu-title">${_('This Repository')}</li>
521 <li>
523 <li>
522 <a href="${h.route_path('pullrequest_new',repo_name=c.repo_name)}">${_('Create Pull Request')}</a>
524 <a href="${h.route_path('pullrequest_new',repo_name=c.repo_name)}">${_('Create Pull Request')}</a>
523 </li>
525 </li>
524 % if can_fork:
526 % if can_fork:
525 <li>
527 <li>
526 <a href="${h.route_path('repo_fork_new',repo_name=c.repo_name,_query=default_target_group)}">${_('Fork this repository')}</a>
528 <a href="${h.route_path('repo_fork_new',repo_name=c.repo_name,_query=default_target_group)}">${_('Fork this repository')}</a>
527 </li>
529 </li>
528 % endif
530 % endif
529 % endif
531 % endif
530
532
531 ## scope of within repository groups
533 ## scope of within repository groups
532 % if hasattr(c, 'repo_group') and c.repo_group and (can_create_repos_in_group or can_create_repo_groups_in_group):
534 % if hasattr(c, 'repo_group') and c.repo_group and (can_create_repos_in_group or can_create_repo_groups_in_group):
533 <li class="submenu-title">${_('This Repository Group')}</li>
535 <li class="submenu-title">${_('This Repository Group')}</li>
534
536
535 % if can_create_repos_in_group:
537 % if can_create_repos_in_group:
536 <li>
538 <li>
537 <a href="${h.route_path('repo_new',_query=dict(parent_group=c.repo_group.group_id))}">${_('New Repository')}</a>
539 <a href="${h.route_path('repo_new',_query=dict(parent_group=c.repo_group.group_id))}">${_('New Repository')}</a>
538 </li>
540 </li>
539 % endif
541 % endif
540
542
541 % if can_create_repo_groups_in_group:
543 % if can_create_repo_groups_in_group:
542 <li>
544 <li>
543 <a href="${h.route_path('repo_group_new',_query=dict(parent_group=c.repo_group.group_id))}">${_(u'New Repository Group')}</a>
545 <a href="${h.route_path('repo_group_new',_query=dict(parent_group=c.repo_group.group_id))}">${_('New Repository Group')}</a>
544 </li>
546 </li>
545 % endif
547 % endif
546 % endif
548 % endif
547
549
548 ## personal group
550 ## personal group
549 % if c.rhodecode_user.personal_repo_group:
551 % if c.rhodecode_user.personal_repo_group:
550 <li class="submenu-title">Personal Group</li>
552 <li class="submenu-title">Personal Group</li>
551
553
552 <li>
554 <li>
553 <a href="${h.route_path('repo_new',_query=dict(parent_group=c.rhodecode_user.personal_repo_group.group_id))}" >${_('New Repository')} </a>
555 <a href="${h.route_path('repo_new',_query=dict(parent_group=c.rhodecode_user.personal_repo_group.group_id))}" >${_('New Repository')} </a>
554 </li>
556 </li>
555
557
556 <li>
558 <li>
557 <a href="${h.route_path('repo_group_new',_query=dict(parent_group=c.rhodecode_user.personal_repo_group.group_id))}">${_('New Repository Group')} </a>
559 <a href="${h.route_path('repo_group_new',_query=dict(parent_group=c.rhodecode_user.personal_repo_group.group_id))}">${_('New Repository Group')} </a>
558 </li>
560 </li>
559 % endif
561 % endif
560
562
561 ## Global actions
563 ## Global actions
562 <li class="submenu-title">RhodeCode</li>
564 <li class="submenu-title">RhodeCode</li>
563 % if can_create_repos:
565 % if can_create_repos:
564 <li>
566 <li>
565 <a href="${h.route_path('repo_new')}" >${_('New Repository')}</a>
567 <a href="${h.route_path('repo_new')}" >${_('New Repository')}</a>
566 </li>
568 </li>
567 % endif
569 % endif
568
570
569 % if can_create_repo_groups:
571 % if can_create_repo_groups:
570 <li>
572 <li>
571 <a href="${h.route_path('repo_group_new')}" >${_(u'New Repository Group')}</a>
573 <a href="${h.route_path('repo_group_new')}" >${_('New Repository Group')}</a>
572 </li>
574 </li>
573 % endif
575 % endif
574
576
575 <li>
577 <li>
576 <a href="${h.route_path('gists_new')}">${_(u'New Gist')}</a>
578 <a href="${h.route_path('gists_new')}">${_('New Gist')}</a>
577 </li>
579 </li>
578
580
579 </ol>
581 </ol>
580
582
581 </div>
583 </div>
582 </li>
584 </li>
583
585
584 ## notifications
586 ## notifications
585 <li>
587 <li>
586 <a class="${('empty' if c.unread_notifications == 0 else '')}" href="${h.route_path('notifications_show_all')}">
588 <a class="${('empty' if c.unread_notifications == 0 else '')}" href="${h.route_path('notifications_show_all')}">
587 ${c.unread_notifications}
589 ${c.unread_notifications}
588 </a>
590 </a>
589 </li>
591 </li>
590 % endif
592 % endif
591
593
592 ## USER MENU
594 ## USER MENU
593 <li id="quick_login_li" class="${'active' if active else ''}">
595 <li id="quick_login_li" class="${'active' if active else ''}">
594 % if c.rhodecode_user.username == h.DEFAULT_USER:
596 % if c.rhodecode_user.username == h.DEFAULT_USER:
595 <a id="quick_login_link" class="menulink childs" href="${h.route_path('login', _query={'came_from': h.current_route_path(request)})}">
597 <a id="quick_login_link" class="menulink childs" href="${h.route_path('login', _query={'came_from': h.current_route_path(request)})}">
596 ${gravatar(c.rhodecode_user.email, 20)}
598 ${gravatar(c.rhodecode_user.email, 20)}
597 <span class="user">
599 <span class="user">
598 <span>${_('Sign in')}</span>
600 <span>${_('Sign in')}</span>
599 </span>
601 </span>
600 </a>
602 </a>
601 % else:
603 % else:
602 ## logged in user
604 ## logged in user
603 <a id="quick_login_link" class="menulink childs">
605 <a id="quick_login_link" class="menulink childs">
604 ${gravatar(c.rhodecode_user.email, 20)}
606 ${gravatar(c.rhodecode_user.email, 20)}
605 <span class="user">
607 <span class="user">
606 <span class="menu_link_user">${c.rhodecode_user.username}</span>
608 <span class="menu_link_user">${c.rhodecode_user.username}</span>
607 <div class="show_more"></div>
609 <div class="show_more"></div>
608 </span>
610 </span>
609 </a>
611 </a>
610 ## subnav with menu for logged in user
612 ## subnav with menu for logged in user
611 <div class="user-menu submenu">
613 <div class="user-menu submenu">
612 <div id="quick_login">
614 <div id="quick_login">
613 %if c.rhodecode_user.username != h.DEFAULT_USER:
615 %if c.rhodecode_user.username != h.DEFAULT_USER:
614 <div class="">
616 <div class="">
615 <div class="big_gravatar">${gravatar(c.rhodecode_user.email, 48)}</div>
617 <div class="big_gravatar">${gravatar(c.rhodecode_user.email, 48)}</div>
616 <div class="full_name">${c.rhodecode_user.full_name_or_username}</div>
618 <div class="full_name">${c.rhodecode_user.full_name_or_username}</div>
617 <div class="email">${c.rhodecode_user.email}</div>
619 <div class="email">${c.rhodecode_user.email}</div>
618 </div>
620 </div>
619 <div class="">
621 <div class="">
620 <ol class="links">
622 <ol class="links">
621 <li>${h.link_to(_(u'My account'),h.route_path('my_account_profile'))}</li>
623 <li>${h.link_to(_(u'My account'),h.route_path('my_account_profile'))}</li>
622 % if c.rhodecode_user.personal_repo_group:
624 % if c.rhodecode_user.personal_repo_group:
623 <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>
625 <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>
624 % endif
626 % endif
625 <li>${h.link_to(_(u'Pull Requests'), h.route_path('my_account_pullrequests'))}</li>
627 <li>${h.link_to(_(u'Pull Requests'), h.route_path('my_account_pullrequests'))}</li>
626
628
627 % if c.debug_style:
629 % if c.debug_style:
628 <li>
630 <li>
629 <a class="menulink" title="${_('Style')}" href="${h.route_path('debug_style_home')}">
631 <a class="menulink" title="${_('Style')}" href="${h.route_path('debug_style_home')}">
630 <div class="menulabel">${_('[Style]')}</div>
632 <div class="menulabel">${_('[Style]')}</div>
631 </a>
633 </a>
632 </li>
634 </li>
633 % endif
635 % endif
634
636
635 ## bookmark-items
637 ## bookmark-items
636 <li class="bookmark-items">
638 <li class="bookmark-items">
637 ${_('Bookmarks')}
639 ${_('Bookmarks')}
638 <div class="pull-right">
640 <div class="pull-right">
639 <a href="${h.route_path('my_account_bookmarks')}">
641 <a href="${h.route_path('my_account_bookmarks')}">
640
642
641 <i class="icon-cog"></i>
643 <i class="icon-cog"></i>
642 </a>
644 </a>
643 </div>
645 </div>
644 </li>
646 </li>
645 % if not c.bookmark_items:
647 % if not c.bookmark_items:
646 <li>
648 <li>
647 <a href="${h.route_path('my_account_bookmarks')}">${_('No Bookmarks yet.')}</a>
649 <a href="${h.route_path('my_account_bookmarks')}">${_('No Bookmarks yet.')}</a>
648 </li>
650 </li>
649 % endif
651 % endif
650 % for item in c.bookmark_items:
652 % for item in c.bookmark_items:
651 <li>
653 <li>
652 % if item.repository:
654 % if item.repository:
653 <div>
655 <div>
654 <a class="bookmark-item" href="${h.route_path('my_account_goto_bookmark', bookmark_id=item.position)}">
656 <a class="bookmark-item" href="${h.route_path('my_account_goto_bookmark', bookmark_id=item.position)}">
655 <code>${item.position}</code>
657 <code>${item.position}</code>
656 % if item.repository.repo_type == 'hg':
658 % if item.repository.repo_type == 'hg':
657 <i class="icon-hg" title="${_('Repository')}" style="font-size: 16px"></i>
659 <i class="icon-hg" title="${_('Repository')}" style="font-size: 16px"></i>
658 % elif item.repository.repo_type == 'git':
660 % elif item.repository.repo_type == 'git':
659 <i class="icon-git" title="${_('Repository')}" style="font-size: 16px"></i>
661 <i class="icon-git" title="${_('Repository')}" style="font-size: 16px"></i>
660 % elif item.repository.repo_type == 'svn':
662 % elif item.repository.repo_type == 'svn':
661 <i class="icon-svn" title="${_('Repository')}" style="font-size: 16px"></i>
663 <i class="icon-svn" title="${_('Repository')}" style="font-size: 16px"></i>
662 % endif
664 % endif
663 ${(item.title or h.shorter(item.repository.repo_name, 30))}
665 ${(item.title or h.shorter(item.repository.repo_name, 30))}
664 </a>
666 </a>
665 </div>
667 </div>
666 % elif item.repository_group:
668 % elif item.repository_group:
667 <div>
669 <div>
668 <a class="bookmark-item" href="${h.route_path('my_account_goto_bookmark', bookmark_id=item.position)}">
670 <a class="bookmark-item" href="${h.route_path('my_account_goto_bookmark', bookmark_id=item.position)}">
669 <code>${item.position}</code>
671 <code>${item.position}</code>
670 <i class="icon-repo-group" title="${_('Repository group')}" style="font-size: 14px"></i>
672 <i class="icon-repo-group" title="${_('Repository group')}" style="font-size: 14px"></i>
671 ${(item.title or h.shorter(item.repository_group.group_name, 30))}
673 ${(item.title or h.shorter(item.repository_group.group_name, 30))}
672 </a>
674 </a>
673 </div>
675 </div>
674 % else:
676 % else:
675 <a class="bookmark-item" href="${h.route_path('my_account_goto_bookmark', bookmark_id=item.position)}">
677 <a class="bookmark-item" href="${h.route_path('my_account_goto_bookmark', bookmark_id=item.position)}">
676 <code>${item.position}</code>
678 <code>${item.position}</code>
677 ${item.title}
679 ${item.title}
678 </a>
680 </a>
679 % endif
681 % endif
680 </li>
682 </li>
681 % endfor
683 % endfor
682
684
683 <li class="logout">
685 <li class="logout">
684 ${h.secure_form(h.route_path('logout'), request=request)}
686 ${h.secure_form(h.route_path('logout'), request=request)}
685 ${h.submit('log_out', _(u'Sign Out'),class_="btn btn-primary")}
687 ${h.submit('log_out', _(u'Sign Out'),class_="btn btn-primary")}
686 ${h.end_form()}
688 ${h.end_form()}
687 </li>
689 </li>
688 </ol>
690 </ol>
689 </div>
691 </div>
690 %endif
692 %endif
691 </div>
693 </div>
692 </div>
694 </div>
693
695
694 % endif
696 % endif
695 </li>
697 </li>
696 </%def>
698 </%def>
697
699
698 <%def name="menu_items(active=None)">
700 <%def name="menu_items(active=None)">
699 <%
701 <%
700 notice_messages, notice_level = c.rhodecode_user.get_notice_messages()
702 notice_messages, notice_level = c.rhodecode_user.get_notice_messages()
701 notice_display = 'none' if len(notice_messages) == 0 else ''
703 notice_display = 'none' if len(notice_messages) == 0 else ''
702 %>
704 %>
703
705
704 <ul id="quick" class="main_nav navigation horizontal-list">
706 <ul id="quick" class="main_nav navigation horizontal-list">
705 ## notice box for important system messages
707 ## notice box for important system messages
706 <li style="display: ${notice_display}">
708 <li style="display: ${notice_display}">
707 <a class="notice-box" href="#openNotice" onclick="$('.notice-messages-container').toggle(); return false">
709 <a class="notice-box" href="#openNotice" onclick="$('.notice-messages-container').toggle(); return false">
708 <div class="menulabel-notice ${notice_level}" >
710 <div class="menulabel-notice ${notice_level}" >
709 ${len(notice_messages)}
711 ${len(notice_messages)}
710 </div>
712 </div>
711 </a>
713 </a>
712 </li>
714 </li>
713 <div class="notice-messages-container" style="display: none">
715 <div class="notice-messages-container" style="display: none">
714 <div class="notice-messages">
716 <div class="notice-messages">
715 <table class="rctable">
717 <table class="rctable">
716 % for notice in notice_messages:
718 % for notice in notice_messages:
717 <tr id="notice-message-${notice['msg_id']}" class="notice-message-${notice['level']}">
719 <tr id="notice-message-${notice['msg_id']}" class="notice-message-${notice['level']}">
718 <td style="vertical-align: text-top; width: 20px">
720 <td style="vertical-align: text-top; width: 20px">
719 <i class="tooltip icon-info notice-color-${notice['level']}" title="${notice['level']}"></i>
721 <i class="tooltip icon-info notice-color-${notice['level']}" title="${notice['level']}"></i>
720 </td>
722 </td>
721 <td>
723 <td>
722 <span><i class="icon-plus-squared cursor-pointer" onclick="$('#notice-${notice['msg_id']}').toggle()"></i> </span>
724 <span><i class="icon-plus-squared cursor-pointer" onclick="$('#notice-${notice['msg_id']}').toggle()"></i> </span>
723 ${notice['subject']}
725 ${notice['subject']}
724
726
725 <div id="notice-${notice['msg_id']}" style="display: none">
727 <div id="notice-${notice['msg_id']}" style="display: none">
726 ${h.render(notice['body'], renderer='markdown')}
728 ${h.render(notice['body'], renderer='markdown')}
727 </div>
729 </div>
728 </td>
730 </td>
729 <td style="vertical-align: text-top; width: 35px;">
731 <td style="vertical-align: text-top; width: 35px;">
730 <a class="tooltip" title="${_('dismiss')}" href="#dismiss" onclick="dismissNotice(${notice['msg_id']});return false">
732 <a class="tooltip" title="${_('dismiss')}" href="#dismiss" onclick="dismissNotice(${notice['msg_id']});return false">
731 <i class="icon-remove icon-filled-red"></i>
733 <i class="icon-remove icon-filled-red"></i>
732 </a>
734 </a>
733 </td>
735 </td>
734 </tr>
736 </tr>
735
737
736 % endfor
738 % endfor
737 </table>
739 </table>
738 </div>
740 </div>
739 </div>
741 </div>
740 ## Main filter
742 ## Main filter
741 <li>
743 <li>
742 <div class="menulabel main_filter_box">
744 <div class="menulabel main_filter_box">
743 <div class="main_filter_input_box">
745 <div class="main_filter_input_box">
744 <ul class="searchItems">
746 <ul class="searchItems">
745
747
746 <li class="searchTag searchTagIcon">
748 <li class="searchTag searchTagIcon">
747 <i class="icon-search"></i>
749 <i class="icon-search"></i>
748 </li>
750 </li>
749
751
750 % if c.template_context['search_context']['repo_id']:
752 % if c.template_context['search_context']['repo_id']:
751 <li class="searchTag searchTagFilter searchTagHidable" >
753 <li class="searchTag searchTagFilter searchTagHidable" >
752 ##<a href="${h.route_path('search_repo',repo_name=c.template_context['search_context']['repo_name'])}">
754 ##<a href="${h.route_path('search_repo',repo_name=c.template_context['search_context']['repo_name'])}">
753 <span class="tag">
755 <span class="tag">
754 This repo
756 This repo
755 <a href="#removeGoToFilter" onclick="removeGoToFilter(); return false"><i class="icon-cancel-circled"></i></a>
757 <a href="#removeGoToFilter" onclick="removeGoToFilter(); return false"><i class="icon-cancel-circled"></i></a>
756 </span>
758 </span>
757 ##</a>
759 ##</a>
758 </li>
760 </li>
759 % elif c.template_context['search_context']['repo_group_id']:
761 % elif c.template_context['search_context']['repo_group_id']:
760 <li class="searchTag searchTagFilter searchTagHidable">
762 <li class="searchTag searchTagFilter searchTagHidable">
761 ##<a href="${h.route_path('search_repo_group',repo_group_name=c.template_context['search_context']['repo_group_name'])}">
763 ##<a href="${h.route_path('search_repo_group',repo_group_name=c.template_context['search_context']['repo_group_name'])}">
762 <span class="tag">
764 <span class="tag">
763 This group
765 This group
764 <a href="#removeGoToFilter" onclick="removeGoToFilter(); return false"><i class="icon-cancel-circled"></i></a>
766 <a href="#removeGoToFilter" onclick="removeGoToFilter(); return false"><i class="icon-cancel-circled"></i></a>
765 </span>
767 </span>
766 ##</a>
768 ##</a>
767 </li>
769 </li>
768 % endif
770 % endif
769
771
770 <li class="searchTagInput">
772 <li class="searchTagInput">
771 <input class="main_filter_input" id="main_filter" size="25" type="text" name="main_filter" placeholder="${_('search / go to...')}" value="" />
773 <input class="main_filter_input" id="main_filter" size="25" type="text" name="main_filter" placeholder="${_('search / go to...')}" value="" />
772 </li>
774 </li>
773 <li class="searchTag searchTagHelp">
775 <li class="searchTag searchTagHelp">
774 <a href="#showFilterHelp" onclick="showMainFilterBox(); return false">?</a>
776 <a href="#showFilterHelp" onclick="showMainFilterBox(); return false">?</a>
775 </li>
777 </li>
776 </ul>
778 </ul>
777 </div>
779 </div>
778 </div>
780 </div>
779
781
780 <div id="main_filter_help" style="display: none">
782 <div id="main_filter_help" style="display: none">
781 - Use '/' key to quickly access this field.
783 - Use '/' key to quickly access this field.
782
784
783 - Enter a name of repository, or repository group for quick search.
785 - Enter a name of repository, or repository group for quick search.
784
786
785 - Prefix query to allow special search:
787 - Prefix query to allow special search:
786
788
787 <strong>user:</strong>admin, to search for usernames, always global
789 <strong>user:</strong>admin, to search for usernames, always global
788
790
789 <strong>user_group:</strong>devops, to search for user groups, always global
791 <strong>user_group:</strong>devops, to search for user groups, always global
790
792
791 <strong>pr:</strong>303, to search for pull request number, title, or description, always global
793 <strong>pr:</strong>303, to search for pull request number, title, or description, always global
792
794
793 <strong>commit:</strong>efced4, to search for commits, scoped to repositories or groups
795 <strong>commit:</strong>efced4, to search for commits, scoped to repositories or groups
794
796
795 <strong>file:</strong>models.py, to search for file paths, scoped to repositories or groups
797 <strong>file:</strong>models.py, to search for file paths, scoped to repositories or groups
796
798
797 % if c.template_context['search_context']['repo_id']:
799 % if c.template_context['search_context']['repo_id']:
798 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>
800 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>
799 % elif c.template_context['search_context']['repo_group_id']:
801 % elif c.template_context['search_context']['repo_group_id']:
800 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>
802 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>
801 % else:
803 % else:
802 For advanced full text search visit: <a href="${h.route_path('search')}">global search</a>
804 For advanced full text search visit: <a href="${h.route_path('search')}">global search</a>
803 % endif
805 % endif
804 </div>
806 </div>
805 </li>
807 </li>
806
808
807 ## ROOT MENU
809 ## ROOT MENU
808 <li class="${h.is_active('home', active)}">
810 <li class="${h.is_active('home', active)}">
809 <a class="menulink" title="${_('Home')}" href="${h.route_path('home')}">
811 <a class="menulink" title="${_('Home')}" href="${h.route_path('home')}">
810 <div class="menulabel">${_('Home')}</div>
812 <div class="menulabel">${_('Home')}</div>
811 </a>
813 </a>
812 </li>
814 </li>
813
815
814 %if c.rhodecode_user.username != h.DEFAULT_USER:
816 %if c.rhodecode_user.username != h.DEFAULT_USER:
815 <li class="${h.is_active('journal', active)}">
817 <li class="${h.is_active('journal', active)}">
816 <a class="menulink" title="${_('Show activity journal')}" href="${h.route_path('journal')}">
818 <a class="menulink" title="${_('Show activity journal')}" href="${h.route_path('journal')}">
817 <div class="menulabel">${_('Journal')}</div>
819 <div class="menulabel">${_('Journal')}</div>
818 </a>
820 </a>
819 </li>
821 </li>
820 %else:
822 %else:
821 <li class="${h.is_active('journal', active)}">
823 <li class="${h.is_active('journal', active)}">
822 <a class="menulink" title="${_('Show Public activity journal')}" href="${h.route_path('journal_public')}">
824 <a class="menulink" title="${_('Show Public activity journal')}" href="${h.route_path('journal_public')}">
823 <div class="menulabel">${_('Public journal')}</div>
825 <div class="menulabel">${_('Public journal')}</div>
824 </a>
826 </a>
825 </li>
827 </li>
826 %endif
828 %endif
827
829
828 <li class="${h.is_active('gists', active)}">
830 <li class="${h.is_active('gists', active)}">
829 <a class="menulink childs" title="${_('Show Gists')}" href="${h.route_path('gists_show')}">
831 <a class="menulink childs" title="${_('Show Gists')}" href="${h.route_path('gists_show')}">
830 <div class="menulabel">${_('Gists')}</div>
832 <div class="menulabel">${_('Gists')}</div>
831 </a>
833 </a>
832 </li>
834 </li>
833
835
834 % if c.is_super_admin or c.is_delegated_admin:
836 % if c.is_super_admin or c.is_delegated_admin:
835 <li class="${h.is_active('admin', active)}">
837 <li class="${h.is_active('admin', active)}">
836 <a class="menulink childs" title="${_('Admin settings')}" href="${h.route_path('admin_home')}">
838 <a class="menulink childs" title="${_('Admin settings')}" href="${h.route_path('admin_home')}">
837 <div class="menulabel">${_('Admin')} </div>
839 <div class="menulabel">${_('Admin')} </div>
838 </a>
840 </a>
839 </li>
841 </li>
840 % endif
842 % endif
841
843
842 ## render extra user menu
844 ## render extra user menu
843 ${usermenu(active=(active=='my_account'))}
845 ${usermenu(active=(active=='my_account'))}
844
846
845 </ul>
847 </ul>
846
848
847 <script type="text/javascript">
849 <script type="text/javascript">
848 var visualShowPublicIcon = "${c.visual.show_public_icon}" == "True";
850 var visualShowPublicIcon = "${c.visual.show_public_icon}" == "True";
849
851
850 var formatRepoResult = function(result, container, query, escapeMarkup) {
852 var formatRepoResult = function(result, container, query, escapeMarkup) {
851 return function(data, escapeMarkup) {
853 return function(data, escapeMarkup) {
852 if (!data.repo_id){
854 if (!data.repo_id){
853 return data.text; // optgroup text Repositories
855 return data.text; // optgroup text Repositories
854 }
856 }
855
857
856 var tmpl = '';
858 var tmpl = '';
857 var repoType = data['repo_type'];
859 var repoType = data['repo_type'];
858 var repoName = data['text'];
860 var repoName = data['text'];
859
861
860 if(data && data.type == 'repo'){
862 if(data && data.type == 'repo'){
861 if(repoType === 'hg'){
863 if(repoType === 'hg'){
862 tmpl += '<i class="icon-hg"></i> ';
864 tmpl += '<i class="icon-hg"></i> ';
863 }
865 }
864 else if(repoType === 'git'){
866 else if(repoType === 'git'){
865 tmpl += '<i class="icon-git"></i> ';
867 tmpl += '<i class="icon-git"></i> ';
866 }
868 }
867 else if(repoType === 'svn'){
869 else if(repoType === 'svn'){
868 tmpl += '<i class="icon-svn"></i> ';
870 tmpl += '<i class="icon-svn"></i> ';
869 }
871 }
870 if(data['private']){
872 if(data['private']){
871 tmpl += '<i class="icon-lock" ></i> ';
873 tmpl += '<i class="icon-lock" ></i> ';
872 }
874 }
873 else if(visualShowPublicIcon){
875 else if(visualShowPublicIcon){
874 tmpl += '<i class="icon-unlock-alt"></i> ';
876 tmpl += '<i class="icon-unlock-alt"></i> ';
875 }
877 }
876 }
878 }
877 tmpl += escapeMarkup(repoName);
879 tmpl += escapeMarkup(repoName);
878 return tmpl;
880 return tmpl;
879
881
880 }(result, escapeMarkup);
882 }(result, escapeMarkup);
881 };
883 };
882
884
883 var formatRepoGroupResult = function(result, container, query, escapeMarkup) {
885 var formatRepoGroupResult = function(result, container, query, escapeMarkup) {
884 return function(data, escapeMarkup) {
886 return function(data, escapeMarkup) {
885 if (!data.repo_group_id){
887 if (!data.repo_group_id){
886 return data.text; // optgroup text Repositories
888 return data.text; // optgroup text Repositories
887 }
889 }
888
890
889 var tmpl = '';
891 var tmpl = '';
890 var repoGroupName = data['text'];
892 var repoGroupName = data['text'];
891
893
892 if(data){
894 if(data){
893
895
894 tmpl += '<i class="icon-repo-group"></i> ';
896 tmpl += '<i class="icon-repo-group"></i> ';
895
897
896 }
898 }
897 tmpl += escapeMarkup(repoGroupName);
899 tmpl += escapeMarkup(repoGroupName);
898 return tmpl;
900 return tmpl;
899
901
900 }(result, escapeMarkup);
902 }(result, escapeMarkup);
901 };
903 };
902
904
903 var escapeRegExChars = function (value) {
905 var escapeRegExChars = function (value) {
904 return value.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
906 return value.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
905 };
907 };
906
908
907 var getRepoIcon = function(repo_type) {
909 var getRepoIcon = function(repo_type) {
908 if (repo_type === 'hg') {
910 if (repo_type === 'hg') {
909 return '<i class="icon-hg"></i> ';
911 return '<i class="icon-hg"></i> ';
910 }
912 }
911 else if (repo_type === 'git') {
913 else if (repo_type === 'git') {
912 return '<i class="icon-git"></i> ';
914 return '<i class="icon-git"></i> ';
913 }
915 }
914 else if (repo_type === 'svn') {
916 else if (repo_type === 'svn') {
915 return '<i class="icon-svn"></i> ';
917 return '<i class="icon-svn"></i> ';
916 }
918 }
917 return ''
919 return ''
918 };
920 };
919
921
920 var autocompleteMainFilterFormatResult = function (data, value, org_formatter) {
922 var autocompleteMainFilterFormatResult = function (data, value, org_formatter) {
921
923
922 if (value.split(':').length === 2) {
924 if (value.split(':').length === 2) {
923 value = value.split(':')[1]
925 value = value.split(':')[1]
924 }
926 }
925
927
926 var searchType = data['type'];
928 var searchType = data['type'];
927 var searchSubType = data['subtype'];
929 var searchSubType = data['subtype'];
928 var valueDisplay = data['value_display'];
930 var valueDisplay = data['value_display'];
929 var valueIcon = data['value_icon'];
931 var valueIcon = data['value_icon'];
930
932
931 var pattern = '(' + escapeRegExChars(value) + ')';
933 var pattern = '(' + escapeRegExChars(value) + ')';
932
934
933 valueDisplay = Select2.util.escapeMarkup(valueDisplay);
935 valueDisplay = Select2.util.escapeMarkup(valueDisplay);
934
936
935 // highlight match
937 // highlight match
936 if (searchType != 'text') {
938 if (searchType != 'text') {
937 valueDisplay = valueDisplay.replace(new RegExp(pattern, 'gi'), '<strong>$1<\/strong>');
939 valueDisplay = valueDisplay.replace(new RegExp(pattern, 'gi'), '<strong>$1<\/strong>');
938 }
940 }
939
941
940 var icon = '';
942 var icon = '';
941
943
942 if (searchType === 'hint') {
944 if (searchType === 'hint') {
943 icon += '<i class="icon-repo-group"></i> ';
945 icon += '<i class="icon-repo-group"></i> ';
944 }
946 }
945 // full text search/hints
947 // full text search/hints
946 else if (searchType === 'search') {
948 else if (searchType === 'search') {
947 if (valueIcon === undefined) {
949 if (valueIcon === undefined) {
948 icon += '<i class="icon-more"></i> ';
950 icon += '<i class="icon-more"></i> ';
949 } else {
951 } else {
950 icon += valueIcon + ' ';
952 icon += valueIcon + ' ';
951 }
953 }
952
954
953 if (searchSubType !== undefined && searchSubType == 'repo') {
955 if (searchSubType !== undefined && searchSubType == 'repo') {
954 valueDisplay += '<div class="pull-right tag">repository</div>';
956 valueDisplay += '<div class="pull-right tag">repository</div>';
955 }
957 }
956 else if (searchSubType !== undefined && searchSubType == 'repo_group') {
958 else if (searchSubType !== undefined && searchSubType == 'repo_group') {
957 valueDisplay += '<div class="pull-right tag">repo group</div>';
959 valueDisplay += '<div class="pull-right tag">repo group</div>';
958 }
960 }
959 }
961 }
960 // repository
962 // repository
961 else if (searchType === 'repo') {
963 else if (searchType === 'repo') {
962
964
963 var repoIcon = getRepoIcon(data['repo_type']);
965 var repoIcon = getRepoIcon(data['repo_type']);
964 icon += repoIcon;
966 icon += repoIcon;
965
967
966 if (data['private']) {
968 if (data['private']) {
967 icon += '<i class="icon-lock" ></i> ';
969 icon += '<i class="icon-lock" ></i> ';
968 }
970 }
969 else if (visualShowPublicIcon) {
971 else if (visualShowPublicIcon) {
970 icon += '<i class="icon-unlock-alt"></i> ';
972 icon += '<i class="icon-unlock-alt"></i> ';
971 }
973 }
972 }
974 }
973 // repository groups
975 // repository groups
974 else if (searchType === 'repo_group') {
976 else if (searchType === 'repo_group') {
975 icon += '<i class="icon-repo-group"></i> ';
977 icon += '<i class="icon-repo-group"></i> ';
976 }
978 }
977 // user group
979 // user group
978 else if (searchType === 'user_group') {
980 else if (searchType === 'user_group') {
979 icon += '<i class="icon-group"></i> ';
981 icon += '<i class="icon-group"></i> ';
980 }
982 }
981 // user
983 // user
982 else if (searchType === 'user') {
984 else if (searchType === 'user') {
983 icon += '<img class="gravatar" src="{0}"/>'.format(data['icon_link']);
985 icon += '<img class="gravatar" src="{0}"/>'.format(data['icon_link']);
984 }
986 }
985 // pull request
987 // pull request
986 else if (searchType === 'pull_request') {
988 else if (searchType === 'pull_request') {
987 icon += '<i class="icon-merge"></i> ';
989 icon += '<i class="icon-merge"></i> ';
988 }
990 }
989 // commit
991 // commit
990 else if (searchType === 'commit') {
992 else if (searchType === 'commit') {
991 var repo_data = data['repo_data'];
993 var repo_data = data['repo_data'];
992 var repoIcon = getRepoIcon(repo_data['repository_type']);
994 var repoIcon = getRepoIcon(repo_data['repository_type']);
993 if (repoIcon) {
995 if (repoIcon) {
994 icon += repoIcon;
996 icon += repoIcon;
995 } else {
997 } else {
996 icon += '<i class="icon-tag"></i>';
998 icon += '<i class="icon-tag"></i>';
997 }
999 }
998 }
1000 }
999 // file
1001 // file
1000 else if (searchType === 'file') {
1002 else if (searchType === 'file') {
1001 var repo_data = data['repo_data'];
1003 var repo_data = data['repo_data'];
1002 var repoIcon = getRepoIcon(repo_data['repository_type']);
1004 var repoIcon = getRepoIcon(repo_data['repository_type']);
1003 if (repoIcon) {
1005 if (repoIcon) {
1004 icon += repoIcon;
1006 icon += repoIcon;
1005 } else {
1007 } else {
1006 icon += '<i class="icon-tag"></i>';
1008 icon += '<i class="icon-tag"></i>';
1007 }
1009 }
1008 }
1010 }
1009 // generic text
1011 // generic text
1010 else if (searchType === 'text') {
1012 else if (searchType === 'text') {
1011 icon = '';
1013 icon = '';
1012 }
1014 }
1013
1015
1014 var tmpl = '<div class="ac-container-wrap">{0}{1}</div>';
1016 var tmpl = '<div class="ac-container-wrap">{0}{1}</div>';
1015 return tmpl.format(icon, valueDisplay);
1017 return tmpl.format(icon, valueDisplay);
1016 };
1018 };
1017
1019
1018 var handleSelect = function(element, suggestion) {
1020 var handleSelect = function(element, suggestion) {
1019 if (suggestion.type === "hint") {
1021 if (suggestion.type === "hint") {
1020 // we skip action
1022 // we skip action
1021 $('#main_filter').focus();
1023 $('#main_filter').focus();
1022 }
1024 }
1023 else if (suggestion.type === "text") {
1025 else if (suggestion.type === "text") {
1024 // we skip action
1026 // we skip action
1025 $('#main_filter').focus();
1027 $('#main_filter').focus();
1026
1028
1027 } else {
1029 } else {
1028 window.location = suggestion['url'];
1030 window.location = suggestion['url'];
1029 }
1031 }
1030 };
1032 };
1031
1033
1032 var autocompleteMainFilterResult = function (suggestion, originalQuery, queryLowerCase) {
1034 var autocompleteMainFilterResult = function (suggestion, originalQuery, queryLowerCase) {
1033 if (queryLowerCase.split(':').length === 2) {
1035 if (queryLowerCase.split(':').length === 2) {
1034 queryLowerCase = queryLowerCase.split(':')[1]
1036 queryLowerCase = queryLowerCase.split(':')[1]
1035 }
1037 }
1036 if (suggestion.type === "text") {
1038 if (suggestion.type === "text") {
1037 // special case we don't want to "skip" display for
1039 // special case we don't want to "skip" display for
1038 return true
1040 return true
1039 }
1041 }
1040 return suggestion.value_display.toLowerCase().indexOf(queryLowerCase) !== -1;
1042 return suggestion.value_display.toLowerCase().indexOf(queryLowerCase) !== -1;
1041 };
1043 };
1042
1044
1043 var cleanContext = {
1045 var cleanContext = {
1044 repo_view_type: null,
1046 repo_view_type: null,
1045
1047
1046 repo_id: null,
1048 repo_id: null,
1047 repo_name: "",
1049 repo_name: "",
1048
1050
1049 repo_group_id: null,
1051 repo_group_id: null,
1050 repo_group_name: null
1052 repo_group_name: null
1051 };
1053 };
1052 var removeGoToFilter = function () {
1054 var removeGoToFilter = function () {
1053 $('.searchTagHidable').hide();
1055 $('.searchTagHidable').hide();
1054 $('#main_filter').autocomplete(
1056 $('#main_filter').autocomplete(
1055 'setOptions', {params:{search_context: cleanContext}});
1057 'setOptions', {params:{search_context: cleanContext}});
1056 };
1058 };
1057
1059
1058 $('#main_filter').autocomplete({
1060 $('#main_filter').autocomplete({
1059 serviceUrl: pyroutes.url('goto_switcher_data'),
1061 serviceUrl: pyroutes.url('goto_switcher_data'),
1060 params: {
1062 params: {
1061 "search_context": templateContext.search_context
1063 "search_context": templateContext.search_context
1062 },
1064 },
1063 minChars:2,
1065 minChars:2,
1064 maxHeight:400,
1066 maxHeight:400,
1065 deferRequestBy: 300, //miliseconds
1067 deferRequestBy: 300, //miliseconds
1066 tabDisabled: true,
1068 tabDisabled: true,
1067 autoSelectFirst: false,
1069 autoSelectFirst: false,
1068 containerClass: 'autocomplete-qfilter-suggestions',
1070 containerClass: 'autocomplete-qfilter-suggestions',
1069 formatResult: autocompleteMainFilterFormatResult,
1071 formatResult: autocompleteMainFilterFormatResult,
1070 lookupFilter: autocompleteMainFilterResult,
1072 lookupFilter: autocompleteMainFilterResult,
1071 onSelect: function (element, suggestion) {
1073 onSelect: function (element, suggestion) {
1072 handleSelect(element, suggestion);
1074 handleSelect(element, suggestion);
1073 return false;
1075 return false;
1074 },
1076 },
1075 onSearchError: function (element, query, jqXHR, textStatus, errorThrown) {
1077 onSearchError: function (element, query, jqXHR, textStatus, errorThrown) {
1076 if (jqXHR !== 'abort') {
1078 if (jqXHR !== 'abort') {
1077 var message = formatErrorMessage(jqXHR, textStatus, errorThrown);
1079 var message = formatErrorMessage(jqXHR, textStatus, errorThrown);
1078 SwalNoAnimation.fire({
1080 SwalNoAnimation.fire({
1079 icon: 'error',
1081 icon: 'error',
1080 title: _gettext('Error during search operation'),
1082 title: _gettext('Error during search operation'),
1081 html: '<span style="white-space: pre-line">{0}</span>'.format(message),
1083 html: '<span style="white-space: pre-line">{0}</span>'.format(message),
1082 }).then(function(result) {
1084 }).then(function(result) {
1083 window.location.reload();
1085 window.location.reload();
1084 })
1086 })
1085 }
1087 }
1086 },
1088 },
1087 onSearchStart: function (params) {
1089 onSearchStart: function (params) {
1088 $('.searchTag.searchTagIcon').html('<i class="icon-spin animate-spin"></i>')
1090 $('.searchTag.searchTagIcon').html('<i class="icon-spin animate-spin"></i>')
1089 },
1091 },
1090 onSearchComplete: function (query, suggestions) {
1092 onSearchComplete: function (query, suggestions) {
1091 $('.searchTag.searchTagIcon').html('<i class="icon-search"></i>')
1093 $('.searchTag.searchTagIcon').html('<i class="icon-search"></i>')
1092 },
1094 },
1093 });
1095 });
1094
1096
1095 showMainFilterBox = function () {
1097 showMainFilterBox = function () {
1096 $('#main_filter_help').toggle();
1098 $('#main_filter_help').toggle();
1097 };
1099 };
1098
1100
1099 $('#main_filter').on('keydown.autocomplete', function (e) {
1101 $('#main_filter').on('keydown.autocomplete', function (e) {
1100
1102
1101 var BACKSPACE = 8;
1103 var BACKSPACE = 8;
1102 var el = $(e.currentTarget);
1104 var el = $(e.currentTarget);
1103 if(e.which === BACKSPACE){
1105 if(e.which === BACKSPACE){
1104 var inputVal = el.val();
1106 var inputVal = el.val();
1105 if (inputVal === ""){
1107 if (inputVal === ""){
1106 removeGoToFilter()
1108 removeGoToFilter()
1107 }
1109 }
1108 }
1110 }
1109 });
1111 });
1110
1112
1111 var dismissNotice = function(noticeId) {
1113 var dismissNotice = function(noticeId) {
1112
1114
1113 var url = pyroutes.url('user_notice_dismiss',
1115 var url = pyroutes.url('user_notice_dismiss',
1114 {"user_id": templateContext.rhodecode_user.user_id});
1116 {"user_id": templateContext.rhodecode_user.user_id});
1115
1117
1116 var postData = {
1118 var postData = {
1117 'csrf_token': CSRF_TOKEN,
1119 'csrf_token': CSRF_TOKEN,
1118 'notice_id': noticeId,
1120 'notice_id': noticeId,
1119 };
1121 };
1120
1122
1121 var success = function(response) {
1123 var success = function(response) {
1122 $('#notice-message-' + noticeId).remove();
1124 $('#notice-message-' + noticeId).remove();
1123 return false;
1125 return false;
1124 };
1126 };
1125 var failure = function(data, textStatus, xhr) {
1127 var failure = function(data, textStatus, xhr) {
1126 alert("error processing request: " + textStatus);
1128 alert("error processing request: " + textStatus);
1127 return false;
1129 return false;
1128 };
1130 };
1129 ajaxPOST(url, postData, success, failure);
1131 ajaxPOST(url, postData, success, failure);
1130 }
1132 }
1131
1133
1132 var hideLicenseWarning = function () {
1134 var hideLicenseWarning = function () {
1133 var fingerprint = templateContext.session_attrs.license_fingerprint;
1135 var fingerprint = templateContext.session_attrs.license_fingerprint;
1134 storeUserSessionAttr('rc_user_session_attr.hide_license_warning', fingerprint);
1136 storeUserSessionAttr('rc_user_session_attr.hide_license_warning', fingerprint);
1135 $('#notifications').hide();
1137 $('#notifications').hide();
1136 }
1138 }
1137
1139
1138 var hideLicenseError = function () {
1140 var hideLicenseError = function () {
1139 var fingerprint = templateContext.session_attrs.license_fingerprint;
1141 var fingerprint = templateContext.session_attrs.license_fingerprint;
1140 storeUserSessionAttr('rc_user_session_attr.hide_license_error', fingerprint);
1142 storeUserSessionAttr('rc_user_session_attr.hide_license_error', fingerprint);
1141 $('#notifications').hide();
1143 $('#notifications').hide();
1142 }
1144 }
1143
1145
1144 </script>
1146 </script>
1145 <script src="${h.asset('js/rhodecode/base/keyboard-bindings.js', ver=c.rhodecode_version_hash)}"></script>
1147 <script src="${h.asset('js/rhodecode/base/keyboard-bindings.js', ver=c.rhodecode_version_hash)}"></script>
1146 </%def>
1148 </%def>
1147
1149
1148 <div class="modal" id="help_kb" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
1150 <div class="modal" id="help_kb" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
1149 <div class="modal-dialog">
1151 <div class="modal-dialog">
1150 <div class="modal-content">
1152 <div class="modal-content">
1151 <div class="modal-header">
1153 <div class="modal-header">
1152 <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
1154 <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
1153 <h4 class="modal-title" id="myModalLabel">${_('Keyboard shortcuts')}</h4>
1155 <h4 class="modal-title" id="myModalLabel">${_('Keyboard shortcuts')}</h4>
1154 </div>
1156 </div>
1155 <div class="modal-body">
1157 <div class="modal-body">
1156 <div class="block-left">
1158 <div class="block-left">
1157 <table class="keyboard-mappings">
1159 <table class="keyboard-mappings">
1158 <tbody>
1160 <tbody>
1159 <tr>
1161 <tr>
1160 <th></th>
1162 <th></th>
1161 <th>${_('Site-wide shortcuts')}</th>
1163 <th>${_('Site-wide shortcuts')}</th>
1162 </tr>
1164 </tr>
1163 <%
1165 <%
1164 elems = [
1166 elems = [
1165 ('/', 'Use quick search box'),
1167 ('/', 'Use quick search box'),
1166 ('g h', 'Goto home page'),
1168 ('g h', 'Goto home page'),
1167 ('g g', 'Goto my private gists page'),
1169 ('g g', 'Goto my private gists page'),
1168 ('g G', 'Goto my public gists page'),
1170 ('g G', 'Goto my public gists page'),
1169 ('g 0-9', 'Goto bookmarked items from 0-9'),
1171 ('g 0-9', 'Goto bookmarked items from 0-9'),
1170 ('n r', 'New repository page'),
1172 ('n r', 'New repository page'),
1171 ('n g', 'New gist page'),
1173 ('n g', 'New gist page'),
1172 ]
1174 ]
1173 %>
1175 %>
1174 %for key, desc in elems:
1176 %for key, desc in elems:
1175 <tr>
1177 <tr>
1176 <td class="keys">
1178 <td class="keys">
1177 <span class="key tag">${key}</span>
1179 <span class="key tag">${key}</span>
1178 </td>
1180 </td>
1179 <td>${desc}</td>
1181 <td>${desc}</td>
1180 </tr>
1182 </tr>
1181 %endfor
1183 %endfor
1182 </tbody>
1184 </tbody>
1183 </table>
1185 </table>
1184 </div>
1186 </div>
1185 <div class="block-left">
1187 <div class="block-left">
1186 <table class="keyboard-mappings">
1188 <table class="keyboard-mappings">
1187 <tbody>
1189 <tbody>
1188 <tr>
1190 <tr>
1189 <th></th>
1191 <th></th>
1190 <th>${_('Repositories')}</th>
1192 <th>${_('Repositories')}</th>
1191 </tr>
1193 </tr>
1192 <%
1194 <%
1193 elems = [
1195 elems = [
1194 ('g s', 'Goto summary page'),
1196 ('g s', 'Goto summary page'),
1195 ('g c', 'Goto changelog page'),
1197 ('g c', 'Goto changelog page'),
1196 ('g f', 'Goto files page'),
1198 ('g f', 'Goto files page'),
1197 ('g F', 'Goto files page with file search activated'),
1199 ('g F', 'Goto files page with file search activated'),
1198 ('g p', 'Goto pull requests page'),
1200 ('g p', 'Goto pull requests page'),
1199 ('g o', 'Goto repository settings'),
1201 ('g o', 'Goto repository settings'),
1200 ('g O', 'Goto repository access permissions settings'),
1202 ('g O', 'Goto repository access permissions settings'),
1201 ('t s', 'Toggle sidebar on some pages'),
1203 ('t s', 'Toggle sidebar on some pages'),
1202 ]
1204 ]
1203 %>
1205 %>
1204 %for key, desc in elems:
1206 %for key, desc in elems:
1205 <tr>
1207 <tr>
1206 <td class="keys">
1208 <td class="keys">
1207 <span class="key tag">${key}</span>
1209 <span class="key tag">${key}</span>
1208 </td>
1210 </td>
1209 <td>${desc}</td>
1211 <td>${desc}</td>
1210 </tr>
1212 </tr>
1211 %endfor
1213 %endfor
1212 </tbody>
1214 </tbody>
1213 </table>
1215 </table>
1214 </div>
1216 </div>
1215 </div>
1217 </div>
1216 <div class="modal-footer">
1218 <div class="modal-footer">
1217 </div>
1219 </div>
1218 </div><!-- /.modal-content -->
1220 </div><!-- /.modal-content -->
1219 </div><!-- /.modal-dialog -->
1221 </div><!-- /.modal-dialog -->
1220 </div><!-- /.modal -->
1222 </div><!-- /.modal -->
1221
1223
1222
1224
1223 <script type="text/javascript">
1225 <script type="text/javascript">
1224 (function () {
1226 (function () {
1225 "use sctrict";
1227 "use sctrict";
1226
1228
1227 // details block auto-hide menu
1229 // details block auto-hide menu
1228 $(document).mouseup(function(e) {
1230 $(document).mouseup(function(e) {
1229 var container = $('.details-inline-block');
1231 var container = $('.details-inline-block');
1230 if (!container.is(e.target) && container.has(e.target).length === 0) {
1232 if (!container.is(e.target) && container.has(e.target).length === 0) {
1231 $('.details-inline-block[open]').removeAttr('open')
1233 $('.details-inline-block[open]').removeAttr('open')
1232 }
1234 }
1233 });
1235 });
1234
1236
1235 var $sideBar = $('.right-sidebar');
1237 var $sideBar = $('.right-sidebar');
1236 var expanded = $sideBar.hasClass('right-sidebar-expanded');
1238 var expanded = $sideBar.hasClass('right-sidebar-expanded');
1237 var sidebarState = templateContext.session_attrs.sidebarState;
1239 var sidebarState = templateContext.session_attrs.sidebarState;
1238 var sidebarEnabled = $('aside.right-sidebar').get(0);
1240 var sidebarEnabled = $('aside.right-sidebar').get(0);
1239
1241
1240 if (sidebarState === 'expanded') {
1242 if (sidebarState === 'expanded') {
1241 expanded = true
1243 expanded = true
1242 } else if (sidebarState === 'collapsed') {
1244 } else if (sidebarState === 'collapsed') {
1243 expanded = false
1245 expanded = false
1244 }
1246 }
1245 if (sidebarEnabled) {
1247 if (sidebarEnabled) {
1246 // show sidebar since it's hidden on load
1248 // show sidebar since it's hidden on load
1247 $('.right-sidebar').show();
1249 $('.right-sidebar').show();
1248
1250
1249 // init based on set initial class, or if defined user session attrs
1251 // init based on set initial class, or if defined user session attrs
1250 if (expanded) {
1252 if (expanded) {
1251 window.expandSidebar();
1253 window.expandSidebar();
1252 window.updateStickyHeader();
1254 window.updateStickyHeader();
1253
1255
1254 } else {
1256 } else {
1255 window.collapseSidebar();
1257 window.collapseSidebar();
1256 window.updateStickyHeader();
1258 window.updateStickyHeader();
1257 }
1259 }
1258 }
1260 }
1259 })()
1261 })()
1260
1262
1261 </script>
1263 </script>
General Comments 0
You need to be logged in to leave comments. Login now